~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to libs/ksysguard/processui/ksysguardprocesslist.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    KSysGuard, the KDE System Guard
 
3
 
 
4
        Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>
 
5
        Copyright (c) 2006 - 2007 John Tapsell <john.tapsell@kde.org>
 
6
 
 
7
    This library is free software; you can redistribute it and/or
 
8
    modify it under the terms of the GNU Library General Public
 
9
    License as published by the Free Software Foundation; either
 
10
    version 2 of the License, or (at your option) any later version.
 
11
 
 
12
    This library is distributed in the hope that it will be useful,
 
13
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
    Library General Public License for more details.
 
16
 
 
17
    You should have received a copy of the GNU Library General Public License
 
18
    along with this library; see the file COPYING.LIB.  If not, write to
 
19
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
20
    Boston, MA 02110-1301, USA.
 
21
 
 
22
*/
 
23
 
 
24
#include "ksysguardprocesslist.moc"
 
25
#include "ksysguardprocesslist.h"
 
26
 
 
27
#include "../config-ksysguard.h"
 
28
 
 
29
#include <QApplication>
 
30
#include <QTimer>
 
31
#include <QList>
 
32
#include <QShowEvent>
 
33
#include <QHideEvent>
 
34
#include <QHeaderView>
 
35
#include <QAction>
 
36
#include <QMenu>
 
37
#include <QSet>
 
38
#include <QComboBox>
 
39
#include <QStyle>
 
40
#include <QStyledItemDelegate>
 
41
#include <QPainter>
 
42
#include <QLineEdit>
 
43
#include <QSignalMapper>
 
44
#include <QToolTip>
 
45
#include <QAbstractItemModel>
 
46
#include <QtDBus>
 
47
 
 
48
#include <signal.h> //For SIGTERM
 
49
 
 
50
#include <kauth.h>
 
51
#include <kaction.h>
 
52
#include <klocale.h>
 
53
#include <kmessagebox.h>
 
54
#include <kdialog.h>
 
55
#include <kicon.h>
 
56
#include <kdebug.h>
 
57
#include <KWindowSystem>
 
58
 
 
59
#include "ReniceDlg.h"
 
60
#include "ui_ProcessWidgetUI.h"
 
61
#include "scripting.h"
 
62
 
 
63
#include <sys/types.h>
 
64
#include <unistd.h>
 
65
 
 
66
//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
 
67
//#define DO_MODELCHECK
 
68
#ifdef DO_MODELCHECK
 
69
#include "modeltest.h"
 
70
#endif
 
71
class ProgressBarItemDelegate : public QStyledItemDelegate
 
72
{
 
73
    public:
 
74
        ProgressBarItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
 
75
        }
 
76
 
 
77
        virtual void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
 
78
        {
 
79
            QStyleOptionViewItemV4 option = opt;
 
80
            initStyleOption(&option,index);
 
81
 
 
82
            float percentage = index.data(ProcessModel::PercentageRole).toFloat();
 
83
            if (percentage >= 0)
 
84
                drawPercentageDisplay(painter,option, percentage);
 
85
            else
 
86
                QStyledItemDelegate::paint(painter, option, index);
 
87
        }
 
88
 
 
89
    private:
 
90
        inline void drawPercentageDisplay(QPainter *painter, QStyleOptionViewItemV4 &option, float percentage) const
 
91
        {
 
92
            QStyle *style = option.widget ? option.widget->style() : QApplication::style();
 
93
 
 
94
            // draw the background
 
95
            style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
 
96
 
 
97
            QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
 
98
                ? QPalette::Normal : QPalette::Disabled;
 
99
            if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
 
100
                cg = QPalette::Inactive;
 
101
 
 
102
            //Now draw our percentage thingy
 
103
            const QRect &rect = option.rect;
 
104
            int size = qMin(percentage,1.0f) * rect.width();
 
105
            if(size > 2 ) { //make sure the line will have a width of more than 1 pixel
 
106
                painter->setPen(Qt::NoPen);
 
107
                QColor color = option.palette.color(cg, QPalette::Link);
 
108
                color.setAlpha(50);
 
109
 
 
110
                painter->fillRect( rect.x(), rect.y(), size, rect.height(), color);
 
111
            }
 
112
 
 
113
            // draw the text
 
114
            if (!option.text.isEmpty()) {
 
115
                QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
 
116
 
 
117
 
 
118
                if (option.state & QStyle::State_Selected) {
 
119
                    painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
 
120
                } else {
 
121
                    painter->setPen(option.palette.color(cg, QPalette::Text));
 
122
                }
 
123
 
 
124
                painter->setFont(option.font);
 
125
                QTextOption textOption;
 
126
                textOption.setWrapMode(QTextOption::ManualWrap);
 
127
                textOption.setTextDirection(option.direction);
 
128
                textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
 
129
 
 
130
                painter->drawText(textRect, option.text, textOption);
 
131
            }
 
132
 
 
133
            // draw the focus rect
 
134
             if (option.state & QStyle::State_HasFocus) {
 
135
                QStyleOptionFocusRect o;
 
136
                o.QStyleOption::operator=(option);
 
137
                o.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, option.widget);
 
138
                o.state |= QStyle::State_KeyboardFocusChange;
 
139
                o.state |= QStyle::State_Item;
 
140
                QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
 
141
                              ? QPalette::Normal : QPalette::Disabled;
 
142
                o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected)
 
143
                                             ? QPalette::Highlight : QPalette::Window);
 
144
                style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget);
 
145
            }
 
146
        }
 
147
};
 
148
 
 
149
struct KSysGuardProcessListPrivate {
 
150
 
 
151
    KSysGuardProcessListPrivate(KSysGuardProcessList* q, const QString &hostName)
 
152
        : mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(NULL), mUpdateTimer(NULL)
 
153
    {
 
154
        mScripting = NULL;
 
155
        mNeedToExpandInit = false;
 
156
        mNumItemsSelected = -1;
 
157
        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
 
158
        renice = new KAction(i18np("Set Priority...", "Set Priority...", 1), q);
 
159
        renice->setShortcut(Qt::Key_F8);
 
160
        selectParent = new KAction(i18n("Jump to Parent Process"), q);
 
161
 
 
162
        selectTracer = new KAction(i18n("Jump to Process Debugging This One"), q);
 
163
        window = new KAction(i18n("Show Application Window"), q);
 
164
        resume = new KAction(i18n("Resume Stopped Process"), q);
 
165
        terminate = new KAction(i18np("End Process", "End Processes", 1), q);
 
166
        terminate->setIcon(KIcon("process-stop"));
 
167
        terminate->setShortcut(Qt::Key_Delete);
 
168
        kill = new KAction(i18np("Forcibly Kill Process", "Forcibly Kill Processes", 1), q);
 
169
        kill->setIcon(KIcon("process-stop"));
 
170
        kill->setShortcut(Qt::SHIFT + Qt::Key_Delete);
 
171
 
 
172
        sigStop = new KAction(i18n("Suspend (STOP)"), q);
 
173
        sigCont = new KAction(i18n("Continue (CONT)"), q);
 
174
        sigHup = new KAction(i18n("Hangup (HUP)"), q);
 
175
        sigInt = new KAction(i18n("Interrupt (INT)"), q);
 
176
        sigTerm = new KAction(i18n("Terminate (TERM)"), q);
 
177
        sigKill = new KAction(i18n("Kill (KILL)"), q);
 
178
        sigUsr1 = new KAction(i18n("User 1 (USR1)"), q);
 
179
        sigUsr2 = new KAction(i18n("User 2 (USR2)"), q);
 
180
 
 
181
        //Set up '/' as a shortcut to jump to the quick search text widget
 
182
        jumpToSearchFilter = new KAction(i18n("Focus on Quick Search"), q);
 
183
        jumpToSearchFilter->setShortcuts(QList<QKeySequence>() << QKeySequence::Find << '/');
 
184
    }
 
185
 
 
186
    ~KSysGuardProcessListPrivate() { delete mUi; mUi = NULL; }
 
187
 
 
188
    /** The number rows and their children for the given parent in the mFilterModel model */
 
189
    int totalRowCount(const QModelIndex &parent) const;
 
190
 
 
191
    /** Helper function to setup 'action' with the given pids */
 
192
    void setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) const;
 
193
 
 
194
    /** fire a timer event if we are set to use our internal timer*/
 
195
    void fireTimerEvent();
 
196
 
 
197
    /** The process model.  This contains all the data on all the processes running on the system */
 
198
    ProcessModel mModel;
 
199
 
 
200
    /** The process filter.  The mModel is connected to this, and this filter model connects to the view.  This lets us
 
201
     *  sort the view and filter (by using the combo box or the search line)
 
202
     */
 
