diff --git a/libksysguard/processui/ProcessModel.h b/libksysguard/processui/ProcessModel.h --- a/libksysguard/processui/ProcessModel.h +++ b/libksysguard/processui/ProcessModel.h @@ -138,7 +138,7 @@ * setup header function, and make sure you increase PROCESSHEADERVERSION. This will ensure * that old saved settings won't be used */ -#define PROCESSHEADERVERSION 6 +#define PROCESSHEADERVERSION 7 enum { HeadingName=0, HeadingUser, HeadingPid, @@ -153,6 +153,7 @@ HeadingSharedMemory, HeadingStartTime, HeadingCommand, + HeadingKillBtn, HeadingXMemory, HeadingXTitle }; diff --git a/libksysguard/processui/ProcessModel.cpp b/libksysguard/processui/ProcessModel.cpp --- a/libksysguard/processui/ProcessModel.cpp +++ b/libksysguard/processui/ProcessModel.cpp @@ -968,6 +968,7 @@ case HeadingUser: case HeadingCPUUsage: case HeadingCPUTime: + case HeadingKillBtn: return QVariant(Qt::AlignCenter); } @@ -1933,6 +1934,7 @@ headings << i18nc("process heading", "Shared Mem"); headings << i18nc("process heading", "Relative Start Time"); 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/ksysguardprocesslist.h b/libksysguard/processui/ksysguardprocesslist.h --- a/libksysguard/processui/ksysguardprocesslist.h +++ b/libksysguard/processui/ksysguardprocesslist.h @@ -26,6 +26,8 @@ #include #include +#include +#include #include @@ -46,6 +48,26 @@ * 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 @@ */ 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. diff --git a/libksysguard/processui/ksysguardprocesslist.cpp b/libksysguard/processui/ksysguardprocesslist.cpp --- a/libksysguard/processui/ksysguardprocesslist.cpp +++ b/libksysguard/processui/ksysguardprocesslist.cpp @@ -69,6 +69,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("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("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: @@ -301,6 +392,9 @@ 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); @@ -334,6 +428,7 @@ 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); // NOTE! After this is all setup, the settings for the header are restored // from the user's last run. (in restoreHeaderState) // So making changes here only affects the default settings. To @@ -645,7 +740,7 @@ 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 @@ -659,7 +754,7 @@ 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; @@ -1305,32 +1400,43 @@ return true; } +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; @@ -1351,7 +1457,7 @@ 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(), @@ -1363,10 +1469,10 @@ //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(); @@ -1376,6 +1482,24 @@ 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(); }