]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
This is the rather large commit of adding the feature to create multiple connections. I
[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 "bat.h"
35 #include <QAbstractEventDispatcher>
36 #include <QTableWidgetItem>
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    /* Can't do this in constructor because not neccesarily conected in constructor */
112    prepareFilterWidgets();
113    m_populated = true;
114
115    Freeze frz(*mp_tableWidget); /* disable updating*/
116
117    /* Set up query */
118    QString query;
119    fillQueryString(query);
120
121    /* Set up the Header for the table */
122    QStringList headerlist = (QStringList()
123       << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime") 
124       << tr("Job Type") << tr("Job Level") << tr("Job Files") 
125       << tr("Job Bytes") << tr("Job Status")  << tr("Purged") << tr("File Set"));
126
127    m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
128    m_purgedIndex = headerlist.indexOf(tr("Purged"));
129    m_typeIndex = headerlist.indexOf(tr("Job Type"));
130    m_statusIndex = headerlist.indexOf(tr("Job Status"));
131    m_startIndex = headerlist.indexOf(tr("Job Starttime"));
132    m_filesIndex = headerlist.indexOf(tr("Job Files"));
133    m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
134
135    /* Initialize the QTableWidget */
136    m_checkCurrentWidget = false;
137    mp_tableWidget->clear();
138    m_checkCurrentWidget = true;
139    mp_tableWidget->setColumnCount(headerlist.size());
140    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
141    mp_tableWidget->horizontalHeader()->setHighlightSections(false);
142    mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
143    mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
144
145    if (mainWin->m_sqlDebug) {
146       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
147    }
148
149    QStringList results;
150    if (m_console->sql_cmd(query, results)) {
151       m_resultCount = results.count();
152
153       QStringList fieldlist;
154       mp_tableWidget->setRowCount(results.size());
155
156       int row = 0;
157       /* Iterate through the record returned from the query */
158       QString resultline;
159       foreach (resultline, results) {
160          fieldlist = resultline.split("\t");
161          if (fieldlist.size() < 12)
162             continue; /* some fields missing, ignore row */
163
164          TableItemFormatter jobitem(*mp_tableWidget, row);
165   
166          /* Iterate through fields in the record */
167          QStringListIterator fld(fieldlist);
168          int col = 0;
169
170          /* job id */
171          jobitem.setNumericFld(col++, fld.next());
172
173          /* job name */
174          jobitem.setTextFld(col++, fld.next());
175
176          /* client */
177          jobitem.setTextFld(col++, fld.next());
178
179          /* job starttime */
180          jobitem.setTextFld(col++, fld.next(), true);
181
182          /* job type */
183          jobitem.setJobTypeFld(col++, fld.next());
184
185          /* job level */
186          jobitem.setJobLevelFld(col++, fld.next());
187
188          /* job files */
189          jobitem.setNumericFld(col++, fld.next());
190
191          /* job bytes */
192          jobitem.setBytesFld(col++, fld.next());
193
194          /* job status */
195          jobitem.setJobStatusFld(col++, fld.next());
196
197          /* purged */
198          jobitem.setBoolFld(col++, fld.next());
199
200          /* fileset */
201          jobitem.setTextFld(col++, fld.next());
202
203          row++;
204       }
205    } 
206    /* set default sorting */
207    mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
208    mp_tableWidget->setSortingEnabled(true);
209    
210    /* Resize the columns */
211    mp_tableWidget->resizeColumnsToContents();
212    mp_tableWidget->resizeRowsToContents();
213    mp_tableWidget->verticalHeader()->hide();
214    if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
215       /* for context sensitive searches, let the user know if there were no
216        * results */
217       QMessageBox::warning(this, "Bat",
218           tr("The Jobs query returned no results.\n"
219          "Press OK to continue?"), QMessageBox::Ok );
220    }
221 }
222
223 void JobList::prepareFilterWidgets()
224 {
225    if (!m_populated) {
226       clientComboBox->addItem(tr("Any"));
227       clientComboBox->addItems(m_console->client_list);
228       comboSel(clientComboBox, m_clientName);
229
230       QStringList volumeList;
231       m_console->getVolumeList(volumeList);
232       volumeComboBox->addItem(tr("Any"));
233       volumeComboBox->addItems(volumeList);
234       comboSel(volumeComboBox, m_mediaName);
235
236       jobComboBox->addItem(tr("Any"));
237       jobComboBox->addItems(m_console->job_list);
238       comboSel(jobComboBox, m_jobName);
239
240       levelComboFill(levelComboBox);
241
242       boolComboFill(purgedComboBox);
243
244       fileSetComboBox->addItem(tr("Any"));
245       fileSetComboBox->addItems(m_console->fileset_list);
246       comboSel(fileSetComboBox, m_filesetName);
247
248       jobStatusComboFill(statusComboBox);
249    }
250 }
251
252 void JobList::fillQueryString(QString &query)
253 {
254    query = "";
255    int volumeIndex = volumeComboBox->currentIndex();
256    if (volumeIndex != -1)
257       m_mediaName = volumeComboBox->itemText(volumeIndex);
258    QString distinct = "";
259    if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
260    query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, " 
261             " Client.Name AS Client,"
262             " Job.Starttime AS JobStart, Job.Type AS JobType,"
263             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
264             " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
265             " Job.PurgedFiles AS Purged, FileSet.FileSet"
266             " FROM Job"
267             " JOIN Client ON (Client.ClientId=Job.ClientId)"
268             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
269    QStringList conditions;
270    if (m_mediaName != tr("Any")) {
271       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
272                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
273       conditions.append("Media.VolumeName='" + m_mediaName + "'");
274    }
275
276    comboCond(conditions, clientComboBox, "Client.Name");
277    comboCond(conditions, jobComboBox, "Job.Name");
278    levelComboCond(conditions, levelComboBox, "Job.Level");
279    jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
280    boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
281    comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
282
283    /* If Limit check box For limit by days is checked  */
284    if (daysCheckBox->checkState() == Qt::Checked) {
285       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
286       QString since = stamp.toString(Qt::ISODate);
287       conditions.append("Job.Starttime>'" + since + "'");
288    }
289    bool first = true;
290    foreach (QString condition, conditions) {
291       if (first) {
292          query += " WHERE " + condition;
293          first = false;
294       } else {
295          query += " AND " + condition;
296       }
297    }
298    /* Descending */
299    query += " ORDER BY Job.JobId DESC";
300    /* If Limit check box for limit records returned is checked  */
301    if (limitCheckBox->checkState() == Qt::Checked) {
302       QString limit;
303       limit.setNum(limitSpinBox->value());
304       query += " LIMIT " + limit;
305    }
306 }
307
308 /*
309  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
310  * The tree has been populated.
311  */
312 void JobList::PgSeltreeWidgetClicked()
313 {
314    if (!m_populated) {
315       populateTable();
316    }
317 }
318
319 /*
320  *  Virtual function override of pages function which is called when this page
321  *  is visible on the stack
322  */
323 void JobList::currentStackItem()
324 {
325 /*   if (!m_populated) populate every time user comes back to this object */
326       populateTable();
327 }
328
329 /*
330  * Virtual Function to return the name for the medialist tree widget
331  */
332 void JobList::treeWidgetName(QString &desc)
333 {
334    if (m_mediaName != "" ) {
335      desc = tr("JobList of Volume %1").arg(m_mediaName);
336    } else if (m_clientName != "" ) {
337      desc = tr("JobList of Client %1").arg(m_clientName);
338    } else if (m_jobName != "" ) {
339      desc = tr("JobList of Job %1").arg(m_jobName);
340    } else if (m_filesetName != "" ) {
341      desc = tr("JobList of fileset %1").arg(m_filesetName);
342    } else {
343      desc = tr("JobList");
344    }
345 }
346
347 /*
348  * This functions much line tableItemChanged for trees like the page selector,
349  * but I will do much less here
350  */
351 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
352 {
353    if (m_checkCurrentWidget) {
354       int row = currentItem->row();
355       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
356       m_currentJob = jobitem->text();
357
358       /* include purged action or not */
359       jobitem = mp_tableWidget->item(row, m_purgedIndex);
360       QString purged = jobitem->text();
361       mp_tableWidget->removeAction(actionPurgeFiles);
362       if (purged == tr("No") ) {
363          mp_tableWidget->addAction(actionPurgeFiles);
364       }
365       /* include restore from time and job action or not */
366       jobitem = mp_tableWidget->item(row, m_typeIndex);
367       QString type = jobitem->text();
368       mp_tableWidget->removeAction(actionRestoreFromJob);
369       mp_tableWidget->removeAction(actionRestoreFromTime);
370       if (type == tr("Backup")) {
371          mp_tableWidget->addAction(actionRestoreFromJob);
372          mp_tableWidget->addAction(actionRestoreFromTime);
373       }
374       /* include cancel action or not */
375       jobitem = mp_tableWidget->item(row, m_statusIndex);
376       QString status = jobitem->text();
377       mp_tableWidget->removeAction(actionCancelJob);
378       if (status == tr("Running") || status == tr("Created, not yet running")) {
379          mp_tableWidget->addAction(actionCancelJob);
380       }
381    }
382 }
383
384 /*
385  * Function to create connections for context sensitive menu for this and
386  * the page selector
387  */
388 void JobList::createConnections()
389 {
390    /* connect to the action specific to this pages class that shows up in the 
391     * page selector tree */
392    connect(actionRefreshJobList, SIGNAL(triggered()), this,
393                 SLOT(populateTable()));
394    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
395 #ifdef HAVE_QWT
396    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
397 #else
398    graphButton->setEnabled(false);
399    graphButton->setVisible(false);
400 #endif
401    /* for the tableItemChanged to maintain m_currentJob */
402    connect(mp_tableWidget, SIGNAL(
403            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
404            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
405
406    /* for the tableItemChanged to maintain a delete selection */
407    connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
408            this, SLOT(selectedJobsGet()) );
409
410    /* Do what is required for the local context sensitive menu */
411
412
413    /* setContextMenuPolicy is required */
414    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
415
416    /* Add Actions */
417    mp_tableWidget->addAction(actionRefreshJobList);
418    mp_tableWidget->addAction(actionListJobid);
419    mp_tableWidget->addAction(actionListFilesOnJob);
420    mp_tableWidget->addAction(actionListJobMedia);
421    mp_tableWidget->addAction(actionListVolumes);
422    mp_tableWidget->addAction(actionDeleteJob);
423    mp_tableWidget->addAction(actionPurgeFiles);
424    mp_tableWidget->addAction(actionRestoreFromJob);
425    mp_tableWidget->addAction(actionRestoreFromTime);
426    mp_tableWidget->addAction(actionShowLogForJob);
427
428    /* Make Connections */
429    connect(actionListJobid, SIGNAL(triggered()), this,
430                 SLOT(consoleListJobid()));
431    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
432                 SLOT(consoleListFilesOnJob()));
433    connect(actionListJobMedia, SIGNAL(triggered()), this,
434                 SLOT(consoleListJobMedia()));
435    connect(actionListVolumes, SIGNAL(triggered()), this,
436                 SLOT(consoleListVolumes()));
437    connect(actionDeleteJob, SIGNAL(triggered()), this,
438                 SLOT(consoleDeleteJob()));
439    connect(actionPurgeFiles, SIGNAL(triggered()), this,
440                 SLOT(consolePurgeFiles()));
441    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
442                 SLOT(preRestoreFromJob()));
443    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
444                 SLOT(preRestoreFromTime()));
445    connect(actionShowLogForJob, SIGNAL(triggered()), this,
446                 SLOT(showLogForJob()));
447    connect(actionCancelJob, SIGNAL(triggered()), this,
448                 SLOT(consoleCancelJob()));
449    connect(actionListJobTotals, SIGNAL(triggered()), this,
450                 SLOT(consoleListJobTotals()));
451
452    m_contextActions.append(actionRefreshJobList);
453    m_contextActions.append(actionListJobTotals);
454 }
455
456 /*
457  * Functions to respond to local context sensitive menu sending console commands
458  * If I could figure out how to make these one function passing a string, Yaaaaaa
459  */
460 void JobList::consoleListJobid()
461 {
462    QString cmd("list jobid=");
463    cmd += m_currentJob;
464    if (mainWin->m_longList) { cmd.prepend("l"); }
465    consoleCommand(cmd);
466 }
467 void JobList::consoleListFilesOnJob()
468 {
469    QString cmd("list files jobid=");
470    cmd += m_currentJob;
471    if (mainWin->m_longList) { cmd.prepend("l"); }
472    consoleCommand(cmd);
473 }
474 void JobList::consoleListJobMedia()
475 {
476    QString cmd("list jobmedia jobid=");
477    cmd += m_currentJob;
478    if (mainWin->m_longList) { cmd.prepend("l"); }
479    consoleCommand(cmd);
480 }
481 void JobList::consoleListVolumes()
482 {
483    QString cmd("list volumes jobid=");
484    cmd += m_currentJob;
485    if (mainWin->m_longList) { cmd.prepend("l"); }
486    consoleCommand(cmd);
487 }
488 void JobList::consoleListJobTotals()
489 {
490    QString cmd("list jobtotals");
491    if (mainWin->m_longList) { cmd.prepend("l"); }
492    consoleCommand(cmd);
493 }
494 void JobList::consoleDeleteJob()
495 {
496    if (QMessageBox::warning(this, "Bat",
497       tr("Are you sure you want to delete??  !!!.\n"
498 "This delete command is used to delete a Job record and all associated catalog"
499 " records that were created. This command operates only on the Catalog"
500 " database and has no effect on the actual data written to a Volume. This"
501 " command can be dangerous and we strongly recommend that you do not use"
502 " it unless you know what you are doing.  The Job and all its associated"
503 " records (File and JobMedia) will be deleted from the catalog."
504       "Press OK to proceed with delete operation.?"),
505       QMessageBox::Ok | QMessageBox::Cancel)
506       == QMessageBox::Cancel) { return; }
507
508    QString cmd("delete job jobid=");
509    cmd += m_selectedJobs;
510    consoleCommand(cmd);
511 }
512 void JobList::consolePurgeFiles()
513 {
514    if (QMessageBox::warning(this, "Bat",
515       tr("Are you sure you want to purge ??  !!!.\n"
516 "The Purge command will delete associated Catalog database records from Jobs and"
517 " Volumes without considering the retention period. Purge  works only on the"
518 " Catalog database and does not affect data written to Volumes. This command can"
519 " be dangerous because you can delete catalog records associated with current"
520 " backups of files, and we recommend that you do not use it unless you know what"
521 " you are doing.\n"
522       "Press OK to proceed with the purge operation?"),
523       QMessageBox::Ok | QMessageBox::Cancel)
524       == QMessageBox::Cancel) { return; }
525
526    QString cmd("purge files jobid=");
527    cmd += m_currentJob;
528    consoleCommand(cmd);
529 }
530
531 /*
532  * Subroutine to call preRestore to restore from a select job
533  */
534 void JobList::preRestoreFromJob()
535 {
536    new prerestorePage(m_currentJob, R_JOBIDLIST);
537 }
538
539 /*
540  * Subroutine to call preRestore to restore from a select job
541  */
542 void JobList::preRestoreFromTime()
543 {
544    new prerestorePage(m_currentJob, R_JOBDATETIME);
545 }
546
547 /*
548  * Subroutine to call class to show the log in the database from that job
549  */
550 void JobList::showLogForJob()
551 {
552    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
553    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
554 }
555
556 /*
557  * Cancel a running job
558  */
559 void JobList::consoleCancelJob()
560 {
561    QString cmd("cancel jobid=");
562    cmd += m_currentJob;
563    consoleCommand(cmd);
564 }
565
566 /*
567  * Graph this table
568  */
569 void JobList::graphTable()
570 {
571 #ifdef HAVE_QWT
572    JobPlotPass pass;
573    pass.recordLimitCheck = limitCheckBox->checkState();
574    pass.daysLimitCheck = daysCheckBox->checkState();
575    pass.recordLimitSpin = limitSpinBox->value();
576    pass.daysLimitSpin = daysSpinBox->value();
577    pass.jobCombo = jobComboBox->currentText();
578    pass.clientCombo = clientComboBox->currentText();
579    pass.volumeCombo = volumeComboBox->currentText();
580    pass.fileSetCombo = fileSetComboBox->currentText();
581    pass.purgedCombo = purgedComboBox->currentText();
582    pass.levelCombo = levelComboBox->currentText();
583    pass.statusCombo = statusComboBox->currentText();
584    pass.use = true;
585    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
586    new JobPlot(pageSelectorTreeWidgetItem, pass);
587 #endif
588 }
589
590 /*
591  * Save user settings associated with this page
592  */
593 void JobList::writeSettings()
594 {
595    QSettings settings(m_console->m_dir->name(), "bat");
596    settings.beginGroup(m_groupText);
597    settings.setValue(m_splitText, m_splitter->saveState());
598    settings.endGroup();
599 }
600
601 /*
602  * Read and restore user settings associated with this page
603  */
604 void JobList::readSettings()
605 {
606    m_groupText = "JobListPage";
607    m_splitText = "splitterSizes_1";
608    QSettings settings(m_console->m_dir->name(), "bat");
609    settings.beginGroup(m_groupText);
610    m_splitter->restoreState(settings.value(m_splitText).toByteArray());
611    settings.endGroup();
612 }
613
614 /*
615  * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
616  */
617 void JobList::selectedJobsGet()
618 {
619    QList<int> rowList;
620    QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
621    foreach (QTableWidgetItem *sitem, sitems) {
622       int row = sitem->row();
623       if (!rowList.contains(row)) {
624          rowList.append(row);
625       }
626    }
627
628    m_selectedJobs = "";
629    bool first = true;
630    foreach(int row, rowList) {
631       QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
632       if (!first) m_selectedJobs.append(",");
633       else first = false;
634       m_selectedJobs.append(sitem->text());
635    }
636    m_selectedJobsCount = rowList.count();
637    if (m_selectedJobsCount > 1) {
638      QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
639        actionDeleteJob->setText(text);
640    } else {
641      actionDeleteJob->setText(tr("Delete Single Job"));
642    }
643 }