203
    ProcessFilter mFilterModel;
 
204
 
 
205
    /** The graphical user interface for this process list widget, auto-generated by Qt Designer */
 
206
    Ui::ProcessWidget *mUi;
 
207
 
 
208
    /** The context menu when you right click on a process */
 
209
    QMenu *mProcessContextMenu;
 
210
 
 
211
    /** A timer to call updateList() every mUpdateIntervalMSecs.
 
212
     *  NULL is mUpdateIntervalMSecs is <= 0. */
 
213
    QTimer *mUpdateTimer;
 
214
 
 
215
    /** The time to wait, in milliseconds, between updating the process list */
 
216
    int mUpdateIntervalMSecs;
 
217
 
 
218
    /** Number of items that are selected */
 
219
    int mNumItemsSelected;
 
220
 
 
221
    /** Class to deal with the scripting. NULL if scripting is disabled */
 
222
    Scripting *mScripting;
 
223
 
 
224
    /** A counter to mark when to resort, so that we do not resort on every update */
 
225
    int mResortCountDown;
 
226
 
 
227
    bool mNeedToExpandInit;
 
228
 
 
229
    KAction *renice;
 
230
    KAction *terminate;
 
231
    KAction *kill;
 
232
    KAction *selectParent;
 
233
    KAction *selectTracer;
 
234
    KAction *jumpToSearchFilter;
 
235
    KAction *window;
 
236
    KAction *resume;
 
237
    KAction *sigStop;
 
238
    KAction *sigCont;
 
239
    KAction *sigHup;
 
240
    KAction *sigInt;
 
241
    KAction *sigTerm;
 
242
    KAction *sigKill;
 
243
    KAction *sigUsr1;
 
244
    KAction *sigUsr2;
 
245
};
 
246
 
 
247
KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostName)
 
248
    : QWidget(parent), d(new KSysGuardProcessListPrivate(this, hostName))
 
249
{
 
250
    qRegisterMetaType<QList<long long> >();
 
251
    qDBusRegisterMetaType<QList<long long> >();
 
252
 
 
253
    d->mUpdateIntervalMSecs = 0; //Set process to not update manually by default
 
254
    d->mUi->setupUi(this);
 
255
    d->mFilterModel.setSourceModel(&d->mModel);
 
256
    d->mUi->treeView->setModel(&d->mFilterModel);
 
257
#ifdef DO_MODELCHECK
 
258
    new ModelTest(&d->mModel, this);
 
259
#endif
 
260
    d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView));
 
261
 
 
262
    d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
 
263
    connect(d->mUi->treeView->header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showColumnContextMenu(QPoint)));
 
264
 
 
265
    d->mProcessContextMenu = new QMenu(d->mUi->treeView);
 
266
    d->mUi->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
 
267
    connect(d->mUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showProcessContextMenu(QPoint)));
 
268
 
 
269
    d->mUi->treeView->header()->setClickable(true);
 
270
    d->mUi->treeView->header()->setSortIndicatorShown(true);
 
271
    d->mUi->treeView->header()->setCascadingSectionResizes(false);
 
272
    connect(d->mUi->btnKillProcess, SIGNAL(clicked()), this, SLOT(killSelectedProcesses()));
 
273
    connect(d->mUi->txtFilter, SIGNAL(textChanged(QString)), this, SLOT(filterTextChanged(QString)));
 
274
    connect(d->mUi->cmbFilter, SIGNAL(currentIndexChanged(int)), this, SLOT(setStateInt(int)));
 
275
    connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
 
276
    connect(d->mUi->treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()));
 
277
    connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
 
278
    connect(&d->mFilterModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(processListChanged()));
 
279
    setMinimumSize(sizeHint());
 
280
 
 
281
    d->mFilterModel.setFilterKeyColumn(-1);
 
282
 
 
283
    /*  Hide various columns by default, to reduce information overload */
 
284
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmSize);
 
285
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNiceness);
 
286
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingTty);
 
287
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCommand);
 
288
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPid);
 
289
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCPUTime);
 
290
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead);
 
291
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite);
 
292
    d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory);
 
293
    // NOTE!  After this is all setup, the settings for the header are restored
 
294
    // from the user's last run.  (in restoreHeaderState)
 
295
    // So making changes here only affects the default settings.  To
 
296
    // test changes temporarily, comment out the lines in restoreHeaderState.
 
297
    // When you are happy with the changes and want to commit, increase the
 
298
    // value of PROCESSHEADERVERSION.  This will force the header state
 
299
    // to be reset back to the defaults for all users.
 
300
    d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingCPUUsage, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingCPUUsage));
 
301
    d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingMemory));
 
302
    d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingSharedMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingSharedMemory));
 
303
    d->mUi->treeView->header()->setResizeMode(0, QHeaderView::Interactive);
 
304
    d->mUi->treeView->header()->setStretchLastSection(true);
 
305
 
 
306
    //Process names can have mixed case. Make the filter case insensitive.
 
307
    d->mFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
 
308
    d->mFilterModel.setSortCaseSensitivity(Qt::CaseInsensitive);
 
309
 
 
310
    d->mUi->txtFilter->installEventFilter(this);
 
311
    d->mUi->treeView->installEventFilter(this);
 
312
 
 
313
    d->mUi->treeView->setDragEnabled(true);
 
314
    d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly);
 
315
 
 
316
 
 
317
    //Sort by username by default
 
318
    d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder);
 
319
 
 
320
    // Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked
 
321
    QSignalMapper *signalMapper = new QSignalMapper(this);
 
322
    QList<QAction *> actions;
 
323
    actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter;
 
324
    actions << d->resume << d->sigStop << d->sigCont << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2;
 
325
 
 
326
    foreach(QAction *action, actions) {
 
327
        addAction(action);
 
328
        connect(action, SIGNAL(triggered(bool)), signalMapper, SLOT(map()));
 
329
        signalMapper->setMapping(action, action);
 
330
    }
 
331
    connect(signalMapper, SIGNAL(mapped(QObject*)), SLOT(actionTriggered(QObject*)));
 
332
 
 
333
    retranslateUi();
 
334
 
 
335
    d->mUi->btnKillProcess->setIcon(KIcon("process-stop"));
 
336
    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."));
 
337
}
 
338
 
 
339
KSysGuardProcessList::~KSysGuardProcessList()
 
340
{
 
341
    delete d;
 
342
}
 
343
 
 
344
QTreeView *KSysGuardProcessList::treeView() const {
 
345
    return d->mUi->treeView;
 
346
}
 
347
 
 
348
QLineEdit *KSysGuardProcessList::filterLineEdit() const {
 
349
    return d->mUi->txtFilter;
 
350
}
 
351
 
 
352
ProcessFilter::State KSysGuardProcessList::state() const
 
353
{
 
354
    return d->mFilterModel.filter();
 
355
}
 
356
void KSysGuardProcessList::setStateInt(int state) {
 
357
    setState((ProcessFilter::State) state);
 
358
    d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
 
359
}
 
360
void KSysGuardProcessList::setState(ProcessFilter::State state)
 
361
{  //index is the item the user selected in the combo box
 
362
    d->mFilterModel.setFilter(state);
 
363
    d->mModel.setSimpleMode( (state != ProcessFilter::AllProcessesInTreeForm) );
 
364
    d->mUi->cmbFilter->setCurrentIndex( (int)state);
 
365
    if(isVisible())
 
366
        expandInit();
 
367
}
 
368
void KSysGuardProcessList::filterTextChanged(const QString &newText) {
 
369
    d->mFilterModel.setFilterRegExp(newText.trimmed());
 
370
    if(isVisible())
 
371
        expandInit();
 
372
    d->mUi->btnKillProcess->setEnabled( d->mUi->treeView->selectionModel()->hasSelection() );
 
373
    d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
 
374
}
 
375
 
 
376
int KSysGuardProcessList::visibleProcessesCount() const  {
 
377
    //This assumes that all the visible rows are processes.  This is true currently, but might not be
 
378
    //true if we add support for showing threads etc
 
379
    if(d->mModel.isSimpleMode())
 
380
        return d->mFilterModel.rowCount();
 
381
    return d->totalRowCount(QModelIndex());
 
382
}
 
383
 
 
384
int KSysGuardProcessListPrivate::totalRowCount(const QModelIndex &parent ) const {
 
385
    int numRows = mFilterModel.rowCount(parent);
 
386
    int total = numRows;
 
387
    for (int i = 0; i < numRows; ++i) {
 
388
        QModelIndex index = mFilterModel.index(i, 0,parent);
 
389
        //if it has children add the total
 
390
        if (mFilterModel.hasChildren(index))
 
391
            total += totalRowCount(index);
 
392
    }
 
393
    return total;
 
394
}
 
