kde-workspace/libs/ksysguard/processui/ksysguardprocesslist.cpp
Ivailo Monev 5e6ea65ed8 libs: typo
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-12-07 01:57:39 +02:00

1374 lines
58 KiB
C++

/*
KSysGuard, the KDE System Guard
Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>
Copyright (c) 2006 - 2007 John Tapsell <john.tapsell@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "moc_ksysguardprocesslist.cpp"
#include "ksysguardprocesslist.h"
#include "config-X11.h"
#include <QApplication>
#include <QTimer>
#include <QList>
#include <QtGui/qevent.h>
#include <QHeaderView>
#include <QAction>
#include <QMenu>
#include <QSet>
#include <QComboBox>
#include <QStyle>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QLineEdit>
#include <QSignalMapper>
#include <QToolTip>
#include <QAbstractItemModel>
#include <QtDBus>
#include <signal.h> //For SIGTERM
#include <kauthorization.h>
#include <kaction.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kdialog.h>
#include <kicon.h>
#include <kdebug.h>
#include <KWindowSystem>
#include "ReniceDlg.h"
#include "ui_ProcessWidgetUI.h"
#include <sys/types.h>
#include <unistd.h>
//Trolltech have a testing class for classes that inherit QAbstractItemModel. If you want to run with this run-time testing enabled, put the modeltest.* files in this directory and uncomment the next line
//#define DO_MODELCHECK
#ifdef DO_MODELCHECK
#include "modeltest.h"
#endif
class ProgressBarItemDelegate : public QStyledItemDelegate
{
public:
ProgressBarItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
}
virtual void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
{
QStyleOptionViewItemV4 option = opt;
initStyleOption(&option,index);
float percentage = index.data(ProcessModel::PercentageRole).toFloat();
if (percentage >= 0)
drawPercentageDisplay(painter,option, percentage);
else
QStyledItemDelegate::paint(painter, option, index);
}
private:
inline void drawPercentageDisplay(QPainter *painter, QStyleOptionViewItemV4 &option, float percentage) const
{
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
// draw the background
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
cg = QPalette::Inactive;
//Now draw our percentage thingy
const QRect &rect = option.rect;
int size = qMin(percentage,1.0f) * rect.width();
if(size > 2 ) { //make sure the line will have a width of more than 1 pixel
painter->setPen(Qt::NoPen);
QColor color = option.palette.color(cg, QPalette::Link);
color.setAlpha(50);
painter->fillRect( rect.x(), rect.y(), size, rect.height(), color);
}
// draw the text
if (!option.text.isEmpty()) {
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
} else {
painter->setPen(option.palette.color(cg, QPalette::Text));
}
painter->setFont(option.font);
QTextOption textOption;
textOption.setWrapMode(QTextOption::NoWrap);
textOption.setTextDirection(option.direction);
textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
painter->drawText(textRect, option.text, textOption);
}
// draw the focus rect
if (option.state & QStyle::State_HasFocus) {
QStyleOptionFocusRect o;
o.QStyleOption::operator=(option);
o.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, option.widget);
o.state |= QStyle::State_KeyboardFocusChange;
o.state |= QStyle::State_Item;
QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
? QPalette::Normal : QPalette::Disabled;
o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected)
? QPalette::Highlight : QPalette::Window);
style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget);
}
}
};
struct KSysGuardProcessListPrivate {
KSysGuardProcessListPrivate(KSysGuardProcessList* q, const QString &hostName)
: mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(NULL), mUpdateTimer(NULL)
{
mNeedToExpandInit = false;
mNumItemsSelected = -1;
mResortCountDown = 2; //The items added initially will be already sorted, but without CPU info. On the second refresh we will have CPU usage, so /then/ we can resort
renice = new KAction(i18np("Set Priority...", "Set Priority...", 1), q);
renice->setShortcut(Qt::Key_F8);
selectParent = new KAction(i18n("Jump to Parent Process"), q);
selectTracer = new KAction(i18n("Jump to Process Debugging This One"), q);
window = new KAction(i18n("Show Application Window"), q);
resume = new KAction(i18n("Resume Stopped Process"), q);
terminate = new KAction(i18np("End Process", "End Processes", 1), q);
terminate->setIcon(KIcon("process-stop"));
terminate->setShortcut(Qt::Key_Delete);
kill = new KAction(i18np("Forcibly Kill Process", "Forcibly Kill Processes", 1), q);
kill->setIcon(KIcon("process-stop"));
kill->setShortcut(Qt::SHIFT + Qt::Key_Delete);
sigStop = new KAction(i18n("Suspend (STOP)"), q);
sigCont = new KAction(i18n("Continue (CONT)"), q);
sigHup = new KAction(i18n("Hangup (HUP)"), q);
sigInt = new KAction(i18n("Interrupt (INT)"), q);
sigTerm = new KAction(i18n("Terminate (TERM)"), q);
sigKill = new KAction(i18n("Kill (KILL)"), q);
sigUsr1 = new KAction(i18n("User 1 (USR1)"), q);
sigUsr2 = new KAction(i18n("User 2 (USR2)"), q);
//Set up '/' as a shortcut to jump to the quick search text widget
jumpToSearchFilter = new KAction(i18n("Focus on Quick Search"), q);
jumpToSearchFilter->setShortcuts(QList<QKeySequence>() << QKeySequence::Find << '/');
}
~KSysGuardProcessListPrivate() { delete mUi; mUi = NULL; }
/** The number rows and their children for the given parent in the mFilterModel model */
int totalRowCount(const QModelIndex &parent) const;
/** fire a timer event if we are set to use our internal timer*/
void fireTimerEvent();
/** The process model. This contains all the data on all the processes running on the system */
ProcessModel mModel;
/** The process filter. The mModel is connected to this, and this filter model connects to the view. This lets us
* sort the view and filter (by using the combo box or the search line)
*/
ProcessFilter mFilterModel;
/** The graphical user interface for this process list widget, auto-generated by Qt Designer */
Ui::ProcessWidget *mUi;
/** The context menu when you right click on a process */
QMenu *mProcessContextMenu;
/** A timer to call updateList() every mUpdateIntervalMSecs.
* NULL is mUpdateIntervalMSecs is <= 0. */
QTimer *mUpdateTimer;
/** The time to wait, in milliseconds, between updating the process list */
int mUpdateIntervalMSecs;
/** Number of items that are selected */
int mNumItemsSelected;
/** A counter to mark when to resort, so that we do not resort on every update */
int mResortCountDown;
bool mNeedToExpandInit;
KAction *renice;
KAction *terminate;
KAction *kill;
KAction *selectParent;
KAction *selectTracer;
KAction *jumpToSearchFilter;
KAction *window;
KAction *resume;
KAction *sigStop;
KAction *sigCont;
KAction *sigHup;
KAction *sigInt;
KAction *sigTerm;
KAction *sigKill;
KAction *sigUsr1;
KAction *sigUsr2;
};
KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostName)
: QWidget(parent), d(new KSysGuardProcessListPrivate(this, hostName))
{
qRegisterMetaType<QList<long long> >();
qDBusRegisterMetaType<QList<long long> >();
d->mUpdateIntervalMSecs = 0; //Set process to not update manually by default
d->mUi->setupUi(this);
d->mFilterModel.setSourceModel(&d->mModel);
d->mUi->treeView->setModel(&d->mFilterModel);
#ifdef DO_MODELCHECK
new ModelTest(&d->mModel, this);
#endif
d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView));
d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(d->mUi->treeView->header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showColumnContextMenu(QPoint)));
d->mProcessContextMenu = new QMenu(d->mUi->treeView);
d->mUi->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(d->mUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showProcessContextMenu(QPoint)));
d->mUi->treeView->header()->setClickable(true);
d->mUi->treeView->header()->setSortIndicatorShown(true);
d->mUi->treeView->header()->setCascadingSectionResizes(false);
connect(d->mUi->btnKillProcess, SIGNAL(clicked()), this, SLOT(killSelectedProcesses()));
connect(d->mUi->txtFilter, SIGNAL(textChanged(QString)), this, SLOT(filterTextChanged(QString)));
connect(d->mUi->cmbFilter, SIGNAL(currentIndexChanged(int)), this, SLOT(setStateInt(int)));
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
connect(d->mUi->treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()));
connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
connect(&d->mFilterModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(processListChanged()));
setMinimumSize(sizeHint());
d->mFilterModel.setFilterKeyColumn(-1);
/* Hide various columns by default, to reduce information overload */
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmSize);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNiceness);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingTty);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCommand);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPid);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCPUTime);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite);
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory);
// 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
// test changes temporarily, comment out the lines in restoreHeaderState.
// When you are happy with the changes and want to commit, increase the
// value of PROCESSHEADERVERSION. This will force the header state
// to be reset back to the defaults for all users.
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingCPUUsage, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingCPUUsage));
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingMemory));
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingSharedMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingSharedMemory));
d->mUi->treeView->header()->setResizeMode(0, QHeaderView::Interactive);
d->mUi->treeView->header()->setStretchLastSection(true);
//Process names can have mixed case. Make the filter case insensitive.
d->mFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
d->mFilterModel.setSortCaseSensitivity(Qt::CaseInsensitive);
d->mUi->txtFilter->installEventFilter(this);
d->mUi->treeView->installEventFilter(this);
d->mUi->treeView->setDragEnabled(true);
d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly);
//Sort by username by default
d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder);
// Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked
QSignalMapper *signalMapper = new QSignalMapper(this);
QList<QAction *> actions;
actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter;
actions << d->resume << d->sigStop << d->sigCont << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2;
foreach(QAction *action, actions) {
addAction(action);
connect(action, SIGNAL(triggered(bool)), signalMapper, SLOT(map()));
signalMapper->setMapping(action, action);
}
connect(signalMapper, SIGNAL(mapped(QObject*)), SLOT(actionTriggered(QObject*)));
retranslateUi();
d->mUi->btnKillProcess->setIcon(KIcon("process-stop"));
d->mUi->btnKillProcess->setToolTip(i18n("<qt>End the selected process. Warning - you may lose unsaved work.<br>Right click on a process to send other signals.<br>See What's This for technical information.<br>To target a specific window to kill, press Ctrl+Alt+Esc at any time."));
}
KSysGuardProcessList::~KSysGuardProcessList()
{
delete d;
}
QTreeView *KSysGuardProcessList::treeView() const {
return d->mUi->treeView;
}
QLineEdit *KSysGuardProcessList::filterLineEdit() const {
return d->mUi->txtFilter;
}
ProcessFilter::State KSysGuardProcessList::state() const
{
return d->mFilterModel.filter();
}
void KSysGuardProcessList::setStateInt(int state) {
setState((ProcessFilter::State) state);
d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
}
void KSysGuardProcessList::setState(ProcessFilter::State state)
{ //index is the item the user selected in the combo box
d->mFilterModel.setFilter(state);
d->mModel.setSimpleMode( (state != ProcessFilter::AllProcessesInTreeForm) );
d->mUi->cmbFilter->setCurrentIndex( (int)state);
if(isVisible())
expandInit();
}
void KSysGuardProcessList::filterTextChanged(const QString &newText) {
d->mFilterModel.setFilterRegExp(newText.trimmed());
if(isVisible())
expandInit();
d->mUi->btnKillProcess->setEnabled( d->mUi->treeView->selectionModel()->hasSelection() );
d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
}
int KSysGuardProcessList::visibleProcessesCount() const {
//This assumes that all the visible rows are processes. This is true currently, but might not be
//true if we add support for showing threads etc
if(d->mModel.isSimpleMode())
return d->mFilterModel.rowCount();
return d->totalRowCount(QModelIndex());
}
int KSysGuardProcessListPrivate::totalRowCount(const QModelIndex &parent ) const {
int numRows = mFilterModel.rowCount(parent);
int total = numRows;
for (int i = 0; i < numRows; ++i) {
QModelIndex index = mFilterModel.index(i, 0,parent);
//if it has children add the total
if (mFilterModel.hasChildren(index))
total += totalRowCount(index);
}
return total;
}
void KSysGuardProcessList::selectionChanged()
{
int numSelected = d->mUi->treeView->selectionModel()->selectedRows().size();
if(numSelected == d->mNumItemsSelected)
return;
d->mNumItemsSelected = numSelected;
d->mUi->btnKillProcess->setEnabled( numSelected != 0 );
d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected));
d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected));
d->terminate->setText(i18ncp("Context menu", "End Process", "End Processes", numSelected));
}
void KSysGuardProcessList::showProcessContextMenu(const QModelIndex &index) {
if(!index.isValid()) return;
QRect rect = d->mUi->treeView->visualRect(index);
QPoint point(rect.x() + rect.width()/4, rect.y() + rect.height()/2 );
showProcessContextMenu(point);
}
void KSysGuardProcessList::showProcessContextMenu(const QPoint &point) {
d->mProcessContextMenu->clear();
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
int numProcesses = selectedIndexes.size();
if(numProcesses == 0) {
//No processes selected, so no process context menu
//Check just incase we have no columns visible. In which case show the column context menu
//so that users can unhide columns if there are no columns visible
for(int i = 0; i < d->mFilterModel.columnCount(); ++i) {
if(!d->mUi->treeView->header()->isSectionHidden(i))
return;
}
showColumnContextMenu(point);
return;
}
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
//If the selected process is a zombie, do not bother offering renice and kill options
bool showSignalingEntries = numProcesses != 1 || process->status != KSysGuard::Process::Zombie;
if(showSignalingEntries) {
d->mProcessContextMenu->addAction(d->renice);
QMenu *signalMenu = d->mProcessContextMenu->addMenu(i18n("Send Signal"));
signalMenu->addAction(d->sigStop);
signalMenu->addAction(d->sigCont);
signalMenu->addAction(d->sigHup);
signalMenu->addAction(d->sigInt);
signalMenu->addAction(d->sigTerm);
signalMenu->addAction(d->sigKill);
signalMenu->addAction(d->sigUsr1);
signalMenu->addAction(d->sigUsr2);
}
if(numProcesses == 1 && process->parent_pid > 1) {
//As a design decision, I do not show the 'Jump to parent process' option when the
//parent is just 'init'.
KSysGuard::Process *parent_process = d->mModel.getProcess(process->parent_pid);
if(parent_process) { //it should not be possible for this process to not exist, but check just incase
QString parent_name = parent_process->name;
if(parent_name.size() > 20) //Elide the text if it is too long
parent_name = parent_process->name.left(15) + QString::fromUtf8("");
d->selectParent->setText(i18n("Jump to Parent Process (%1)", parent_name));
d->mProcessContextMenu->addAction(d->selectParent);
}
}
if(numProcesses == 1 && process->tracerpid >= 0) {
//If the process is being debugged, offer to select it
d->mProcessContextMenu->addAction(d->selectTracer);
}
if (numProcesses == 1 && !d->mModel.data(realIndex, ProcessModel::WindowIdRole).isNull()) {
d->mProcessContextMenu->addAction(d->window);
}
if(numProcesses == 1 && process->status == KSysGuard::Process::Stopped) {
//If the process is stopped, offer to resume it
d->mProcessContextMenu->addAction(d->resume);
}
if (showSignalingEntries) {
d->mProcessContextMenu->addSeparator();
d->mProcessContextMenu->addAction(d->terminate);
if (numProcesses == 1 && !process->timeKillWasSent.isNull())
d->mProcessContextMenu->addAction(d->kill);
}
d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point));
}
void KSysGuardProcessList::actionTriggered(QObject *object) {
if(!isVisible()) //Ignore triggered actions if we are not visible!
return;
//Reset the text back to normal
d->selectParent->setText(i18n("Jump to Parent Process"));
QAction *result = qobject_cast<QAction *>(object);
if(result == 0) {
//Escape was pressed. Do nothing.
} else if(result == d->renice) {
reniceSelectedProcesses();
} else if(result == d->terminate) {
sendSignalToSelectedProcesses(SIGTERM, true);
} else if(result == d->kill) {
sendSignalToSelectedProcesses(SIGKILL, true);
} else if(result == d->selectParent) {
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
int numProcesses = selectedIndexes.size();
if(numProcesses == 0) return; //No processes selected
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
if(process)
selectAndJumpToProcess(process->parent_pid);
} else if(result == d->selectTracer) {
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
int numProcesses = selectedIndexes.size();
if(numProcesses == 0) return; //No processes selected
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
if(process)
selectAndJumpToProcess(process->tracerpid);
} else if(result == d->window) {
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
int numProcesses = selectedIndexes.size();
if(numProcesses == 0) return; //No processes selected
foreach( const QModelIndex &index, selectedIndexes) {
QModelIndex realIndex = d->mFilterModel.mapToSource(index);
QVariant widVar= d->mModel.data(realIndex, ProcessModel::WindowIdRole);
if( !widVar.isNull() ) {
int wid = widVar.toInt();
KWindowSystem::activateWindow(wid);
}
}
} else if(result == d->jumpToSearchFilter) {
d->mUi->txtFilter->setFocus();
} else {
int sig;
if(result == d->resume || result == d->sigCont)
sig = SIGCONT; //Despite the function name, this sends a signal, rather than kill it. Silly unix :)
else if(result == d->sigStop)
sig = SIGSTOP;
else if(result == d->sigHup)
sig = SIGHUP;
else if(result == d->sigInt)
sig = SIGINT;
else if(result == d->sigTerm)
sig = SIGTERM;
else if(result == d->sigKill)
sig = SIGKILL;
else if(result == d->sigUsr1)
sig = SIGUSR1;
else if(result == d->sigUsr2)
sig = SIGUSR2;
else
return;
sendSignalToSelectedProcesses(sig, false);
}
}
void KSysGuardProcessList::selectAndJumpToProcess(int pid) {
KSysGuard::Process *process = d->mModel.getProcess(pid);
if(!process) return;
QModelIndex sourceIndex = d->mModel.getQModelIndex(process, 0);
QModelIndex filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
if(!filterIndex.isValid() && !d->mUi->txtFilter->text().isEmpty()) {
//The filter is preventing us from finding the parent. Clear the filter
//(It could also be the combo box - should we deal with that case as well?)
d->mUi->txtFilter->clear();
filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
}
d->mUi->treeView->clearSelection();
d->mUi->treeView->setCurrentIndex(filterIndex);
d->mUi->treeView->scrollTo( filterIndex, QAbstractItemView::PositionAtCenter);
}
void KSysGuardProcessList::showColumnContextMenu(const QPoint &point){
QMenu menu;
QAction *action;
int num_headings = d->mFilterModel.columnCount();
int index = d->mUi->treeView->header()->logicalIndexAt(point);
if(index >= 0) {
bool anyOtherVisibleColumns = false;
for(int i = 0; i < num_headings; ++i) {
if(i != index && !d->mUi->treeView->header()->isSectionHidden(i)) {
anyOtherVisibleColumns = true;
break;
}
}
if(anyOtherVisibleColumns) {
//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
action->setText(i18n("Hide Column '%1'", d->mFilterModel.headerData(index, Qt::Horizontal, Qt::DisplayRole).toString()));
menu.addAction(action);
if(d->mUi->treeView->header()->sectionsHidden()) {
menu.addSeparator();
}
}
}
if(d->mUi->treeView->header()->sectionsHidden()) {
for(int i = 0; i < num_headings; ++i) {
if(d->mUi->treeView->header()->isSectionHidden(i)) {
#ifndef HAVE_XRES
if(i == ProcessModel::HeadingXMemory)
continue;
#endif
action = new QAction(&menu);
action->setText(i18n("Show Column '%1'", d->mFilterModel.headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()));
action->setData(i); //We set data to be negative (and minus 1) to hide a column, and positive to show a column
menu.addAction(action);
}
}
}
QAction *actionAuto = NULL;
QAction *actionKB = NULL;
QAction *actionMB = NULL;
QAction *actionGB = NULL;
QAction *actionPercentage = NULL;
QAction *actionShowCmdlineOptions = NULL;
QAction *actionShowTooltips = NULL;
QAction *actionNormalizeCPUUsage = NULL;
QAction *actionIoCharacters = NULL;
QAction *actionIoSyscalls = NULL;
QAction *actionIoActualCharacters = NULL;
QAction *actionIoShowRate = NULL;
bool showIoRate = false;
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
showIoRate = d->mModel.ioInformation() == ProcessModel::BytesRate ||
d->mModel.ioInformation() == ProcessModel::SyscallsRate ||
d->mModel.ioInformation() == ProcessModel::ActualBytesRate;
if( index == ProcessModel::HeadingVmSize || index == ProcessModel::HeadingMemory || index == ProcessModel::HeadingXMemory || index == ProcessModel::HeadingSharedMemory || ( (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) && d->mModel.ioInformation() != ProcessModel::Syscalls)) {
//If the user right clicks on a column that contains a memory size, show a toggle option for displaying
//the memory in different units. e.g. "2000 k" or "2 m"
menu.addSeparator()->setText(i18n("Display Units"));
QActionGroup *unitsGroup = new QActionGroup(&menu);
/* Automatic (human readable)*/
actionAuto = new QAction(&menu);
actionAuto->setText(i18n("Mixed"));
actionAuto->setCheckable(true);
menu.addAction(actionAuto);
unitsGroup->addAction(actionAuto);
/* Kilobytes */
actionKB = new QAction(&menu);
actionKB->setText((showIoRate)?i18n("Kilobytes per second"):i18n("Kilobytes"));
actionKB->setCheckable(true);
menu.addAction(actionKB);
unitsGroup->addAction(actionKB);
/* Megabytes */
actionMB = new QAction(&menu);
actionMB->setText((showIoRate)?i18n("Megabytes per second"):i18n("Megabytes"));
actionMB->setCheckable(true);
menu.addAction(actionMB);
unitsGroup->addAction(actionMB);
/* Gigabytes */
actionGB = new QAction(&menu);
actionGB->setText((showIoRate)?i18n("Gigabytes per second"):i18n("Gigabytes"));
actionGB->setCheckable(true);
menu.addAction(actionGB);
unitsGroup->addAction(actionGB);
ProcessModel::Units currentUnit;
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
currentUnit = d->mModel.ioUnits();
} else {
actionPercentage = new QAction(&menu);
actionPercentage->setText(i18n("Percentage"));
actionPercentage->setCheckable(true);
menu.addAction(actionPercentage);
unitsGroup->addAction(actionPercentage);
currentUnit = d->mModel.units();
}
switch(currentUnit) {
case ProcessModel::UnitsAuto:
actionAuto->setChecked(true);
break;
case ProcessModel::UnitsKB:
actionKB->setChecked(true);
break;
case ProcessModel::UnitsMB:
actionMB->setChecked(true);
break;
case ProcessModel::UnitsGB:
actionGB->setChecked(true);
break;
case ProcessModel::UnitsPercentage:
actionPercentage->setChecked(true);
break;
default:
break;
}
unitsGroup->setExclusive(true);
} else if(index == ProcessModel::HeadingName) {
menu.addSeparator();
actionShowCmdlineOptions = new QAction(&menu);
actionShowCmdlineOptions->setText(i18n("Display command line options"));
actionShowCmdlineOptions->setCheckable(true);
actionShowCmdlineOptions->setChecked(d->mModel.isShowCommandLineOptions());
menu.addAction(actionShowCmdlineOptions);
} else if(index == ProcessModel::HeadingCPUUsage) {
menu.addSeparator();
actionNormalizeCPUUsage = new QAction(&menu);
actionNormalizeCPUUsage->setText(i18n("Divide CPU usage by number of CPUs"));
actionNormalizeCPUUsage->setCheckable(true);
actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage());
menu.addAction(actionNormalizeCPUUsage);
}
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
menu.addSeparator()->setText(i18n("Displayed Information"));
QActionGroup *ioInformationGroup = new QActionGroup(&menu);
actionIoCharacters = new QAction(&menu);
actionIoCharacters->setText(i18n("Characters read/written"));
actionIoCharacters->setCheckable(true);
menu.addAction(actionIoCharacters);
ioInformationGroup->addAction(actionIoCharacters);
actionIoSyscalls = new QAction(&menu);
actionIoSyscalls->setText(i18n("Number of Read/Write operations"));
actionIoSyscalls->setCheckable(true);
menu.addAction(actionIoSyscalls);
ioInformationGroup->addAction(actionIoSyscalls);
actionIoActualCharacters = new QAction(&menu);
actionIoActualCharacters->setText(i18n("Bytes actually read/written"));
actionIoActualCharacters->setCheckable(true);
menu.addAction(actionIoActualCharacters);
ioInformationGroup->addAction(actionIoActualCharacters);
actionIoShowRate = new QAction(&menu);
actionIoShowRate->setText(i18n("Show I/O rate"));
actionIoShowRate->setCheckable(true);
actionIoShowRate->setChecked(showIoRate);
menu.addAction(actionIoShowRate);
switch(d->mModel.ioInformation()) {
case ProcessModel::Bytes:
case ProcessModel::BytesRate:
actionIoCharacters->setChecked(true);
break;
case ProcessModel::Syscalls:
case ProcessModel::SyscallsRate:
actionIoSyscalls->setChecked(true);
break;
case ProcessModel::ActualBytes:
case ProcessModel::ActualBytesRate:
actionIoActualCharacters->setChecked(true);
break;
default:
break;
}
}
menu.addSeparator();
actionShowTooltips = new QAction(&menu);
actionShowTooltips->setCheckable(true);
actionShowTooltips->setChecked(d->mModel.isShowingTooltips());
actionShowTooltips->setText(i18n("Show Tooltips"));
menu.addAction(actionShowTooltips);
QAction *result = menu.exec(d->mUi->treeView->header()->mapToGlobal(point));
if(!result) return; //Menu cancelled
if(result == actionAuto) {
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
d->mModel.setIoUnits(ProcessModel::UnitsAuto);
else
d->mModel.setUnits(ProcessModel::UnitsAuto);
return;
} else if(result == actionKB) {
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
d->mModel.setIoUnits(ProcessModel::UnitsKB);
else
d->mModel.setUnits(ProcessModel::UnitsKB);
return;
} else if(result == actionMB) {
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
d->mModel.setIoUnits(ProcessModel::UnitsMB);
else
d->mModel.setUnits(ProcessModel::UnitsMB);
return;
} else if(result == actionGB) {
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
d->mModel.setIoUnits(ProcessModel::UnitsGB);
else
d->mModel.setUnits(ProcessModel::UnitsGB);
return;
} else if(result == actionPercentage) {
d->mModel.setUnits(ProcessModel::UnitsPercentage);
return;
} else if(result == actionShowCmdlineOptions) {
d->mModel.setShowCommandLineOptions(actionShowCmdlineOptions->isChecked());
return;
} else if(result == actionNormalizeCPUUsage) {
d->mModel.setNormalizedCPUUsage(actionNormalizeCPUUsage->isChecked());
return;
} else if(result == actionShowTooltips) {
d->mModel.setShowingTooltips(actionShowTooltips->isChecked());
return;
} else if(result == actionIoCharacters) {
d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
return;
} else if(result == actionIoSyscalls) {
d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
return;
} else if(result == actionIoActualCharacters) {
d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
return;
} else if(result == actionIoShowRate) {
showIoRate = actionIoShowRate->isChecked();
switch(d->mModel.ioInformation()) {
case ProcessModel::Bytes:
case ProcessModel::BytesRate:
d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
break;
case ProcessModel::Syscalls:
case ProcessModel::SyscallsRate:
d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
break;
case ProcessModel::ActualBytes:
case ProcessModel::ActualBytesRate:
d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
break;
default:
break;
}
}
int i = result->data().toInt();
//We set data to be negative to hide a column, and positive to show a column
if(i < 0)
d->mUi->treeView->hideColumn(-1-i);
else {
d->mUi->treeView->showColumn(i);
updateList();
d->mUi->treeView->resizeColumnToContents(i);
d->mUi->treeView->resizeColumnToContents(d->mFilterModel.columnCount());
}
menu.deleteLater();
}
void KSysGuardProcessList::expandAllChildren(const QModelIndex &parent)
{
//This is called when the user expands a node. This then expands all of its
//children. This will trigger this function again recursively.
QModelIndex sourceParent = d->mFilterModel.mapToSource(parent);
for(int i = 0; i < d->mModel.rowCount(sourceParent); i++) {
d->mUi->treeView->expand(d->mFilterModel.mapFromSource(d->mModel.index(i,0, sourceParent)));
}
}
void KSysGuardProcessList::rowsInserted(const QModelIndex & parent, int start, int end )
{
if(d->mModel.isSimpleMode() || parent.isValid()) {
emit processListChanged();
return; //No tree or not a root node - no need to expand init
}
disconnect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
//It is a root node that we just inserted - expand it
bool expanded = false;
for(int i = start; i <= end; i++) {
QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
if(!d->mUi->treeView->isExpanded(index)) {
if(!expanded) {
disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
expanded = true;
}
d->mUi->treeView->expand(index);
d->mNeedToExpandInit = true;
}
}
if(expanded)
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
emit processListChanged();
}
void KSysGuardProcessList::expandInit()
{
if(d->mModel.isSimpleMode()) return; //No tree - no need to expand init
bool expanded = false;
for(int i = 0; i < d->mFilterModel.rowCount(QModelIndex()); i++) {
QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
if(!d->mUi->treeView->isExpanded(index)) {
if(!expanded) {
disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
expanded = true;
}
d->mUi->treeView->expand(index);
}
}
if(expanded)
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
}
void KSysGuardProcessList::hideEvent ( QHideEvent * event ) //virtual protected from QWidget
{
//Stop updating the process list if we are hidden
if(d->mUpdateTimer)
d->mUpdateTimer->stop();
QWidget::hideEvent(event);
}
void KSysGuardProcessList::showEvent ( QShowEvent * event ) //virtual protected from QWidget
{
//Start updating the process list again if we are shown again
updateList();
QHeaderView *header = d->mUi->treeView->header();
d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
QWidget::showEvent(event);
}
void KSysGuardProcessList::changeEvent( QEvent * event )
{
if (event->type() == QEvent::LanguageChange) {
d->mModel.retranslateUi();
d->mUi->retranslateUi(this);
retranslateUi();
}
QWidget::changeEvent(event);
}
void KSysGuardProcessList::retranslateUi()
{
d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcesses, KIcon("view-process-all"));
d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcessesInTreeForm, KIcon("view-process-all-tree"));
d->mUi->cmbFilter->setItemIcon(ProcessFilter::SystemProcesses, KIcon("view-process-system"));
d->mUi->cmbFilter->setItemIcon(ProcessFilter::UserProcesses, KIcon("view-process-users"));
d->mUi->cmbFilter->setItemIcon(ProcessFilter::OwnProcesses, KIcon("view-process-own"));
d->mUi->cmbFilter->setItemIcon(ProcessFilter::ProgramsOnly, KIcon("view-process-all"));
}
void KSysGuardProcessList::updateList()
{
if(isVisible()) {
KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::StandardInformation;
if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoRead) || !d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoWrite))
updateFlags |= KSysGuard::Processes::IOStatistics;
if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingXMemory))
updateFlags |= KSysGuard::Processes::XMemory;
d->mModel.update(d->mUpdateIntervalMSecs, updateFlags);
if(d->mUpdateTimer)
d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
emit updated();
if (QToolTip::isVisible() && qApp->topLevelAt(QCursor::pos()) == window()) {
QWidget *w = d->mUi->treeView->viewport();
if(w->geometry().contains(d->mUi->treeView->mapFromGlobal( QCursor::pos() ))) {
QHelpEvent event(QEvent::ToolTip, w->mapFromGlobal( QCursor::pos() ), QCursor::pos());
qApp->notify(w, &event);
}
}
if(--d->mResortCountDown <= 0) {
d->mResortCountDown = 2; //resort every second time
//resort now
QHeaderView *header= d->mUi->treeView->header();
d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
}
if( d->mNeedToExpandInit ) {
expandInit();
d->mNeedToExpandInit = false;
}
}
}
int KSysGuardProcessList::updateIntervalMSecs() const
{
return d->mUpdateIntervalMSecs;
}
void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs)
{
if(intervalMSecs == d->mUpdateIntervalMSecs)
return;
d->mUpdateIntervalMSecs = intervalMSecs;
if(intervalMSecs <= 0) { //no point keep the timer around if we aren't updating automatically
delete d->mUpdateTimer;
d->mUpdateTimer = NULL;
return;
}
if(!d->mUpdateTimer) {
//intervalMSecs is a valid time, so set up a timer
d->mUpdateTimer = new QTimer(this);
d->mUpdateTimer->setSingleShot(true);
connect(d->mUpdateTimer, SIGNAL(timeout()), SLOT(updateList()));
if(isVisible())
d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
} else
d->mUpdateTimer->setInterval(d->mUpdateIntervalMSecs);
}
bool KSysGuardProcessList::reniceProcesses(const QList<long long> &pids, int niceValue)
{
QList< long long> unreniced_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->setNiceness(pids.at(i), niceValue);
if(!success) {
unreniced_pids << pids.at(i);
}
}
if(unreniced_pids.isEmpty()) return true; //All processes were reniced successfully
if(!d->mModel.isLocalhost()) return false; //We can't use kauth to renice non-localhost processes
QVariantMap actionarguments;
int processCount = unreniced_pids.count();
for(int i = 0; i < processCount; i++) {
actionarguments.insert(QString("pid%1").arg(i), unreniced_pids[i]);
}
actionarguments.insert("pidcount", processCount);
actionarguments.insert("nicevalue", niceValue);
int reply = KAuthorization::execute(
"org.kde.ksysguard.processlisthelper", "renice", actionarguments
);
if (reply == KAuthorization::NoError) {
updateList();
} else if (reply == KAuthorization::AuthorizationError) {
KMessageBox::sorry(this, i18n("You do not have the permission to renice the process."));
return false;
} else {
KMessageBox::sorry(this, i18n("There was a problem trying to renice the process"));
return false;
}
return true;
}
QList<KSysGuard::Process *> KSysGuardProcessList::selectedProcesses() const
{
QList<KSysGuard::Process *> processes;
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
for(int i = 0; i < selectedIndexes.size(); ++i) {
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (d->mFilterModel.mapToSource(selectedIndexes.at(i)).internalPointer());
processes << process;
}
return processes;
}
void KSysGuardProcessList::reniceSelectedProcesses()
{
QList<long long> pids;
QPointer<ReniceDlg> reniceDlg;
{
QList<KSysGuard::Process *> processes = selectedProcesses();
QStringList selectedAsStrings;
if (processes.isEmpty()) {
KMessageBox::sorry(this, i18n("You must select a process first."));
return;
}
int sched = -2;
foreach(KSysGuard::Process *process, processes) {
pids << process->pid;
selectedAsStrings << d->mModel.getStringForProcess(process);
if(sched == -2) {
sched = (int)process->scheduler;
} else if(sched != -1 && sched != (int)process->scheduler) {
// If two processes have different schedulers, disable the cpu scheduler stuff
sched = -1;
}
}
int firstPriority = processes.first()->niceLevel;
reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched);
if(reniceDlg->exec() == QDialog::Rejected) {
delete reniceDlg;
return;
}
}
//Because we've done into ReniceDlg, which calls processEvents etc, our processes list is no
//longer valid
QList<long long> renicePids;
QList<long long> changeCPUSchedulerPids;
foreach (long long pid, pids) {
KSysGuard::Process *process = d->mModel.getProcess(pid);
if (!process)
continue;
switch(reniceDlg->newCPUSched) {
case -2:
case -1: //Invalid, not changed etc.
break; //So do nothing
case KSysGuard::Process::Other:
case KSysGuard::Process::Fifo:
if(reniceDlg->newCPUSched != (int)process->scheduler) {
changeCPUSchedulerPids << pid;
renicePids << pid;
} else if(reniceDlg->newCPUPriority != process->niceLevel)
renicePids << pid;
break;
case KSysGuard::Process::RoundRobin:
case KSysGuard::Process::Batch:
if(reniceDlg->newCPUSched != (int)process->scheduler || reniceDlg->newCPUPriority != process->niceLevel) {
changeCPUSchedulerPids << pid;
}
break;
}
}
if(!changeCPUSchedulerPids.isEmpty()) {
Q_ASSERT(reniceDlg->newCPUSched >= 0);
if(!changeCpuScheduler(changeCPUSchedulerPids, (KSysGuard::Process::Scheduler) reniceDlg->newCPUSched, reniceDlg->newCPUPriority)) {
delete reniceDlg;
return;
}
}
if(!renicePids.isEmpty()) {
Q_ASSERT(reniceDlg->newCPUPriority <= 20 && reniceDlg->newCPUPriority >= -20);
if(!reniceProcesses(renicePids, reniceDlg->newCPUPriority)) {
delete reniceDlg;
return;
}
}
delete reniceDlg;
updateList();
}
bool KSysGuardProcessList::changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority)
{
if(newCpuSched == KSysGuard::Process::Other || newCpuSched == KSysGuard::Process::Batch) newCpuSchedPriority = 0;
QList< long long> unchanged_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->setScheduler(pids.at(i), newCpuSched, newCpuSchedPriority);
if(!success) {
unchanged_pids << pids.at(i);
}
}
if(unchanged_pids.isEmpty()) return true;
if(!d->mModel.isLocalhost()) return false; //We can't use KAuth to affect non-localhost processes
QVariantMap actionarguments;
int processCount = unchanged_pids.count();
for(int i = 0; i < processCount; i++) {
actionarguments.insert(QString("pid%1").arg(i), unchanged_pids[i]);
}
actionarguments.insert("pidcount", processCount);
actionarguments.insert("cpuScheduler", (int)newCpuSched);
actionarguments.insert("cpuSchedulerPriority", newCpuSchedPriority);
int reply = KAuthorization::execute(
"org.kde.ksysguard.processlisthelper", "changecpuscheduler", actionarguments
);
if (reply == KAuthorization::NoError) {
updateList();
} else if (reply == KAuthorization::AuthorizationError) {
KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process."));
return false;
} else {
KMessageBox::sorry(this, i18n("There was a problem trying to change the CPU Scheduler for the process"));
return false;
}
return true;
}
bool KSysGuardProcessList::killProcesses(const QList< long long> &pids, int sig)
{
QList< long long> unkilled_pids;
for (int i = 0; i < pids.size(); ++i) {
bool success = d->mModel.processController()->sendSignal(pids.at(i), sig);
// If we failed due to a reason other than insufficient permissions, elevating to root can't
// help us
if(!success && (d->mModel.processController()->lastError() == KSysGuard::Processes::InsufficientPermissions || d->mModel.processController()->lastError() == KSysGuard::Processes::Unknown)) {
unkilled_pids << pids.at(i);
}
}
if(unkilled_pids.isEmpty()) return true;
if(!d->mModel.isLocalhost()) return false; //We can't elevate privileges to kill non-localhost processes
KMessageBox::sorry(this, i18n("You do not have the permission to send signal to the process."));
return false;
}
void KSysGuardProcessList::killSelectedProcesses()
{
sendSignalToSelectedProcesses(SIGTERM, true);
}
void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm)
{
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
QStringList selectedAsStrings;
QList< long long> selectedPids;
QList<KSysGuard::Process *> processes = selectedProcesses();
foreach(KSysGuard::Process *process, processes) {
selectedPids << process->pid;
if (!confirm)
continue;
QString name = d->mModel.getStringForProcess(process);
if(name.size() > 100)
name = name.left(95) + QString::fromUtf8("");
selectedAsStrings << name;
}
if (selectedPids.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();
QString msg;
QString title;
QString dontAskAgainKey;
QString closeButton;
if (sig == SIGTERM) {
msg = i18np("Are you sure you want to end this process? Any unsaved work may be lost.",
"Are you sure you want to end these %1 processes? Any unsaved work may be lost",
count);
title = i18ncp("Dialog title", "End Process", "End %1 Processes", count);
dontAskAgainKey = "endconfirmation";
closeButton = i18n("End");
} else if (sig == SIGKILL) {
msg = i18np("<qt>Are you sure you want to <b>immediately and forcibly kill</b> this process? Any unsaved work may be lost.",
"<qt>Are you sure you want to <b>immediately and forcibly kill</b> these %1 processes? Any unsaved work may be lost",
count);
title = i18ncp("Dialog title", "Forcibly Kill Process", "Forcibly Kill %1 Processes", count);
dontAskAgainKey = "killconfirmation";
closeButton = i18n("Kill");
}
int res = KMessageBox::warningContinueCancelList(this, msg, selectedAsStrings,
title,
KGuiItem(closeButton, "process-stop"),
KStandardGuiItem::cancel(),
dontAskAgainKey);
if (res != KMessageBox::Continue)
return;
}
//We have shown a GUI dialog box, which processes events etc.
//So processes is NO LONGER VALID
if (!killProcesses(selectedPids, sig))
return;
if (sig == SIGTERM || sig == SIGKILL) {
foreach (long long pid, selectedPids) {
KSysGuard::Process *process = d->mModel.getProcess(pid);
if (process)
process->timeKillWasSent.start();
d->mUi->treeView->selectionModel()->clearSelection();
}
}
updateList();
}
bool KSysGuardProcessList::showTotals() const {
return d->mModel.showTotals();
}
void KSysGuardProcessList::setShowTotals(bool showTotals) //slot
{
d->mModel.setShowTotals(showTotals);
}
ProcessModel::Units KSysGuardProcessList::units() const {
return d->mModel.units();
}
void KSysGuardProcessList::setUnits(ProcessModel::Units unit) {
d->mModel.setUnits(unit);
}
void KSysGuardProcessList::saveSettings(KConfigGroup &cg) {
/* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */
cg.writeEntry("units", (int)(units()));
cg.writeEntry("ioUnits", (int)(d->mModel.ioUnits()));
cg.writeEntry("ioInformation", (int)(d->mModel.ioInformation()));
cg.writeEntry("showCommandLineOptions", d->mModel.isShowCommandLineOptions());
cg.writeEntry("normalizeCPUUsage", d->mModel.isNormalizedCPUUsage());
cg.writeEntry("showTooltips", d->mModel.isShowingTooltips());
cg.writeEntry("showTotals", showTotals());
cg.writeEntry("filterState", (int)(state()));
cg.writeEntry("updateIntervalMSecs", updateIntervalMSecs());
cg.writeEntry("headerState", d->mUi->treeView->header()->saveState());
//If we change, say, the header between versions of ksysguard, then the old headerState settings will not be valid.
//The version property lets us keep track of which version we are
cg.writeEntry("version", PROCESSHEADERVERSION);
}
void KSysGuardProcessList::loadSettings(const KConfigGroup &cg) {
/* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */
setUnits((ProcessModel::Units) cg.readEntry("units", (int)ProcessModel::UnitsKB));
d->mModel.setIoUnits((ProcessModel::Units) cg.readEntry("ioUnits", (int)ProcessModel::UnitsKB));
d->mModel.setIoInformation((ProcessModel::IoInformation) cg.readEntry("ioInformation", (int)ProcessModel::ActualBytesRate));
d->mModel.setShowCommandLineOptions(cg.readEntry("showCommandLineOptions", false));
d->mModel.setNormalizedCPUUsage(cg.readEntry("normalizeCPUUsage", true));
d->mModel.setShowingTooltips(cg.readEntry("showTooltips", true));
setShowTotals(cg.readEntry("showTotals", true));
setStateInt(cg.readEntry("filterState", (int)ProcessFilter::AllProcesses));
setUpdateIntervalMSecs(cg.readEntry("updateIntervalMSecs", 2000));
int version = cg.readEntry("version", 0);
if(version == PROCESSHEADERVERSION) { //If the header has changed, the old settings are no longer valid. Only restore if version is the same
restoreHeaderState(cg.readEntry("headerState", QByteArray()));
}
}
void KSysGuardProcessList::restoreHeaderState(const QByteArray & state) {
d->mUi->treeView->header()->restoreState(state);
}
bool KSysGuardProcessList::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(obj == d->mUi->treeView) {
if(keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
d->mUi->treeView->selectionModel()->select(d->mUi->treeView->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
showProcessContextMenu(d->mUi->treeView->currentIndex());
return true;
} else if(keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) ||
keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage)) {
if (d->mUi->treeView->selectionModel()->selectedRows().size() == 1 &&
d->mUi->treeView->selectionModel()->selectedRows().first().row() == 0) {
// when first row is selected, pressing up or pgup moves to the textfield
d->mUi->txtFilter->setFocus();
return true;
}
} else if (!keyEvent->text().isEmpty() && keyEvent->key() != Qt::Key_Tab) {
// move to textfield and forward keyevent if user starts typing from treeview
d->mUi->txtFilter->setFocus();
QApplication::sendEvent(d->mUi->txtFilter, event);
return true;
}
} else {
Q_ASSERT(obj == d->mUi->txtFilter);
if (d->mUi->treeView->model()->rowCount() == 0) {
// treeview is empty, do nothing
return false;
}
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
// pressing enter will send enter to the first row in the list
// the focusin eventfilter will make sure the first row is selected if there was
// no previous selection
d->mUi->treeView->setFocus();
QApplication::sendEvent(d->mUi->treeView, event);
return true;
} else if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) ||
keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)) {
// attempting to move down by down-key or pgdown, or pressing enter will move focus
// to the treeview
d->mUi->treeView->setFocus();
return true;
}
}
} else if (event->type() == QEvent::FocusIn) {
if (obj == d->mUi->treeView && !d->mUi->treeView->selectionModel()->hasSelection()) {
// treeview is focused, but there is no current selection. select the first row
d->mUi->treeView->setCurrentIndex(d->mUi->treeView->model()->index(0, 0));
} else if (obj == d->mUi->txtFilter) {
// text field is focused, so clear treeview selection to communicate that you will not
// interact with any elements in the view until the view is focused explicitly
d->mUi->treeView->setCurrentIndex(QModelIndex());
}
}
return false;
}
ProcessModel *KSysGuardProcessList::processModel() {
return &d->mModel;
}
void KSysGuardProcessList::setKillButtonVisible(bool visible)
{
d->mUi->btnKillProcess->setVisible(visible);
}
bool KSysGuardProcessList::isKillButtonVisible() const
{
return d->mUi->btnKillProcess->isVisible();
}