mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 19:02:51 +00:00
1468 lines
63 KiB
C++
1468 lines
63 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-ksysguard.h"
|
|
|
|
#include <QApplication>
|
|
#include <QTimer>
|
|
#include <QList>
|
|
#include <QtGui/qevent.h>
|
|
#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 <kauthaction.h>
|
|
#include <kauthactionreply.h>
|
|
#include <kauthhelpersupport.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::ManualWrap);
|
|
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;
|
|
|
|
/** Helper function to setup 'action' with the given pids */
|
|
void setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) 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 KSysGuardProcessListPrivate::setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) const
|
|
{
|
|
action->setHelperID("org.kde.ksysguard.processlisthelper");
|
|
|
|
int processCount = pids.count();
|
|
for(int i = 0; i < processCount; i++) {
|
|
action->addArgument(QString("pid%1").arg(i), pids[i]);
|
|
}
|
|
action->addArgument("pidcount", processCount);
|
|
}
|
|
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
|
|
|
|
|
|
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.renice");
|
|
action->setParentWidget(window());
|
|
d->setupKAuthAction( action, unreniced_pids);
|
|
action->addArgument("nicevalue", niceValue);
|
|
KAuth::ActionReply reply = action->execute();
|
|
|
|
if (reply == KAuth::ActionReply::SuccessReply) {
|
|
updateList();
|
|
delete action;
|
|
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
|
|
KMessageBox::sorry(this, i18n("You do not have the permission to renice the process and there "
|
|
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
|
|
delete action;
|
|
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;
|
|
int iosched = -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) sched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff
|
|
if(iosched == -2) iosched = (int)process->ioPriorityClass;
|
|
else if(iosched != -1 && iosched != (int)process->ioPriorityClass) iosched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff
|
|
|
|
}
|
|
int firstPriority = processes.first()->niceLevel;
|
|
int firstIOPriority = processes.first()->ioniceLevel;
|
|
|
|
bool supportsIoNice = d->mModel.processController()->supportsIoNiceness();
|
|
if(!supportsIoNice) { iosched = -2; firstIOPriority = -2; }
|
|
reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched, firstIOPriority, iosched);
|
|
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;
|
|
QList<long long> changeIOSchedulerPids;
|
|
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;
|
|
}
|
|
switch(reniceDlg->newIOSched) {
|
|
case -2:
|
|
case -1: //Invalid, not changed etc.
|
|
break; //So do nothing
|
|
case KSysGuard::Process::None:
|
|
if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
|
|
// Unfortunately linux doesn't actually let us set the ioniceness back to none after being set to something else
|
|
if(process->ioPriorityClass != KSysGuard::Process::BestEffort || reniceDlg->newIOPriority != process->ioniceLevel)
|
|
changeIOSchedulerPids << pid;
|
|
}
|
|
break;
|
|
case KSysGuard::Process::Idle:
|
|
if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
|
|
changeIOSchedulerPids << pid;
|
|
}
|
|
break;
|
|
case KSysGuard::Process::BestEffort:
|
|
if(process->ioPriorityClass == KSysGuard::Process::None && reniceDlg->newIOPriority == (process->niceLevel + 20)/5)
|
|
break; //Don't set to BestEffort if it's on None and the nicelevel wouldn't change
|
|
case KSysGuard::Process::RealTime:
|
|
if(reniceDlg->newIOSched != (int)process->ioPriorityClass || reniceDlg->newIOPriority != process->ioniceLevel) {
|
|
changeIOSchedulerPids << 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;
|
|
}
|
|
}
|
|
if(!changeIOSchedulerPids.isEmpty()) {
|
|
if(!changeIoScheduler(changeIOSchedulerPids, (KSysGuard::Process::IoPriorityClass) reniceDlg->newIOSched, reniceDlg->newIOPriority)) {
|
|
delete reniceDlg;
|
|
return;
|
|
}
|
|
}
|
|
delete reniceDlg;
|
|
updateList();
|
|
}
|
|
|
|
bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority)
|
|
{
|
|
if(newIoSched == KSysGuard::Process::None) newIoSched = KSysGuard::Process::BestEffort;
|
|
if(newIoSched == KSysGuard::Process::Idle) newIoSchedPriority = 0;
|
|
QList< long long> unchanged_pids;
|
|
for (int i = 0; i < pids.size(); ++i) {
|
|
bool success = d->mModel.processController()->setIoNiceness(pids.at(i), newIoSched, newIoSchedPriority);
|
|
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
|
|
|
|
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changeioscheduler");
|
|
action->setParentWidget(window());
|
|
|
|
d->setupKAuthAction( action, unchanged_pids);
|
|
action->addArgument("ioScheduler", (int)newIoSched);
|
|
action->addArgument("ioSchedulerPriority", newIoSchedPriority);
|
|
|
|
KAuth::ActionReply reply = action->execute();
|
|
|
|
if (reply == KAuth::ActionReply::SuccessReply) {
|
|
updateList();
|
|
delete action;
|
|
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
|
|
KMessageBox::sorry(this, i18n("You do not have the permission to change the I/O priority of the process and there "
|
|
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
|
|
delete action;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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
|
|
|
|
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changecpuscheduler");
|
|
action->setParentWidget(window());
|
|
d->setupKAuthAction( action, unchanged_pids);
|
|
action->addArgument("cpuScheduler", (int)newCpuSched);
|
|
action->addArgument("cpuSchedulerPriority", newCpuSchedPriority);
|
|
KAuth::ActionReply reply = action->execute();
|
|
|
|
if (reply == KAuth::ActionReply::SuccessReply) {
|
|
updateList();
|
|
delete action;
|
|
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
|
|
KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process and there "
|
|
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
|
|
delete action;
|
|
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
|
|
|
|
KAuth::Action action("org.kde.ksysguard.processlisthelper.sendsignal");
|
|
action.setParentWidget(window());
|
|
d->setupKAuthAction( &action, unkilled_pids);
|
|
action.addArgument("signal", sig);
|
|
KAuth::ActionReply reply = action.execute();
|
|
|
|
if (reply == KAuth::ActionReply::SuccessReply) {
|
|
updateList();
|
|
} else if (reply.type() == KAuth::ActionReply::HelperError) {
|
|
if (reply.errorCode() > 0)
|
|
KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
|
|
"was a problem trying to run as root. %1", reply.errorDescription()));
|
|
return false;
|
|
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
|
|
KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
|
|
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|