395
 
 
396
void KSysGuardProcessListPrivate::setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) const
 
397
{
 
398
    action->setHelperID("org.kde.ksysguard.processlisthelper");
 
399
 
 
400
    int processCount = pids.count();
 
401
    for(int i = 0; i < processCount; i++) {
 
402
        action->addArgument(QString("pid%1").arg(i), pids[i]);
 
403
    }
 
404
    action->addArgument("pidcount", processCount);
 
405
}
 
406
void KSysGuardProcessList::selectionChanged()
 
407
{
 
408
    int numSelected =  d->mUi->treeView->selectionModel()->selectedRows().size();
 
409
    if(numSelected == d->mNumItemsSelected)
 
410
        return;
 
411
    d->mNumItemsSelected = numSelected;
 
412
    d->mUi->btnKillProcess->setEnabled( numSelected != 0 );
 
413
 
 
414
    d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected));
 
415
    d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected));
 
416
    d->terminate->setText(i18ncp("Context menu", "End Process", "End Processes", numSelected));
 
417
}
 
418
void KSysGuardProcessList::showProcessContextMenu(const QModelIndex &index) {
 
419
    if(!index.isValid()) return;
 
420
    QRect rect = d->mUi->treeView->visualRect(index);
 
421
    QPoint point(rect.x() + rect.width()/4, rect.y() + rect.height()/2 );
 
422
    showProcessContextMenu(point);
 
423
}
 
424
void KSysGuardProcessList::showProcessContextMenu(const QPoint &point) {
 
425
    d->mProcessContextMenu->clear();
 
426
 
 
427
    QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
428
    int numProcesses = selectedIndexes.size();
 
429
 
 
430
    if(numProcesses == 0) return;  //No processes selected, so no context menu
 
431
 
 
432
    QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
 
433
    KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
 
434
 
 
435
 
 
436
 
 
437
    //If the selected process is a zombie, do not bother offering renice and kill options
 
438
    bool showSignalingEntries = numProcesses != 1 || process->status != KSysGuard::Process::Zombie;
 
439
    if(showSignalingEntries) {
 
440
        d->mProcessContextMenu->addAction(d->renice);
 
441
        QMenu *signalMenu = d->mProcessContextMenu->addMenu(i18n("Send Signal"));
 
442
        signalMenu->addAction(d->sigStop);
 
443
        signalMenu->addAction(d->sigCont);
 
444
        signalMenu->addAction(d->sigHup);
 
445
        signalMenu->addAction(d->sigInt);
 
446
        signalMenu->addAction(d->sigTerm);
 
447
        signalMenu->addAction(d->sigKill);
 
448
        signalMenu->addAction(d->sigUsr1);
 
449
        signalMenu->addAction(d->sigUsr2);
 
450
    }
 
451
 
 
452
    if(numProcesses == 1 && process->parent_pid > 1) {
 
453
        //As a design decision, I do not show the 'Jump to parent process' option when the
 
454
        //parent is just 'init'.
 
455
 
 
456
        KSysGuard::Process *parent_process = d->mModel.getProcess(process->parent_pid);
 
457
        if(parent_process) { //it should not be possible for this process to not exist, but check just incase
 
458
            QString parent_name = parent_process->name;
 
459
            if(parent_name.size() > 20) //Elide the text if it is too long
 
460
                parent_name = parent_process->name.left(15) + QString::fromUtf8("…");
 
461
            d->selectParent->setText(i18n("Jump to Parent Process (%1)", parent_name));
 
462
            d->mProcessContextMenu->addAction(d->selectParent);
 
463
        }
 
464
    }
 
465
 
 
466
    if(numProcesses == 1 && process->tracerpid >= 0) {
 
467
        //If the process is being debugged, offer to select it
 
468
        d->mProcessContextMenu->addAction(d->selectTracer);
 
469
    }
 
470
 
 
471
    if (numProcesses == 1 && !d->mModel.data(realIndex, ProcessModel::WindowIdRole).isNull()) {
 
472
        d->mProcessContextMenu->addAction(d->window);
 
473
    }
 
474
 
 
475
    if(numProcesses == 1 && process->status == KSysGuard::Process::Stopped) {
 
476
        //If the process is stopped, offer to resume it
 
477
        d->mProcessContextMenu->addAction(d->resume);
 
478
    }
 
479
 
 
480
    if(numProcesses == 1 && d->mScripting) {
 
481
        foreach(QAction *action, d->mScripting->actions()) {
 
482
            d->mProcessContextMenu->addAction(action);
 
483
        }
 
484
    }
 
485
    if (showSignalingEntries) {
 
486
        d->mProcessContextMenu->addSeparator();
 
487
        d->mProcessContextMenu->addAction(d->terminate);
 
488
        if (numProcesses == 1 && !process->timeKillWasSent.isNull())
 
489
            d->mProcessContextMenu->addAction(d->kill);
 
490
    }
 
491
 
 
492
    d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point));
 
493
}
 
494
void KSysGuardProcessList::actionTriggered(QObject *object) {
 
495
    if(!isVisible()) //Ignore triggered actions if we are not visible!
 
496
        return;
 
497
    //Reset the text back to normal
 
498
    d->selectParent->setText(i18n("Jump to Parent Process"));
 
499
    QAction *result = qobject_cast<QAction *>(object);
 
500
    if(result == 0) {
 
501
        //Escape was pressed. Do nothing.
 
502
    } else if(result == d->renice) {
 
503
        reniceSelectedProcesses();
 
504
    } else if(result == d->terminate) {
 
505
        sendSignalToSelectedProcesses(SIGTERM, true);
 
506
    } else if(result == d->kill) {
 
507
        sendSignalToSelectedProcesses(SIGKILL, true);
 
508
    } else if(result == d->selectParent) {
 
509
        QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
510
        int numProcesses = selectedIndexes.size();
 
511
        if(numProcesses == 0) return;  //No processes selected
 
512
        QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
 
513
        KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
 
514
        if(process)
 
515
            selectAndJumpToProcess(process->parent_pid);
 
516
    } else if(result == d->selectTracer) {
 
517
        QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
518
        int numProcesses = selectedIndexes.size();
 
519
        if(numProcesses == 0) return;  //No processes selected
 
520
        QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
 
521
        KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
 
522
        if(process)
 
523
            selectAndJumpToProcess(process->tracerpid);
 
524
    } else if(result == d->window) {
 
525
        QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
526
        int numProcesses = selectedIndexes.size();
 
527
        if(numProcesses == 0) return;  //No processes selected
 
528
        foreach( const QModelIndex &index, selectedIndexes) {
 
529
            QModelIndex realIndex = d->mFilterModel.mapToSource(index);
 
530
            QVariant widVar= d->mModel.data(realIndex, ProcessModel::WindowIdRole);
 
531
            if( !widVar.isNull() ) {
 
532
                int wid = widVar.toInt();
 
533
                KWindowSystem::activateWindow(wid);
 
534
            }
 
535
        }
 
536
    } else if(result == d->jumpToSearchFilter) {
 
537
        d->mUi->txtFilter->setFocus();
 
538
    } else {
 
539
        int sig;
 
540
        if(result == d->resume || result == d->sigCont)
 
541
            sig = SIGCONT;  //Despite the function name, this sends a signal, rather than kill it.  Silly unix :)
 
542
        else if(result == d->sigStop)
 
543
            sig = SIGSTOP;
 
544
        else if(result == d->sigHup)
 
545
            sig = SIGHUP;
 
546
        else if(result == d->sigInt)
 
547
            sig = SIGINT;
 
548
        else if(result == d->sigTerm)
 
549
            sig = SIGTERM;
 
550
        else if(result == d->sigKill)
 
551
            sig = SIGKILL;
 
552
        else if(result == d->sigUsr1)
 
553
            sig = SIGUSR1;
 
554
        else if(result == d->sigUsr2)
 
555
            sig = SIGUSR2;
 
556
        else
 
557
            return;
 
558
        sendSignalToSelectedProcesses(sig, false);
 
559
    }
 
560
}
 
561
 
 
562
void KSysGuardProcessList::selectAndJumpToProcess(int pid) {
 
563
    KSysGuard::Process *process = d->mModel.getProcess(pid);
 
564
    if(!process) return;
 
565
    QModelIndex sourceIndex = d->mModel.getQModelIndex(process, 0);
 
566
    QModelIndex filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
 
567
    if(!filterIndex.isValid() && !d->mUi->txtFilter->text().isEmpty()) {
 
568
        //The filter is preventing us from finding the parent.  Clear the filter
 
569
        //(It could also be the combo box - should we deal with that case as well?)
 
570
        d->mUi->txtFilter->clear();
 
571
        filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
 
572
    }
 
573
    d->mUi->treeView->clearSelection();
 
574
    d->mUi->treeView->setCurrentIndex(filterIndex);
 
575
    d->mUi->treeView->scrollTo( filterIndex, QAbstractItemView::PositionAtCenter);
 
576
 
 
577
}
 
