commit 8de2ffd5ebfc0ba90cf829675e07ae35ff2ea82a Author: Oleg Solovyov Date: Wed Oct 24 09:59:54 2018 +0300 - processui: implement a killbutton diff --git a/libksysguard/processui/ProcessModel.cpp b/libksysguard/processui/ProcessModel.cpp --- a/libksysguard/processui/ProcessModel.cpp +++ b/libksysguard/processui/ProcessModel.cpp @@ -1004,6 +1004,7 @@ static inline QVariant columnAlignment(c case ProcessModel::HeadingVmSize: case ProcessModel::HeadingIoWrite: case ProcessModel::HeadingIoRead: + case ProcessModel::HeadingKillBtn: case ProcessModel::HeadingVmPSS: return QVariant(Qt::AlignRight | Qt::AlignVCenter); case ProcessModel::HeadingTty: @@ -2077,6 +2078,7 @@ void ProcessModel::setupHeader() { headings << i18nc("process heading", "Relative Start Time"); headings << i18nc("process heading", "NNP"); headings << i18nc("process heading", "Command"); + headings << i18nc("process heading", "Kill Button"); #if HAVE_X11 if (d->mIsX11) { headings << i18nc("process heading", "X11 Memory"); diff --git a/libksysguard/processui/ProcessModel.h b/libksysguard/processui/ProcessModel.h --- a/libksysguard/processui/ProcessModel.h +++ b/libksysguard/processui/ProcessModel.h @@ -142,7 +142,7 @@ class KSYSGUARD_EXPORT ProcessModel : pu * setup header function, and make sure you increase PROCESSHEADERVERSION. This will ensure * that old saved settings won't be used */ -#define PROCESSHEADERVERSION 10 +#define PROCESSHEADERVERSION 11 enum { HeadingName=0, HeadingUser, HeadingPid, @@ -158,6 +158,7 @@ class KSYSGUARD_EXPORT ProcessModel : pu HeadingStartTime, HeadingNoNewPrivileges, HeadingCommand, + HeadingKillBtn, HeadingXMemory, HeadingXTitle, HeadingCGroup, diff --git a/libksysguard/processui/ksysguardprocesslist.cpp b/libksysguard/processui/ksysguardprocesslist.cpp --- a/libksysguard/processui/ksysguardprocesslist.cpp +++ b/libksysguard/processui/ksysguardprocesslist.cpp @@ -74,6 +74,97 @@ #ifdef DO_MODELCHECK #include "modeltest.h" #endif +KillButtonDelegate::KillButtonDelegate(QObject *parent) { + if(QTreeView *treeView = qobject_cast(parent)) + { + m_treeView = treeView; + m_btn = new QPushButton(i18n("Terminate process"), treeView); + m_btn->hide(); + m_counter = 0; + } +} + +void KillButtonDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const +{ + m_btn->setGeometry(opt.rect); + m_btn->setText(i18n("Terminate process")); + if(opt.state == QStyle::State_Selected) + { + painter->fillRect(opt.rect, opt.palette.highlight()); + } + QPixmap map = m_btn->grab(); + painter->drawPixmap(opt.rect.x(), opt.rect.y(), map); +} + +QSize KillButtonDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return m_btn->sizeHint(); +} + +bool KillButtonDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() == QEvent::MouseMove) { + if (index != m_lastUnderMouse) { + if (m_lastUnderMouse.isValid()) { + //model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole); + //emit needsUpdate(m_lastUnderMouse); + } + if (index.isValid() && index.column() == ProcessModel::HeadingKillBtn) { + //model->setData(index, (int)Hovered, Qt::UserRole); + //emit needsUpdate(index); + m_lastUnderMouse = index; + } else { + m_lastUnderMouse = QModelIndex(); + } + } + } + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) { + if (index != m_lastUnderMouse) { + if (m_lastUnderMouse.isValid()) { + //model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole); + //emit needsUpdate(m_lastUnderMouse); + } + if (index.isValid() && index.column() == ProcessModel::HeadingKillBtn) { + //model->setData(index, (int)Pressed, Qt::UserRole); + //emit needsUpdate(index); + qDebug() << "Emitting clicked and changing m_lastUnderMouse"; + emit clicked(index); + m_lastUnderMouse = index; + } else { + m_lastUnderMouse = QModelIndex(); + } + } else { + if (m_lastUnderMouse.isValid()) { + //model->setData(m_lastUnderMouse, (int)Pressed, Qt::UserRole); + //emit needsUpdate(m_lastUnderMouse); + qDebug() << "Emitting clicked"; + emit clicked(m_lastUnderMouse); + } + } + } + if (event->type() == QEvent::MouseButtonRelease) { + if (index != m_lastUnderMouse) { + if (m_lastUnderMouse.isValid()) { + //model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole); + //emit needsUpdate(m_lastUnderMouse); + } + if (index.isValid() && index.column() == ProcessModel::HeadingKillBtn) { + //model->setData(index, (int)Hovered, Qt::UserRole); + //emit needsUpdate(index); + m_lastUnderMouse = index; + } else { + m_lastUnderMouse = QModelIndex(); + } + } else { + if (m_lastUnderMouse.isValid()) { + //model->setData(m_lastUnderMouse, (int)Hovered, Qt::UserRole); + //emit needsUpdate(m_lastUnderMouse); + } + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + class ProgressBarItemDelegate : public QStyledItemDelegate { public: @@ -313,6 +404,9 @@ KSysGuardProcessList::KSysGuardProcessLi new ModelTest(&d->mModel, this); #endif d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView)); + KillButtonDelegate *delegate = new KillButtonDelegate(d->mUi->treeView); + d->mUi->treeView->setItemDelegateForColumn(ProcessModel::HeadingKillBtn, delegate); + connect(delegate, &KillButtonDelegate::clicked, this, &KSysGuardProcessList::killProcess); d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->mUi->treeView->header(), &QWidget::customContextMenuRequested, this, &KSysGuardProcessList::showColumnContextMenu); @@ -347,6 +441,7 @@ KSysGuardProcessList::KSysGuardProcessLi d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory); + d->mUi->treeView->header()->hideSection(ProcessModel::HeadingKillBtn); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCGroup); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingMACContext); d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmPSS); @@ -736,7 +831,7 @@ void KSysGuardProcessList::showColumnCon break; } } - if(anyOtherVisibleColumns) { + if(anyOtherVisibleColumns && index != ProcessModel::HeadingKillBtn) { //selected a column. Give the option to hide it action = new QAction(&menu); action->setData(-index-1); //We set data to be negative (and minus 1) to hide a column, and positive to show a column @@ -750,7 +845,7 @@ void KSysGuardProcessList::showColumnCon if(d->mUi->treeView->header()->sectionsHidden()) { for(int i = 0; i < num_headings; ++i) { - if(d->mUi->treeView->header()->isSectionHidden(i)) { + if(d->mUi->treeView->header()->isSectionHidden(i) && i != ProcessModel::HeadingKillBtn) { #ifndef HAVE_XRES if(i == ProcessModel::HeadingXMemory) continue; @@ -1351,32 +1446,43 @@ bool KSysGuardProcessList::killProcesses return false; } +void KSysGuardProcessList::killProcess(const QModelIndex index) +{ + qDebug() << "Row" << index.row(); + QModelIndex realIndex = d->mFilterModel.mapToSource(index); + KSysGuard::Process *process = d->mModel.getProcessAtIndex(realIndex.row()); + long long pid = process->pid(); + qDebug() << "Adding pid" << pid; + QList< long long> list; + list << pid; + sendSignalToProcesses(list, SIGTERM, true); + //connect(d->mUi->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KSysGuardProcessList::killSelectedProcesses); + //d->mUi->treeView->selectionModel()->select(index, QItemSelectionModel::Rows); +} + void KSysGuardProcessList::killSelectedProcesses() { sendSignalToSelectedProcesses(SIGTERM, true); + //disconnect(d->mUi->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KSysGuardProcessList::killSelectedProcesses); } -void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm) +void KSysGuardProcessList::sendSignalToProcesses(const QList &pids, int sig, bool confirm) { - QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); - QStringList selectedAsStrings; - QList< long long> selectedPids; - - QList processes = selectedProcesses(); - foreach(KSysGuard::Process *process, processes) { - selectedPids << process->pid(); + QStringList asStrings; + foreach(long long pid, pids) { if (!confirm) continue; - QString name = d->mModel.getStringForProcess(process); - selectedAsStrings << name; + QString name = d->mModel.getStringForProcess(d->mModel.getProcess(pid)); + qDebug() << "String" << name; + asStrings << name; } - if (selectedPids.isEmpty()) { + if (pids.isEmpty()) { if (confirm) KMessageBox::sorry(this, i18n("You must select a process first.")); return; } else if (confirm && (sig == SIGTERM || sig == SIGKILL)) { - int count = selectedAsStrings.count(); + int count = asStrings.count(); QString msg; QString title; QString dontAskAgainKey; @@ -1397,7 +1503,7 @@ void KSysGuardProcessList::sendSignalToS closeButton = i18n("Kill"); } - int res = KMessageBox::warningContinueCancelList(this, msg, selectedAsStrings, + int res = KMessageBox::warningContinueCancelList(this, msg, asStrings, title, KGuiItem(closeButton, QStringLiteral("process-stop")), KStandardGuiItem::cancel(), @@ -1409,10 +1515,10 @@ void KSysGuardProcessList::sendSignalToS //We have shown a GUI dialog box, which processes events etc. //So processes is NO LONGER VALID - if (!killProcesses(selectedPids, sig)) + if (!killProcesses(pids, sig)) return; if (sig == SIGTERM || sig == SIGKILL) { - foreach (long long pid, selectedPids) { + foreach (long long pid, pids) { KSysGuard::Process *process = d->mModel.getProcess(pid); if (process) process->timeKillWasSent().start(); @@ -1422,6 +1528,24 @@ void KSysGuardProcessList::sendSignalToS updateList(); } +void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm) +{ + QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows(); + QStringList selectedAsStrings; + QList< long long> selectedPids; + + QList processes = selectedProcesses(); + foreach(KSysGuard::Process *process, processes) { + selectedPids << process->pid(); + if (!confirm) + continue; + QString name = d->mModel.getStringForProcess(process); + selectedAsStrings << name; + } + + sendSignalToProcesses(selectedPids, sig, confirm); +} + bool KSysGuardProcessList::showTotals() const { return d->mModel.showTotals(); } diff --git a/libksysguard/processui/ksysguardprocesslist.h b/libksysguard/processui/ksysguardprocesslist.h index 9e04f19..cbff357 100644 --- a/libksysguard/processui/ksysguardprocesslist.h +++ b/libksysguard/processui/ksysguardprocesslist.h @@ -26,6 +26,8 @@ #include #include +#include +#include #include @@ -46,6 +48,26 @@ struct KSysGuardProcessListPrivate; * update rate and the process filter. The buttons are used to force * an immediate update and to kill a process. */ +class KillButtonDelegate : public QStyledItemDelegate +{ + Q_OBJECT + public: + KillButtonDelegate(QObject *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + + Q_SIGNALS: + void clicked(QModelIndex index); + + private: + QModelIndex m_lastUnderMouse; + QPushButton *m_btn; + QTreeView *m_treeView; + QPersistentModelIndex currentEditedCellIndex; + int m_counter; +}; + class Q_DECL_EXPORT KSysGuardProcessList : public QWidget { Q_OBJECT @@ -136,6 +158,17 @@ class Q_DECL_EXPORT KSysGuardProcessList : public QWidget */ void sendSignalToSelectedProcesses(int sig, bool confirm); + /** Send a signal to a given processes. + * @p pids A list of PIDs that should be sent the signal + * @p confirm - If true, pops up a dialog box to confirm with the user + */ + void sendSignalToProcesses(const QList< long long> &pids, int sig, bool confirm); + + /** Send a signal to a given process. + * @p index Index of a given process allowing a user to kill it. + */ + void killProcess(const QModelIndex index); + /** Send a signal to a list of given processes. * @p pids A list of PIDs that should be sent the signal * @p sig The signal to send.