]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
Remove jobq.c constraint that read and write SD must be
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2008 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *   Version $Id$
30  *
31  *   Dirk Bartley, March 2007
32  */
33  
34 #include <QAbstractEventDispatcher>
35 #include <QTableWidgetItem>
36 #include "bat.h"
37 #include "joblist.h"
38 #include "restore.h"
39 #include "joblog/joblog.h"
40 #ifdef HAVE_QWT
41 #include "jobgraphs/jobplot.h"
42 #endif
43 #include "util/fmtwidgetitem.h"
44 #include "util/comboutil.h"
45
46 /*
47  * Constructor for the class
48  */
49 JobList::JobList(const QString &mediaName, const QString &clientName,
50           const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
51 {
52    setupUi(this);
53    m_name = ""; /* treeWidgetName has a virtual override in this class */
54    m_mediaName = mediaName;
55    m_clientName = clientName;
56    m_jobName = jobName;
57    m_filesetName = filesetName;
58    m_filesetName = filesetName;
59    pgInitialize("", parentTreeWidgetItem);
60    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
61    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
62
63    m_resultCount = 0;
64    m_populated = false;
65    m_closeable = false;
66    if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
67       m_closeable=true;
68    m_checkCurrentWidget = true;
69    createConnections();
70
71    /* Set Defaults for check and spin for limits */
72    limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
73    limitSpinBox->setValue(mainWin->m_recordLimitVal);
74    daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
75    daysSpinBox->setValue(mainWin->m_daysLimitVal);
76    dockPage();
77
78    QGridLayout *gridLayout = new QGridLayout(this);
79    gridLayout->setSpacing(6);
80    gridLayout->setMargin(9);
81    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
82
83    m_splitter = new QSplitter(Qt::Vertical, this);
84    QScrollArea *area = new QScrollArea();
85    area->setObjectName(QString::fromUtf8("area"));
86    area->setWidget(frame);
87    area->setWidgetResizable(true);
88    m_splitter->addWidget(mp_tableWidget);
89    m_splitter->addWidget(area);
90
91    gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
92    readSettings();
93 }
94
95 /*
96  * Write the m_splitter settings in the destructor
97  */
98 JobList::~JobList()
99 {
100    writeSettings();
101 }
102
103 /*
104  * The Meat of the class.
105  * This function will populate the QTableWidget, mp_tablewidget, with
106  * QTableWidgetItems representing the results of a query for what jobs exist on
107  * the media name passed from the constructor stored in m_mediaName.
108  */
109 void JobList::populateTable()
110 {
111    if (!m_console->preventInUseConnect())
112        return;
113
114    /* Can't do this in constructor because not neccesarily conected in constructor */
115    prepareFilterWidgets();
116    m_populated = true;
117
118    /* Set up query */
119    QString query;
120    fillQueryString(query);
121
122    /* Set up the Header for the table */
123    QStringList headerlist = (QStringList()
124       << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime") 
125       << tr("Job Type") << tr("Job Level") << tr("Job Files") 
126       << tr("Job Bytes") << tr("Job Status")  << tr("Purged") << tr("File Set"));
127
128    m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
129    m_purgedIndex = headerlist.indexOf(tr("Purged"));
130    m_typeIndex = headerlist.indexOf(tr("Job Type"));
131    m_statusIndex = headerlist.indexOf(tr("Job Status"));
132    m_startIndex = headerlist.indexOf(tr("Job Starttime"));
133    m_filesIndex = headerlist.indexOf(tr("Job Files"));
134    m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
135
136    /* Initialize the QTableWidget */
137    m_checkCurrentWidget = false;
138    mp_tableWidget->clear();
139    m_checkCurrentWidget = true;
140    mp_tableWidget->setColumnCount(headerlist.size());
141    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
142    mp_tableWidget->horizontalHeader()->setHighlightSections(false);
143    mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
144    mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
145
146    if (mainWin->m_sqlDebug) {
147       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
148    }
149
150    QStringList results;
151    if (m_console->sql_cmd(query, results)) {
152       m_resultCount = results.count();
153
154       QStringList fieldlist;
155       mp_tableWidget->setRowCount(results.size());
156
157       int row = 0;
158       /* Iterate through the record returned from the query */
159       QString resultline;
160       foreach (resultline, results) {
161          fieldlist = resultline.split("\t");
162          if (fieldlist.size() < 12)
163             continue; /* some fields missing, ignore row */
164
165          TableItemFormatter jobitem(*mp_tableWidget, row);
166   
167          /* Iterate through fields in the record */
168          QStringListIterator fld(fieldlist);
169          int col = 0;
170
171          /* job id */
172          jobitem.setNumericFld(col++, fld.next());
173
174          /* job name */
175          jobitem.setTextFld(col++, fld.next());
176
177          /* client */
178          jobitem.setTextFld(col++, fld.next());
179
180          /* job starttime */
181          jobitem.setTextFld(col++, fld.next(), true);
182
183          /* job type */
184          jobitem.setJobTypeFld(col++, fld.next());
185
186          /* job level */
187          jobitem.setJobLevelFld(col++, fld.next());
188
189          /* job files */
190          jobitem.setNumericFld(col++, fld.next());
191
192          /* job bytes */
193          jobitem.setBytesFld(col++, fld.next());
194
195          /* job status */
196          jobitem.setJobStatusFld(col++, fld.next());
197
198          /* purged */
199          jobitem.setBoolFld(col++, fld.next());
200
201          /* fileset */
202          jobitem.setTextFld(col++, fld.next());
203
204          row++;
205       }
206    } 
207    /* set default sorting */
208    mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
209    mp_tableWidget->setSortingEnabled(true);
210    
211    /* Resize the columns */
212    mp_tableWidget->resizeColumnsToContents();
213    mp_tableWidget->resizeRowsToContents();
214    mp_tableWidget->verticalHeader()->hide();
215    if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
216       /* for context sensitive searches, let the user know if there were no
217        * results */
218       QMessageBox::warning(this, "Bat",
219           tr("The Jobs query returned no results.\n"
220          "Press OK to continue?"), QMessageBox::Ok );
221    }
222 }
223
224 void JobList::prepareFilterWidgets()
225 {
226    if (!m_populated) {
227       clientComboBox->addItem(tr("Any"));
228       clientComboBox->addItems(m_console->client_list);
229       comboSel(clientComboBox, m_clientName);
230
231       QStringList volumeList;
232       m_console->getVolumeList(volumeList);
233       volumeComboBox->addItem(tr("Any"));
234       volumeComboBox->addItems(volumeList);
235       comboSel(volumeComboBox, m_mediaName);
236
237       jobComboBox->addItem(tr("Any"));
238       jobComboBox->addItems(m_console->job_list);
239       comboSel(jobComboBox, m_jobName);
240
241       levelComboFill(levelComboBox);
242
243       boolComboFill(purgedComboBox);
244
245       fileSetComboBox->addItem(tr("Any"));
246       fileSetComboBox->addItems(m_console->fileset_list);
247       comboSel(fileSetComboBox, m_filesetName);
248
249       jobStatusComboFill(statusComboBox);
250    }
251 }
252
253 void JobList::fillQueryString(QString &query)
254 {
255    query = "";
256    int volumeIndex = volumeComboBox->currentIndex();
257    if (volumeIndex != -1)
258       m_mediaName = volumeComboBox->itemText(volumeIndex);
259    QString distinct = "";
260    if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
261    query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, " 
262             " Client.Name AS Client,"
263             " Job.Starttime AS JobStart, Job.Type AS JobType,"
264             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
265             " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
266             " Job.PurgedFiles AS Purged, FileSet.FileSet"
267             " FROM Job"
268             " JOIN Client ON (Client.ClientId=Job.ClientId)"
269             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
270    QStringList conditions;
271    if (m_mediaName != tr("Any")) {
272       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
273                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
274       conditions.append("Media.VolumeName='" + m_mediaName + "'");
275    }
276
277    comboCond(conditions, clientComboBox, "Client.Name");
278    comboCond(conditions, jobComboBox, "Job.Name");
279    levelComboCond(conditions, levelComboBox, "Job.Level");
280    jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
281    boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
282    comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
283
284    /* If Limit check box For limit by days is checked  */
285    if (daysCheckBox->checkState() == Qt::Checked) {
286       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
287       QString since = stamp.toString(Qt::ISODate);
288       conditions.append("Job.Starttime>'" + since + "'");
289    }
290    bool first = true;
291    foreach (QString condition, conditions) {
292       if (first) {
293          query += " WHERE " + condition;
294          first = false;
295       } else {
296          query += " AND " + condition;
297       }
298    }
299    /* Descending */
300    query += " ORDER BY Job.JobId DESC";
301    /* If Limit check box for limit records returned is checked  */
302    if (limitCheckBox->checkState() == Qt::Checked) {
303       QString limit;
304       limit.setNum(limitSpinBox->value());
305       query += " LIMIT " + limit;
306    }
307 }
308
309 /*
310  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
311  * The tree has been populated.
312  */
313 void JobList::PgSeltreeWidgetClicked()
314 {
315    if (!m_populated) {
316       populateTable();
317    }
318 }
319
320 /*
321  *  Virtual function override of pages function which is called when this page
322  *  is visible on the stack
323  */
324 void JobList::currentStackItem()
325 {
326 /*   if (!m_populated) populate every time user comes back to this object */
327       populateTable();
328 }
329
330 /*
331  * Virtual Function to return the name for the medialist tree widget
332  */
333 void JobList::treeWidgetName(QString &desc)
334 {
335    if (m_mediaName != "" ) {
336      desc = tr("JobList of Volume %1").arg(m_mediaName);
337    } else if (m_clientName != "" ) {
338      desc = tr("JobList of Client %1").arg(m_clientName);
339    } else if (m_jobName != "" ) {
340      desc = tr("JobList of Job %1").arg(m_jobName);
341    } else if (m_filesetName != "" ) {
342      desc = tr("JobList of fileset %1").arg(m_filesetName);
343    } else {
344      desc = tr("JobList");
345    }
346 }
347
348 /*
349  * This functions much line tableItemChanged for trees like the page selector,
350  * but I will do much less here
351  */
352 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
353 {
354    if (m_checkCurrentWidget) {
355       int row = currentItem->row();
356       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
357       m_currentJob = jobitem->text();
358
359       /* include purged action or not */
360       jobitem = mp_tableWidget->item(row, m_purgedIndex);
361       QString purged = jobitem->text();
362       mp_tableWidget->removeAction(actionPurgeFiles);
363       if (purged == tr("No") ) {
364          mp_tableWidget->addAction(actionPurgeFiles);
365       }
366       /* include restore from time and job action or not */
367       jobitem = mp_tableWidget->item(row, m_typeIndex);
368       QString type = jobitem->text();
369       mp_tableWidget->removeAction(actionRestoreFromJob);
370       mp_tableWidget->removeAction(actionRestoreFromTime);
371       if (type == tr("Backup")) {
372          mp_tableWidget->addAction(actionRestoreFromJob);
373          mp_tableWidget->addAction(actionRestoreFromTime);
374       }
375       /* include cancel action or not */
376       jobitem = mp_tableWidget->item(row, m_statusIndex);
377       QString status = jobitem->text();
378       mp_tableWidget->removeAction(actionCancelJob);
379       if (status == tr("Running") || status == tr("Created, not yet running")) {
380          mp_tableWidget->addAction(actionCancelJob);
381       }
382    }
383 }
384
385 /*
386  * Function to create connections for context sensitive menu for this and
387  * the page selector
388  */
389 void JobList::createConnections()
390 {
391    /* connect to the action specific to this pages class that shows up in the 
392     * page selector tree */
393    connect(actionRefreshJobList, SIGNAL(triggered()), this,
394                 SLOT(populateTable()));
395    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
396 #ifdef HAVE_QWT
397    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
398 #else
399    graphButton->setEnabled(false);
400    graphButton->setVisible(false);
401 #endif
402    /* for the tableItemChanged to maintain m_currentJob */
403    connect(mp_tableWidget, SIGNAL(
404            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
405            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
406
407    /* for the tableItemChanged to maintain a delete selection */
408    connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
409            this, SLOT(selectedJobsGet()) );
410
411    /* Do what is required for the local context sensitive menu */
412
413
414    /* setContextMenuPolicy is required */
415    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
416
417    /* Add Actions */
418    mp_tableWidget->addAction(actionRefreshJobList);
419    mp_tableWidget->addAction(actionListJobid);
420    mp_tableWidget->addAction(actionListFilesOnJob);
421    mp_tableWidget->addAction(actionListJobMedia);
422    mp_tableWidget->addAction(actionListVolumes);
423    mp_tableWidget->addAction(actionDeleteJob);
424    mp_tableWidget->addAction(actionPurgeFiles);
425    mp_tableWidget->addAction(actionRestoreFromJob);
426    mp_tableWidget->addAction(actionRestoreFromTime);
427    mp_tableWidget->addAction(actionShowLogForJob);
428
429    /* Make Connections */
430    connect(actionListJobid, SIGNAL(triggered()), this,
431                 SLOT(consoleListJobid()));
432    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
433                 SLOT(consoleListFilesOnJob()));
434    connect(actionListJobMedia, SIGNAL(triggered()), this,
435                 SLOT(consoleListJobMedia()));
436    connect(actionListVolumes, SIGNAL(triggered()), this,
437                 SLOT(consoleListVolumes()));
438    connect(actionDeleteJob, SIGNAL(triggered()), this,
439                 SLOT(consoleDeleteJob()));
440    connect(actionPurgeFiles, SIGNAL(triggered()), this,
441                 SLOT(consolePurgeFiles()));
442    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
443                 SLOT(preRestoreFromJob()));
444    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
445                 SLOT(preRestoreFromTime()));
446    connect(actionShowLogForJob, SIGNAL(triggered()), this,
447                 SLOT(showLogForJob()));
448    connect(actionCancelJob, SIGNAL(triggered()), this,
449                 SLOT(consoleCancelJob()));
450    connect(actionListJobTotals, SIGNAL(triggered()), this,
451                 SLOT(consoleListJobTotals()));
452
453    m_contextActions.append(actionRefreshJobList);
454    m_contextActions.append(actionListJobTotals);
455 }
456
457 /*
458  * Functions to respond to local context sensitive menu sending console commands
459  * If I could figure out how to make these one function passing a string, Yaaaaaa
460  */
461 void JobList::consoleListJobid()
462 {
463    QString cmd("list jobid=");
464    cmd += m_currentJob;
465    if (mainWin->m_longList) { cmd.prepend("l"); }
466    consoleCommand(cmd);
467 }
468 void JobList::consoleListFilesOnJob()
469 {
470    QString cmd("list files jobid=");
471    cmd += m_currentJob;
472    if (mainWin->m_longList) { cmd.prepend("l"); }
473    consoleCommand(cmd);
474 }
475 void JobList::consoleListJobMedia()
476 {
477    QString cmd("list jobmedia jobid=");
478    cmd += m_currentJob;
479    if (mainWin->m_longList) { cmd.prepend("l"); }
480    consoleCommand(cmd);
481 }
482 void JobList::consoleListVolumes()
483 {
484    QString cmd("list volumes jobid=");
485    cmd += m_currentJob;
486    if (mainWin->m_longList) { cmd.prepend("l"); }
487    consoleCommand(cmd);
488 }
489 void JobList::consoleListJobTotals()
490 {
491    QString cmd("list jobtotals");
492    if (mainWin->m_longList) { cmd.prepend("l"); }
493    consoleCommand(cmd);
494 }
495 void JobList::consoleDeleteJob()
496 {
497    if (QMessageBox::warning(this, "Bat",
498       tr("Are you sure you want to delete??  !!!.\n"
499 "This delete command is used to delete a Job record and all associated catalog"
500 " records that were created. This command operates only on the Catalog"
501 " database and has no effect on the actual data written to a Volume. This"
502 " command can be dangerous and we strongly recommend that you do not use"
503 " it unless you know what you are doing.  The Job and all its associated"
504 " records (File and JobMedia) will be deleted from the catalog."
505       "Press OK to proceed with delete operation.?"),
506       QMessageBox::Ok | QMessageBox::Cancel)
507       == QMessageBox::Cancel) { return; }
508
509    QString cmd("delete job jobid=");
510    cmd += m_selectedJobs;
511    consoleCommand(cmd);
512 }
513 void JobList::consolePurgeFiles()
514 {
515    if (QMessageBox::warning(this, "Bat",
516       tr("Are you sure you want to purge ??  !!!.\n"
517 "The Purge command will delete associated Catalog database records from Jobs and"
518 " Volumes without considering the retention period. Purge  works only on the"
519 " Catalog database and does not affect data written to Volumes. This command can"
520 " be dangerous because you can delete catalog records associated with current"
521 " backups of files, and we recommend that you do not use it unless you know what"
522 " you are doing.\n"
523       "Press OK to proceed with the purge operation?"),
524       QMessageBox::Ok | QMessageBox::Cancel)
525       == QMessageBox::Cancel) { return; }
526
527    QString cmd("purge files jobid=");
528    cmd += m_currentJob;
529    consoleCommand(cmd);
530 }
531
532 /*
533  * Subroutine to call preRestore to restore from a select job
534  */
535 void JobList::preRestoreFromJob()
536 {
537    new prerestorePage(m_currentJob, R_JOBIDLIST);
538 }
539
540 /*
541  * Subroutine to call preRestore to restore from a select job
542  */
543 void JobList::preRestoreFromTime()
544 {
545    new prerestorePage(m_currentJob, R_JOBDATETIME);
546 }
547
548 /*
549  * Subroutine to call class to show the log in the database from that job
550  */
551 void JobList::showLogForJob()
552 {
553    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
554    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
555 }
556
557 /*
558  * Cancel a running job
559  */
560 void JobList::consoleCancelJob()
561 {
562    QString cmd("cancel jobid=");
563    cmd += m_currentJob;
564    consoleCommand(cmd);
565 }
566
567 /*
568  * Graph this table
569  */
570 void JobList::graphTable()
571 {
572 #ifdef HAVE_QWT
573    JobPlotPass pass;
574    pass.recordLimitCheck = limitCheckBox->checkState();
575    pass.daysLimitCheck = daysCheckBox->checkState();
576    pass.recordLimitSpin = limitSpinBox->value();
577    pass.daysLimitSpin = daysSpinBox->value();
578    pass.jobCombo = jobComboBox->currentText();
579    pass.clientCombo = clientComboBox->currentText();
580    pass.volumeCombo = volumeComboBox->currentText();
581    pass.fileSetCombo = fileSetComboBox->currentText();
582    pass.purgedCombo = purgedComboBox->currentText();
583    pass.levelCombo = levelComboBox->currentText();
584    pass.statusCombo = statusComboBox->currentText();
585    pass.use = true;
586    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
587    new JobPlot(pageSelectorTreeWidgetItem, pass);
588 #endif
589 }
590
591 /*
592  * Save user settings associated with this page
593  */
594 void JobList::writeSettings()
595 {
596    QSettings settings(m_console->m_dir->name(), "bat");
597    settings.beginGroup(m_groupText);
598    settings.setValue(m_splitText, m_splitter->saveState());
599    settings.endGroup();
600 }
601
602 /*
603  * Read and restore user settings associated with this page
604  */
605 void JobList::readSettings()
606 {
607    m_groupText = "JobListPage";
608    m_splitText = "splitterSizes_1";
609    QSettings settings(m_console->m_dir->name(), "bat");
610    settings.beginGroup(m_groupText);
611    m_splitter->restoreState(settings.value(m_splitText).toByteArray());
612    settings.endGroup();
613 }
614
615 /*
616  * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
617  */
618 void JobList::selectedJobsGet()
619 {
620    QList<int> rowList;
621    QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
622    foreach (QTableWidgetItem *sitem, sitems) {
623       int row = sitem->row();
624       if (!rowList.contains(row)) {
625          rowList.append(row);
626       }
627    }
628
629    m_selectedJobs = "";
630    bool first = true;
631    foreach(int row, rowList) {
632       QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
633       if (!first) m_selectedJobs.append(",");
634       else first = false;
635       m_selectedJobs.append(sitem->text());
636    }
637    m_selectedJobsCount = rowList.count();
638    if (m_selectedJobsCount > 1) {
639      QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
640        actionDeleteJob->setText(text);
641    } else {
642      actionDeleteJob->setText(tr("Delete Single Job"));
643    }
644 }