578
 
 
579
void KSysGuardProcessList::showColumnContextMenu(const QPoint &point){
 
580
    QMenu menu;
 
581
 
 
582
    QAction *action;
 
583
    int index = d->mUi->treeView->header()->logicalIndexAt(point);
 
584
    if(index >= 0) {
 
585
        //selected a column.  Give the option to hide it
 
586
        action = new QAction(&menu);
 
587
        action->setData(-index-1); //We set data to be negative (and minus 1) to hide a column, and positive to show a column
 
588
        action->setText(i18n("Hide Column '%1'", d->mFilterModel.headerData(index, Qt::Horizontal, Qt::DisplayRole).toString()));
 
589
        menu.addAction(action);
 
590
        if(d->mUi->treeView->header()->sectionsHidden()) {
 
591
            menu.addSeparator();
 
592
        }
 
593
    }
 
594
 
 
595
    if(d->mUi->treeView->header()->sectionsHidden()) {
 
596
        int num_headings = d->mFilterModel.columnCount();
 
597
        for(int i = 0; i < num_headings; ++i) {
 
598
            if(d->mUi->treeView->header()->isSectionHidden(i)) {
 
599
#ifndef HAVE_XRES
 
600
                if(i == ProcessModel::HeadingXMemory)
 
601
                    continue;
 
602
#endif
 
603
                action = new QAction(&menu);
 
604
                action->setText(i18n("Show Column '%1'", d->mFilterModel.headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()));
 
605
                action->setData(i); //We set data to be negative (and minus 1) to hide a column, and positive to show a column
 
606
                menu.addAction(action);
 
607
            }
 
608
        }
 
609
    }
 
610
    QAction *actionAuto = NULL;
 
611
    QAction *actionKB = NULL;
 
612
    QAction *actionMB = NULL;
 
613
    QAction *actionGB = NULL;
 
614
    QAction *actionPercentage = NULL;
 
615
    QAction *actionShowCmdlineOptions = NULL;
 
616
    QAction *actionShowTooltips = NULL;
 
617
    QAction *actionNormalizeCPUUsage = NULL;
 
618
 
 
619
    QAction *actionIoCharacters = NULL;
 
620
    QAction *actionIoSyscalls = NULL;
 
621
    QAction *actionIoActualCharacters = NULL;
 
622
    QAction *actionIoShowRate = NULL;
 
623
    bool showIoRate = false;
 
624
    if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
 
625
        showIoRate = d->mModel.ioInformation() == ProcessModel::BytesRate ||
 
626
                     d->mModel.ioInformation() == ProcessModel::SyscallsRate ||
 
627
                     d->mModel.ioInformation() == ProcessModel::ActualBytesRate;
 
628
 
 
629
    if( index == ProcessModel::HeadingVmSize || index == ProcessModel::HeadingMemory || index == ProcessModel::HeadingXMemory || index == ProcessModel::HeadingSharedMemory || ( (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) && d->mModel.ioInformation() != ProcessModel::Syscalls)) {
 
630
        //If the user right clicks on a column that contains a memory size, show a toggle option for displaying
 
631
        //the memory in different units.  e.g.  "2000 k" or "2 m"
 
632
        menu.addSeparator()->setText(i18n("Display Units"));
 
633
        QActionGroup *unitsGroup = new QActionGroup(&menu);
 
634
        /* Automatic (human readable)*/
 
635
        actionAuto = new QAction(&menu);
 
636
        actionAuto->setText(i18n("Mixed"));
 
637
        actionAuto->setCheckable(true);
 
638
        menu.addAction(actionAuto);
 
639
        unitsGroup->addAction(actionAuto);
 
640
        /* Kilobytes */
 
641
        actionKB = new QAction(&menu);
 
642
        actionKB->setText((showIoRate)?i18n("Kilobytes per second"):i18n("Kilobytes"));
 
643
        actionKB->setCheckable(true);
 
644
        menu.addAction(actionKB);
 
645
        unitsGroup->addAction(actionKB);
 
646
        /* Megabytes */
 
647
        actionMB = new QAction(&menu);
 
648
        actionMB->setText((showIoRate)?i18n("Megabytes per second"):i18n("Megabytes"));
 
649
        actionMB->setCheckable(true);
 
650
        menu.addAction(actionMB);
 
651
        unitsGroup->addAction(actionMB);
 
652
        /* Gigabytes */
 
653
        actionGB = new QAction(&menu);
 
654
        actionGB->setText((showIoRate)?i18n("Gigabytes per second"):i18n("Gigabytes"));
 
655
        actionGB->setCheckable(true);
 
656
        menu.addAction(actionGB);
 
657
        unitsGroup->addAction(actionGB);
 
658
        ProcessModel::Units currentUnit;
 
659
        if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
 
660
            currentUnit = d->mModel.ioUnits();
 
661
        } else {
 
662
            actionPercentage = new QAction(&menu);
 
663
            actionPercentage->setText(i18n("Percentage"));
 
664
            actionPercentage->setCheckable(true);
 
665
            menu.addAction(actionPercentage);
 
666
            unitsGroup->addAction(actionPercentage);
 
667
            currentUnit = d->mModel.units();
 
668
        }
 
669
        switch(currentUnit) {
 
670
            case ProcessModel::UnitsAuto:
 
671
                actionAuto->setChecked(true);
 
672
                break;
 
673
            case ProcessModel::UnitsKB:
 
674
                actionKB->setChecked(true);
 
675
                break;
 
676
            case ProcessModel::UnitsMB:
 
677
                actionMB->setChecked(true);
 
678
                break;
 
679
            case ProcessModel::UnitsGB:
 
680
                actionGB->setChecked(true);
 
681
                break;
 
682
            case ProcessModel::UnitsPercentage:
 
683
                actionPercentage->setChecked(true);
 
684
                break;
 
685
            default:
 
686
                break;
 
687
        }
 
688
        unitsGroup->setExclusive(true);
 
689
    } else if(index == ProcessModel::HeadingName) {
 
690
        menu.addSeparator();
 
691
        actionShowCmdlineOptions = new QAction(&menu);
 
692
        actionShowCmdlineOptions->setText(i18n("Display command line options"));
 
693
        actionShowCmdlineOptions->setCheckable(true);
 
694
        actionShowCmdlineOptions->setChecked(d->mModel.isShowCommandLineOptions());
 
695
        menu.addAction(actionShowCmdlineOptions);
 
696
    } else if(index == ProcessModel::HeadingCPUUsage) {
 
697
        menu.addSeparator();
 
698
        actionNormalizeCPUUsage = new QAction(&menu);
 
699
        actionNormalizeCPUUsage->setText(i18n("Divide CPU usage by number of CPUs"));
 
700
        actionNormalizeCPUUsage->setCheckable(true);
 
701
        actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage());
 
702
        menu.addAction(actionNormalizeCPUUsage);
 
703
    }
 
704
 
 
705
    if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
 
706
        menu.addSeparator()->setText(i18n("Displayed Information"));
 
707
        QActionGroup *ioInformationGroup = new QActionGroup(&menu);
 
708
        actionIoCharacters = new QAction(&menu);
 
709
        actionIoCharacters->setText(i18n("Characters read/written"));
 
710
        actionIoCharacters->setCheckable(true);
 
711
        menu.addAction(actionIoCharacters);
 
712
        ioInformationGroup->addAction(actionIoCharacters);
 
713
        actionIoSyscalls = new QAction(&menu);
 
714
        actionIoSyscalls->setText(i18n("Number of Read/Write operations"));
 
715
        actionIoSyscalls->setCheckable(true);
 
716
        menu.addAction(actionIoSyscalls);
 
717
        ioInformationGroup->addAction(actionIoSyscalls);
 
718
        actionIoActualCharacters = new QAction(&menu);
 
719
        actionIoActualCharacters->setText(i18n("Bytes actually read/written"));
 
720
        actionIoActualCharacters->setCheckable(true);
 
721
        menu.addAction(actionIoActualCharacters);
 
722
        ioInformationGroup->addAction(actionIoActualCharacters);
 
723
 
 
724
        actionIoShowRate = new QAction(&menu);
 
725
        actionIoShowRate->setText(i18n("Show I/O rate"));
 
726
        actionIoShowRate->setCheckable(true);
 
