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