727
        actionIoShowRate->setChecked(showIoRate);
 
728
        menu.addAction(actionIoShowRate);
 
729
 
 
730
        switch(d->mModel.ioInformation()) {
 
731
            case ProcessModel::Bytes:
 
732
            case ProcessModel::BytesRate:
 
733
                actionIoCharacters->setChecked(true);
 
734
                break;
 
735
            case ProcessModel::Syscalls:
 
736
            case ProcessModel::SyscallsRate:
 
737
                actionIoSyscalls->setChecked(true);
 
738
                break;
 
739
            case ProcessModel::ActualBytes:
 
740
            case ProcessModel::ActualBytesRate:
 
741
                actionIoActualCharacters->setChecked(true);
 
742
                break;
 
743
            default:
 
744
                break;
 
745
        }
 
746
    }
 
747
 
 
748
    menu.addSeparator();
 
749
    actionShowTooltips = new QAction(&menu);
 
750
    actionShowTooltips->setCheckable(true);
 
751
    actionShowTooltips->setChecked(d->mModel.isShowingTooltips());
 
752
    actionShowTooltips->setText(i18n("Show Tooltips"));
 
753
    menu.addAction(actionShowTooltips);
 
754
 
 
755
 
 
756
    QAction *result = menu.exec(d->mUi->treeView->header()->mapToGlobal(point));
 
757
    if(!result) return; //Menu cancelled
 
758
    if(result == actionAuto) {
 
759
        if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
 
760
            d->mModel.setIoUnits(ProcessModel::UnitsAuto);
 
761
        else
 
762
            d->mModel.setUnits(ProcessModel::UnitsAuto);
 
763
        return;
 
764
    } else if(result == actionKB) {
 
765
        if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
 
766
            d->mModel.setIoUnits(ProcessModel::UnitsKB);
 
767
        else
 
768
            d->mModel.setUnits(ProcessModel::UnitsKB);
 
769
        return;
 
770
    } else if(result == actionMB) {
 
771
        if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
 
772
            d->mModel.setIoUnits(ProcessModel::UnitsMB);
 
773
        else
 
774
            d->mModel.setUnits(ProcessModel::UnitsMB);
 
775
        return;
 
776
    } else if(result == actionGB) {
 
777
        if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
 
778
            d->mModel.setIoUnits(ProcessModel::UnitsGB);
 
779
        else
 
780
            d->mModel.setUnits(ProcessModel::UnitsGB);
 
781
        return;
 
782
    } else if(result == actionPercentage) {
 
783
        d->mModel.setUnits(ProcessModel::UnitsPercentage);
 
784
        return;
 
785
    } else if(result == actionShowCmdlineOptions) {
 
786
        d->mModel.setShowCommandLineOptions(actionShowCmdlineOptions->isChecked());
 
787
        return;
 
788
    } else if(result == actionNormalizeCPUUsage) {
 
789
        d->mModel.setNormalizedCPUUsage(actionNormalizeCPUUsage->isChecked());
 
790
        return;
 
791
    } else if(result == actionShowTooltips) {
 
792
        d->mModel.setShowingTooltips(actionShowTooltips->isChecked());
 
793
        return;
 
794
    } else if(result == actionIoCharacters) {
 
795
        d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
 
796
        return;
 
797
    } else if(result == actionIoSyscalls) {
 
798
        d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
 
799
        return;
 
800
    } else if(result == actionIoActualCharacters) {
 
801
        d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
 
802
        return;
 
803
    } else if(result == actionIoShowRate) {
 
804
        showIoRate = actionIoShowRate->isChecked();
 
805
        switch(d->mModel.ioInformation()) {
 
806
            case ProcessModel::Bytes:
 
807
            case ProcessModel::BytesRate:
 
808
                d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
 
809
                break;
 
810
            case ProcessModel::Syscalls:
 
811
            case ProcessModel::SyscallsRate:
 
812
                d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
 
813
                break;
 
814
            case ProcessModel::ActualBytes:
 
815
            case ProcessModel::ActualBytesRate:
 
816
                d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
 
817
                break;
 
818
            default:
 
819
                break;
 
820
        }
 
821
    }
 
822
 
 
823
    int i = result->data().toInt();
 
824
    //We set data to be negative to hide a column, and positive to show a column
 
825
    if(i < 0)
 
826
        d->mUi->treeView->hideColumn(-1-i);
 
827
    else {
 
828
        d->mUi->treeView->showColumn(i);
 
829
        updateList();
 
830
        d->mUi->treeView->resizeColumnToContents(i);
 
831
        d->mUi->treeView->resizeColumnToContents(d->mFilterModel.columnCount());
 
832
    }
 
833
    menu.deleteLater();
 
834
}
 
835
 
 
836
void KSysGuardProcessList::expandAllChildren(const QModelIndex &parent)
 
837
{
 
838
    //This is called when the user expands a node.  This then expands all of its
 
839
    //children.  This will trigger this function again recursively.
 
840
    QModelIndex sourceParent = d->mFilterModel.mapToSource(parent);
 
841
    for(int i = 0; i < d->mModel.rowCount(sourceParent); i++) {
 
842
        d->mUi->treeView->expand(d->mFilterModel.mapFromSource(d->mModel.index(i,0, sourceParent)));
 
843
    }
 
844
}
 
845
 
 
846
void KSysGuardProcessList::rowsInserted(const QModelIndex & parent, int start, int end )
 
847
{
 
848
    if(d->mModel.isSimpleMode() || parent.isValid()) {
 
849
        emit processListChanged();
 
850
        return; //No tree or not a root node - no need to expand init
 
851
    }
 
852
    disconnect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
 
853
    //It is a root node that we just inserted - expand it
 
854
    bool expanded = false;
 
855
    for(int i = start; i <= end; i++) {
 
856
        QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
 
857
        if(!d->mUi->treeView->isExpanded(index)) {
 
858
            if(!expanded) {
 
859
                disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
 
860
                expanded = true;
 
861
            }
 
862
            d->mUi->treeView->expand(index);
 
863
            d->mNeedToExpandInit = true;
 
864
        }
 
865
    }
 
866
    if(expanded)
 
867
        connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
 
868
    connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
 
869
    emit processListChanged();
 
870
}
 
871
 
 
872
void KSysGuardProcessList::expandInit()
 
873
{
 
874
    if(d->mModel.isSimpleMode()) return; //No tree - no need to expand init
 
875
 
 
876
    bool expanded = false;
 
877
    for(int i = 0; i < d->mFilterModel.rowCount(QModelIndex()); i++) {
 
878
        QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
 
879
        if(!d->mUi->treeView->isExpanded(index)) {
 
880
            if(!expanded) {
 
881
                disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
 
882
                expanded = true;
 
883
            }
 
884
 
 
885
            d->mUi->treeView->expand(index);
 
886
        }
 
887
    }
 
888
    if(expanded)
 
889
        connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
 
890
}
 
891
 
 
892
void KSysGuardProcessList::hideEvent ( QHideEvent * event )  //virtual protected from QWidget
 
893
{
 
894
    //Stop updating the process list if we are hidden
 
895
    if(d->mUpdateTimer)
 
896
        d->mUpdateTimer->stop();
 
897
    //stop any scripts running, to save on memory
 
898
    if(d->mScripting)
 
899
        d->mScripting->stopAllScripts();
 
900
    QWidget::hideEvent(event);
 
901
}
 
902
 
 
903
void KSysGuardProcessList::showEvent ( QShowEvent * event )  //virtual protected from QWidget
 
904
{
 
905
    //Start updating the process list again if we are shown again
 
906
    updateList();
 
907
    QWidget::showEvent(event);
 
908
}
 
909
 
 
910
void KSysGuardProcessList::changeEvent( QEvent * event )
 
911
{
 
912
    if (event->type() == QEvent::LanguageChange) {
 
913
        d->mModel.retranslateUi();
 
914
        d->mUi->retranslateUi(this);
 
915
        retranslateUi();
 
916
    }
 
917
    QWidget::changeEvent(event);
 
918
}
 
919
void KSysGuardProcessList::retranslateUi()
 
920
{
 
921
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcesses, KIcon("view-process-all"));
 
922
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcessesInTreeForm, KIcon("view-process-all-tree"));
 
923
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::SystemProcesses, KIcon("view-process-system"));
 
924
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::UserProcesses, KIcon("view-process-users"));
 
925
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::OwnProcesses, KIcon("view-process-own"));
 
926
    d->mUi->cmbFilter->setItemIcon(ProcessFilter::ProgramsOnly, KIcon("view-process-all"));
 
927
}
 
928
 
 
929
void KSysGuardProcessList::updateList()
 
930
{
 
931
    if(isVisible()) {
 
932
        KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::StandardInformation;
 
933
        if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoRead) || !d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoWrite))
 
934
            updateFlags |= KSysGuard::Processes::IOStatistics;
 
935
        if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingXMemory))
 
936
            updateFlags |= KSysGuard::Processes::XMemory;
 
937
        d->mModel.update(d->mUpdateIntervalMSecs, updateFlags);
 
938
        if(d->mUpdateTimer)
 
939
            d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
 
940
        emit updated();
 
941
        if (QToolTip::isVisible() && qApp->topLevelAt(QCursor::pos()) == window()) {
 
942
            QWidget *w = d->mUi->treeView->viewport();
 
943
            if(w->geometry().contains(d->mUi->treeView->mapFromGlobal( QCursor::pos() ))) {
 
944
                QHelpEvent event(QEvent::ToolTip, w->mapFromGlobal( QCursor::pos() ), QCursor::pos());
 
945
                qApp->notify(w, &event);
 
946
            }
 
947
        }
 
948
        if(--d->mResortCountDown <= 0) {
 
949
            d->mResortCountDown = 2;  //resort every second time
 
950
            //resort now
 
951
            QHeaderView *header= d->mUi->treeView->header();
 
952
            d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
 
953
        }
 
954
        if( d->mNeedToExpandInit ) {
 
955
            expandInit();
 
956
            d->mNeedToExpandInit = false;
 
957
        }
 
958
    }
 
959
}
 
960
 
 
961
int KSysGuardProcessList::updateIntervalMSecs() const
 
962
{
 
963
    return d->mUpdateIntervalMSecs;
 
964
}
 
965
 
 
966
void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs)
 
967
{
 
968
    if(intervalMSecs == d->mUpdateIntervalMSecs)
 
969
        return;
 
970
 
 
971
    d->mUpdateIntervalMSecs = intervalMSecs;
 
972
    if(intervalMSecs <= 0) { //no point keep the timer around if we aren't updating automatically
 
973
        delete d->mUpdateTimer;
 
974
        d->mUpdateTimer = NULL;
 
975
        return;
 
976
    }
 
977
 
 
978
    if(!d->mUpdateTimer) {
 
979
        //intervalMSecs is a valid time, so set up a timer
 
980
        d->mUpdateTimer = new QTimer(this);
 
981
        d->mUpdateTimer->setSingleShot(true);
 
982
        connect(d->mUpdateTimer, SIGNAL(timeout()), SLOT(updateList()));
 
983
        if(isVisible())
 
984
            d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
 
985
    } else
 
986
        d->mUpdateTimer->setInterval(d->mUpdateIntervalMSecs);
 
987
}
 
988
 
 
989
bool KSysGuardProcessList::reniceProcesses(const QList<long long> &pids, int niceValue)
 
990
{
 
991
    QList< long long> unreniced_pids;
 
992
    for (int i = 0; i < pids.size(); ++i) {
 
993
        bool success = d->mModel.processController()->setNiceness(pids.at(i), niceValue);
 
994
        if(!success) {
 
995
            unreniced_pids << pids.at(i);
 
996
        }
 
997
    }
 
998
    if(unreniced_pids.isEmpty()) return true; //All processes were reniced successfully
 
999
    if(!d->mModel.isLocalhost()) return false; //We can't use kauth to renice non-localhost processes
 
1000
 
 
1001
 
 
1002
    KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.renice");
 
1003
    action->setParentWidget(window());
 
1004
    d->setupKAuthAction( action, unreniced_pids);
 
1005
    action->addArgument("nicevalue", niceValue);
 
1006
    KAuth::ActionReply reply = action->execute();
 
1007
 
 
1008
    if (reply == KAuth::ActionReply::SuccessReply) {
 
1009
        updateList();
 
1010
        delete action;
 
1011
    } else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
 
1012
        KMessageBox::sorry(this, i18n("You do not have the permission to renice the process and there "
 
1013
                    "was a problem trying to run as root.  Error %1 %2", reply.errorCode(), reply.errorDescription()));
 
1014
        delete action;
 
1015
        return false;
 
1016
    }
 
1017
    return true;
 
1018
}
 
1019
 
 
1020
QList<KSysGuard::Process *> KSysGuardProcessList::selectedProcesses() const
 
1021
{
 
1022
    QList<KSysGuard::Process *> processes;
 
1023
    QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
1024
    for(int i = 0; i < selectedIndexes.size(); ++i) {
 
1025
        KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (d->mFilterModel.mapToSource(selectedIndexes.at(i)).internalPointer());
 
1026
        processes << process;
 
1027
    }
 
1028
    return processes;
 
1029
 
 
1030
}
 
1031
 
 
1032
void KSysGuardProcessList::reniceSelectedProcesses()
 
1033
{
 
1034
    QList<long long> pids;
 
1035
    QPointer<ReniceDlg> reniceDlg;
 
1036
    {
 
1037
        QList<KSysGuard::Process *> processes = selectedProcesses();
 
1038
        QStringList selectedAsStrings;
 
1039
 
 
1040
        if (processes.isEmpty()) {
 
1041
            KMessageBox::sorry(this, i18n("You must select a process first."));
 
1042
            return;
 
1043
        }
 
1044
 
 
1045
        int sched = -2;
 
1046
        int iosched = -2;
 
1047
        foreach(KSysGuard::Process *process, processes) {
 
1048
            pids << process->pid;
 
1049
            selectedAsStrings << d->mModel.getStringForProcess(process);
 
1050
            if(sched == -2) sched = (int)process->scheduler;
 
1051
            else if(sched != -1 && sched != (int)process->scheduler) sched = -1;  //If two processes have different schedulers, disable the cpu scheduler stuff
 
1052
            if(iosched == -2) iosched = (int)process->ioPriorityClass;
 
1053
            else if(iosched != -1 && iosched != (int)process->ioPriorityClass) iosched = -1;  //If two processes have different schedulers, disable the cpu scheduler stuff
 
1054
 
 
1055
        }
 
1056
        int firstPriority = processes.first()->niceLevel;
 
1057
        int firstIOPriority = processes.first()->ioniceLevel;
 
1058
 
 
1059
        bool supportsIoNice = d->mModel.processController()->supportsIoNiceness();
 
1060
        if(!supportsIoNice) { iosched = -2; firstIOPriority = -2; }
 
1061
        reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched, firstIOPriority, iosched);
 
1062
        if(reniceDlg->exec() == QDialog::Rejected) {
 
1063
            delete reniceDlg;
 
1064
            return;
 
1065
        }
 
1066
    }
 
1067
 
 
1068
    //Because we've done into ReniceDlg, which calls processEvents etc, our processes list is no
 
1069
    //longer valid
 
1070
 
 
1071
    QList<long long> renicePids;
 
1072
    QList<long long> changeCPUSchedulerPids;
 
1073
    QList<long long> changeIOSchedulerPids;
 
1074
    foreach (long long pid, pids) {
 
1075
        KSysGuard::Process *process = d->mModel.getProcess(pid);
 
1076
        if (!process)
 
1077
            continue;
 
1078
 
 
1079
        switch(reniceDlg->newCPUSched) {
 
1080
            case -2:
 
1081
            case -1:  //Invalid, not changed etc.
 
1082
                break;  //So do nothing
 
1083
            case KSysGuard::Process::Other:
 
1084
            case KSysGuard::Process::Fifo:
 
1085
                if(reniceDlg->newCPUSched != (int)process->scheduler) {
 
1086
                    changeCPUSchedulerPids << pid;
 
1087
                    renicePids << pid;
 
1088
                } else if(reniceDlg->newCPUPriority != process->niceLevel)
 
1089
                    renicePids << pid;
 
1090
                break;
 
1091
 
 
1092
            case KSysGuard::Process::RoundRobin:
 
1093
            case KSysGuard::Process::Batch:
 
1094
                if(reniceDlg->newCPUSched != (int)process->scheduler || reniceDlg->newCPUPriority != process->niceLevel) {
 
1095
                    changeCPUSchedulerPids << pid;
 
1096
                }
 
1097
                break;
 
1098
        }
 
1099
        switch(reniceDlg->newIOSched) {
 
1100
            case -2:
 
1101
            case -1:  //Invalid, not changed etc.
 
1102
                break;  //So do nothing
 
1103
            case KSysGuard::Process::None:
 
1104
                if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
 
1105
                    // Unfortunately linux doesn't actually let us set the ioniceness back to none after being set to something else
 
1106
                    if(process->ioPriorityClass != KSysGuard::Process::BestEffort || reniceDlg->newIOPriority != process->ioniceLevel)
 
1107
                        changeIOSchedulerPids << pid;
 
1108
                }
 
1109
                break;
 
1110
            case KSysGuard::Process::Idle:
 
1111
                if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
 
1112
                    changeIOSchedulerPids << pid;
 
1113
                }
 
1114
                break;
 
1115
            case KSysGuard::Process::BestEffort:
 
1116
                if(process->ioPriorityClass == KSysGuard::Process::None && reniceDlg->newIOPriority  == (process->niceLevel + 20)/5)
 
1117
                    break;  //Don't set to BestEffort if it's on None and the nicelevel wouldn't change
 
1118
            case KSysGuard::Process::RealTime:
 
1119
                if(reniceDlg->newIOSched != (int)process->ioPriorityClass || reniceDlg->newIOPriority != process->ioniceLevel) {
 
1120
                    changeIOSchedulerPids << pid;
 
1121
                }
 
1122
                break;
 
1123
        }
 
1124
 
 
1125
    }
 
1126
    if(!changeCPUSchedulerPids.isEmpty()) {
 
1127
        Q_ASSERT(reniceDlg->newCPUSched >= 0);
 
1128
        if(!changeCpuScheduler(changeCPUSchedulerPids, (KSysGuard::Process::Scheduler) reniceDlg->newCPUSched, reniceDlg->newCPUPriority)) {
 
1129
            delete reniceDlg;
 
1130
            return;
 
1131
        }
 
1132
 
 
1133
    }
 
1134
    if(!renicePids.isEmpty()) {
 
1135
        Q_ASSERT(reniceDlg->newCPUPriority <= 20 && reniceDlg->newCPUPriority >= -20);
 
1136
        if(!reniceProcesses(renicePids, reniceDlg->newCPUPriority)) {
 
1137
            delete reniceDlg;
 
1138
            return;
 
1139
        }
 
1140
    }
 
1141
    if(!changeIOSchedulerPids.isEmpty()) {
 
1142
        if(!changeIoScheduler(changeIOSchedulerPids, (KSysGuard::Process::IoPriorityClass) reniceDlg->newIOSched, reniceDlg->newIOPriority)) {
 
1143
            delete reniceDlg;
 
1144
            return;
 
1145
        }
 
1146
    }
 
1147
    delete reniceDlg;
 
1148
    updateList();
 
1149
}
 
1150
 
 
1151
bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority)
 
1152
{
 
1153
    if(newIoSched == KSysGuard::Process::None) newIoSched = KSysGuard::Process::BestEffort;
 
1154
    if(newIoSched == KSysGuard::Process::Idle) newIoSchedPriority = 0;
 
1155
    QList< long long> unchanged_pids;
 
1156
    for (int i = 0; i < pids.size(); ++i) {
 
1157
        bool success = d->mModel.processController()->setIoNiceness(pids.at(i), newIoSched, newIoSchedPriority);
 
1158
        if(!success) {
 
1159
            unchanged_pids << pids.at(i);
 
1160
        }
 
1161
    }
 
1162
    if(unchanged_pids.isEmpty()) return true;
 
1163
    if(!d->mModel.isLocalhost()) return false; //We can't use kauth to affect non-localhost processes
 
1164
 
 
1165
    KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changeioscheduler");
 
1166
    action->setParentWidget(window());
 
1167
 
 
1168
    d->setupKAuthAction( action, unchanged_pids);
 
1169
    action->addArgument("ioScheduler", (int)newIoSched);
 
1170
    action->addArgument("ioSchedulerPriority", newIoSchedPriority);
 
1171
 
 
1172
    KAuth::ActionReply reply = action->execute();
 
1173
 
 
1174
    if (reply == KAuth::ActionReply::SuccessReply) {
 
1175
        updateList();
 
1176
        delete action;
 
1177
    } else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
 
1178
        KMessageBox::sorry(this, i18n("You do not have the permission to change the I/O priority of the process and there "
 
1179
                    "was a problem trying to run as root.  Error %1 %2", reply.errorCode(), reply.errorDescription()));
 
1180
        delete action;
 
1181
        return false;
 
1182
    }
 
1183
    return true;
 
1184
}
 
1185
 
 
1186
bool KSysGuardProcessList::changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority)
 
1187
{
 
1188
    if(newCpuSched == KSysGuard::Process::Other || newCpuSched == KSysGuard::Process::Batch) newCpuSchedPriority = 0;
 
1189
    QList< long long> unchanged_pids;
 
1190
    for (int i = 0; i < pids.size(); ++i) {
 
1191
        bool success = d->mModel.processController()->setScheduler(pids.at(i), newCpuSched, newCpuSchedPriority);
 
1192
        if(!success) {
 
1193
            unchanged_pids << pids.at(i);
 
1194
        }
 
1195
    }
 
1196
    if(unchanged_pids.isEmpty()) return true;
 
1197
    if(!d->mModel.isLocalhost()) return false; //We can't use KAuth to affect non-localhost processes
 
1198
 
 
1199
    KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changecpuscheduler");
 
1200
    action->setParentWidget(window());
 
1201
    d->setupKAuthAction( action, unchanged_pids);
 
1202
    action->addArgument("cpuScheduler", (int)newCpuSched);
 
1203
    action->addArgument("cpuSchedulerPriority", newCpuSchedPriority);
 
1204
    KAuth::ActionReply reply = action->execute();
 
1205
 
 
1206
    if (reply == KAuth::ActionReply::SuccessReply) {
 
1207
        updateList();
 
1208
        delete action;
 
1209
    } else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
 
1210
        KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process and there "
 
1211
                    "was a problem trying to run as root.  Error %1 %2", reply.errorCode(), reply.errorDescription()));
 
1212
        delete action;
 
1213
        return false;
 
1214
    }
 
1215
    return true;
 
1216
}
 
1217
 
 
1218
bool KSysGuardProcessList::killProcesses(const QList< long long> &pids, int sig)
 
1219
{
 
1220
    QList< long long> unkilled_pids;
 
1221
    for (int i = 0; i < pids.size(); ++i) {
 
1222
        bool success = d->mModel.processController()->sendSignal(pids.at(i), sig);
 
1223
        // If we failed due to a reason other than insufficient permissions, elevating to root can't
 
1224
        // help us
 
1225
        if(!success && (d->mModel.processController()->lastError() == KSysGuard::Processes::InsufficientPermissions || d->mModel.processController()->lastError() == KSysGuard::Processes::Unknown)) {
 
1226
            unkilled_pids << pids.at(i);
 
1227
        }
 
1228
    }
 
1229
    if(unkilled_pids.isEmpty()) return true;
 
1230
    if(!d->mModel.isLocalhost()) return false; //We can't elevate privileges to kill non-localhost processes
 
1231
 
 
1232
    KAuth::Action action("org.kde.ksysguard.processlisthelper.sendsignal");
 
1233
    action.setParentWidget(window());
 
1234
    d->setupKAuthAction( &action, unkilled_pids);
 
1235
    action.addArgument("signal", sig);
 
1236
    KAuth::ActionReply reply = action.execute();
 
1237
 
 
1238
    if (reply == KAuth::ActionReply::SuccessReply) {
 
1239
        updateList();
 
1240
    } else if (reply.type() == KAuth::ActionReply::HelperError) {
 
1241
        if (reply.errorCode() > 0)
 
1242
            KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
 
1243
                        "was a problem trying to run as root. %1", reply.errorDescription()));
 
1244
        return false;
 
1245
    } else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
 
1246
        KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
 
1247
                    "was a problem trying to run as root.  Error %1 %2", reply.errorCode(), reply.errorDescription()));
 
1248
        return false;
 
1249
    }
 
1250
    return true;
 
1251
}
 
1252
 
 
1253
void KSysGuardProcessList::killSelectedProcesses()
 
1254
{
 
1255
    sendSignalToSelectedProcesses(SIGTERM, true);
 
1256
}
 
1257
 
 
1258
void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm)
 
1259
{
 
1260
    QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
 
1261
    QStringList selectedAsStrings;
 
1262
    QList< long long> selectedPids;
 
1263
 
 
1264
    QList<KSysGuard::Process *> processes = selectedProcesses();
 
1265
    foreach(KSysGuard::Process *process, processes) {
 
1266
        selectedPids << process->pid;
 
1267
        if (!confirm)
 
1268
            continue;
 
1269
        QString name = d->mModel.getStringForProcess(process);
 
1270
        if(name.size() > 100)
 
1271
            name = name.left(95) + QString::fromUtf8("…");
 
1272
        selectedAsStrings << name;
 
1273
    }
 
1274
 
 
1275
    if (selectedPids.isEmpty()) {
 
1276
        if (confirm)
 
1277
            KMessageBox::sorry(this, i18n("You must select a process first."));
 
1278
        return;
 
1279
    } else if (confirm && (sig == SIGTERM || sig == SIGKILL)) {
 
1280
        int count = selectedAsStrings.count();
 
1281
        QString msg;
 
1282
        QString title;
 
1283
        QString dontAskAgainKey;
 
1284
        QString closeButton;
 
1285
        if (sig == SIGTERM) {
 
1286
            msg = i18np("Are you sure you want to end this process?  Any unsaved work may be lost.",
 
1287
                "Are you sure you want to end these %1 processes?  Any unsaved work may be lost",
 
1288
                count);
 
1289
            title =  i18ncp("Dialog title", "End Process", "End %1 Processes", count);
 
1290
            dontAskAgainKey = "endconfirmation";
 
1291
            closeButton = i18n("End");
 
1292
        } else if (sig == SIGKILL) {
 
1293
            msg = i18np("<qt>Are you sure you want to <b>immediately and forcibly kill</b> this process?  Any unsaved work may be lost.",
 
1294
                "<qt>Are you sure you want to <b>immediately and forcibly kill</b> these %1 processes?  Any unsaved work may be lost",
 
1295
                count);
 
1296
            title =  i18ncp("Dialog title", "Forcibly Kill Process", "Forcibly Kill %1 Processes", count);
 
1297
            dontAskAgainKey = "killconfirmation";
 
1298
            closeButton = i18n("Kill");
 
1299
        }
 
1300
 
 
1301
        int res = KMessageBox::warningContinueCancelList(this, msg, selectedAsStrings,
 
1302
                title,
 
1303
                KGuiItem(closeButton, "process-stop"),
 
1304
                KStandardGuiItem::cancel(),
 
1305
                dontAskAgainKey);
 
1306
        if (res != KMessageBox::Continue)
 
1307
            return;
 
1308
    }
 
1309
 
 
1310
    //We have shown a GUI dialog box, which processes events etc.
 
1311
    //So processes is NO LONGER VALID
 
1312
 
 
1313
    if (!killProcesses(selectedPids, sig))
 
1314
        return;
 
1315
    if (sig == SIGTERM || sig == SIGKILL) {
 
1316
        foreach (long long pid, selectedPids) {
 
1317
            KSysGuard::Process *process = d->mModel.getProcess(pid);
 
1318
            if (process)
 
1319
                process->timeKillWasSent.start();
 
1320
            d->mUi->treeView->selectionModel()->clearSelection();
 
1321
        }
 
1322
    }
 
1323
    updateList();
 
1324
}
 
1325
 
 
1326
bool KSysGuardProcessList::showTotals() const {
 
1327
    return d->mModel.showTotals();
 
1328
}
 
1329
 
 
1330
void KSysGuardProcessList::setShowTotals(bool showTotals)  //slot
 
1331
{
 
1332
    d->mModel.setShowTotals(showTotals);
 
1333
}
 
1334
 
 
1335
ProcessModel::Units KSysGuardProcessList::units() const {
 
1336
    return d->mModel.units();
 
1337
}
 
1338
 
 
1339
void KSysGuardProcessList::setUnits(ProcessModel::Units unit) {
 
1340
    d->mModel.setUnits(unit);
 
1341
}
 
1342
 
 
1343
void KSysGuardProcessList::saveSettings(KConfigGroup &cg) {
 
1344
    /* Note that the ksysguard program does not use these functions.  It saves the settings itself to an xml file instead */
 
1345
    cg.writeEntry("units", (int)(units()));
 
1346
    cg.writeEntry("ioUnits", (int)(d->mModel.ioUnits()));
 
1347
    cg.writeEntry("ioInformation", (int)(d->mModel.ioInformation()));
 
1348
    cg.writeEntry("showCommandLineOptions", d->mModel.isShowCommandLineOptions());
 
1349
    cg.writeEntry("normalizeCPUUsage", d->mModel.isNormalizedCPUUsage());
 
1350
    cg.writeEntry("showTooltips", d->mModel.isShowingTooltips());
 
1351
    cg.writeEntry("showTotals", showTotals());
 
1352
    cg.writeEntry("filterState", (int)(state()));
 
1353
    cg.writeEntry("updateIntervalMSecs", updateIntervalMSecs());
 
1354
    cg.writeEntry("headerState", d->mUi->treeView->header()->saveState());
 
1355
    //If we change, say, the header between versions of ksysguard, then the old headerState settings will not be valid.
 
1356
    //The version property lets us keep track of which version we are
 
1357
    cg.writeEntry("version", PROCESSHEADERVERSION);
 
1358
}
 
1359
 
 
1360
void KSysGuardProcessList::loadSettings(const KConfigGroup &cg) {
 
1361
    /* Note that the ksysguard program does not use these functions.  It saves the settings itself to an xml file instead */
 
1362
    setUnits((ProcessModel::Units) cg.readEntry("units", (int)ProcessModel::UnitsKB));
 
1363
    d->mModel.setIoUnits((ProcessModel::Units) cg.readEntry("ioUnits", (int)ProcessModel::UnitsKB));
 
1364
    d->mModel.setIoInformation((ProcessModel::IoInformation) cg.readEntry("ioInformation", (int)ProcessModel::ActualBytesRate));
 
1365
    d->mModel.setShowCommandLineOptions(cg.readEntry("showCommandLineOptions", false));
 
1366
    d->mModel.setNormalizedCPUUsage(cg.readEntry("normalizeCPUUsage", true));
 
1367
    d->mModel.setShowingTooltips(cg.readEntry("showTooltips", true));
 
1368
    setShowTotals(cg.readEntry("showTotals", true));
 
1369
    setStateInt(cg.readEntry("filterState", (int)ProcessFilter::AllProcesses));
 
1370
    setUpdateIntervalMSecs(cg.readEntry("updateIntervalMSecs", 2000));
 
1371
    int version = cg.readEntry("version", 0);
 
1372
    if(version == PROCESSHEADERVERSION) {  //If the header has changed, the old settings are no longer valid.  Only restore if version is the same
 
1373
        restoreHeaderState(cg.readEntry("headerState", QByteArray()));
 
1374
    }
 
1375
}
 
1376
 
 
1377
void KSysGuardProcessList::restoreHeaderState(const QByteArray & state) {
 
1378
    d->mUi->treeView->header()->restoreState(state);
 
1379
}
 
1380
 
 
1381
bool KSysGuardProcessList::eventFilter(QObject *obj, QEvent *event) {
 
1382
    if (event->type() == QEvent::KeyPress) {
 
1383
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
 
1384
        if(obj == d->mUi->treeView) {
 
1385
            if(  keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
 
1386
                d->mUi->treeView->selectionModel()->select(d->mUi->treeView->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
 
1387
                showProcessContextMenu(d->mUi->treeView->currentIndex());
 
1388
            }
 
1389
        } else {
 
1390
            // obj must be txtFilter
 
1391
            if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) ||
 
1392
                    keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) ||
 
1393
                    keyEvent->matches(QKeySequence::MoveToNextPage) ||  keyEvent->matches(QKeySequence::SelectNextPage) ||
 
1394
                    keyEvent->matches(QKeySequence::MoveToPreviousPage) ||  keyEvent->matches(QKeySequence::SelectPreviousPage) ||
 
1395
                    keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)
 
1396
            {
 
1397
                QApplication::sendEvent(d->mUi->treeView, event);
 
1398
                return true;
 
1399
            }
 
1400
        }
 
1401
    }
 
1402
    return false;
 
1403
}
 
1404
 
 
1405
ProcessModel *KSysGuardProcessList::processModel() {
 
1406
    return &d->mModel;
 
1407
}
 
1408
 
 
1409
void KSysGuardProcessList::setKillButtonVisible(bool visible)
 
1410
{
 
1411
    d->mUi->btnKillProcess->setVisible(visible);
 
1412
}
 
1413
 
 
1414
bool KSysGuardProcessList::isKillButtonVisible() const
 
1415
{
 
1416
    return d->mUi->btnKillProcess->isVisible();
 
1417
}
 
1418
bool KSysGuardProcessList::scriptingEnabled() const
 
1419
{
 
1420
    return !!d->mScripting;
 
1421
}
 
1422
void KSysGuardProcessList::setScriptingEnabled(bool enabled)
 
1423
{
 
1424
    if(!!d->mScripting == enabled)
 
1425
        return;  //Nothing changed
 
1426
    if(!enabled) {
 
1427
        delete d->mScripting;
 
1428
        d->mScripting = NULL;
 
1429
    } else {
 
1430
        d->mScripting = new Scripting(this);
 
1431
        d->mScripting->hide();
 
1432
    }
 
1433
 
 
1434
}