]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
The "DISTINCT" keyword seems to be required if the joins for jobmedia
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2007 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 John Walker.
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 #include "jobgraphs/jobplot.h"
41
42 /*
43  * Constructor for the class
44  */
45 JobList::JobList(const QString &mediaName, const QString &clientName,
46           const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
47 {
48    setupUi(this);
49    m_name = ""; /* treeWidgetName has a virtual override in this class */
50    m_mediaName = mediaName;
51    m_clientName = clientName;
52    m_jobName = jobName;
53    m_filesetName = filesetName;
54    m_filesetName = filesetName;
55    pgInitialize(parentTreeWidgetItem);
56    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
57    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
58
59    m_resultCount = 0;
60    m_populated = false;
61    m_closeable = false;
62    if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
63       m_closeable=true;
64    m_checkCurrentWidget = true;
65    createConnections();
66
67    /* Set Defaults for check and spin for limits */
68    limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
69    limitSpinBox->setValue(mainWin->m_recordLimitVal);
70    daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
71    daysSpinBox->setValue(mainWin->m_daysLimitVal);
72    dockPage();
73
74    QGridLayout *gridLayout = new QGridLayout(this);
75    gridLayout->setSpacing(6);
76    gridLayout->setMargin(9);
77    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
78
79    m_splitter = new QSplitter(Qt::Vertical, this);
80    QScrollArea *area = new QScrollArea();
81    area->setObjectName(QString::fromUtf8("area"));
82    area->setWidget(frame);
83    area->setWidgetResizable(true);
84    m_splitter->addWidget(mp_tableWidget);
85    m_splitter->addWidget(area);
86
87    gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
88    readSettings();
89 }
90
91 /*
92  * Write the m_splitter settings in the destructor
93  */
94 JobList::~JobList()
95 {
96    writeSettings();
97 }
98
99 /*
100  * The Meat of the class.
101  * This function will populate the QTableWidget, mp_tablewidget, with
102  * QTableWidgetItems representing the results of a query for what jobs exist on
103  * the media name passed from the constructor stored in m_mediaName.
104  */
105 void JobList::populateTable()
106 {
107    QStringList results;
108    QString resultline;
109    QBrush blackBrush(Qt::black);
110
111    if (!m_console->preventInUseConnect())
112        return;
113
114    /* Can't do this in constructor because not neccesarily conected in constructor */
115    if (!m_populated) {
116       clientComboBox->addItem("Any");
117       clientComboBox->addItems(m_console->client_list);
118       int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
119       if (clientIndex != -1)
120          clientComboBox->setCurrentIndex(clientIndex);
121
122       QStringList volumeList;
123       m_console->getVolumeList(volumeList);
124       volumeComboBox->addItem("Any");
125       volumeComboBox->addItems(volumeList);
126       int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
127       if (volumeIndex != -1) {
128          volumeComboBox->setCurrentIndex(volumeIndex);
129       }
130       jobComboBox->addItem("Any");
131       jobComboBox->addItems(m_console->job_list);
132       int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
133       if (jobIndex != -1) {
134          jobComboBox->setCurrentIndex(jobIndex);
135       }
136       levelComboBox->addItem("Any");
137       levelComboBox->addItems( QStringList() << "F" << "D" << "I");
138       purgedComboBox->addItem("Any");
139       purgedComboBox->addItems( QStringList() << "0" << "1");
140       fileSetComboBox->addItem("Any");
141       fileSetComboBox->addItems(m_console->fileset_list);
142       int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
143       if (filesetIndex != -1) {
144          fileSetComboBox->setCurrentIndex(filesetIndex);
145       }
146       QStringList statusLongList;
147       m_console->getStatusList(statusLongList);
148       statusComboBox->addItem("Any");
149       statusComboBox->addItems(statusLongList);
150    }
151
152    /* Set up query */
153    QString query("");
154    int volumeIndex = volumeComboBox->currentIndex();
155    if (volumeIndex != -1)
156       m_mediaName = volumeComboBox->itemText(volumeIndex);
157    QString distinct = "";
158    if (m_mediaName != "Any") { distinct = "DISTINCT "; }
159    query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, " 
160             " Client.Name AS Client,"
161             " Job.Starttime AS JobStart, Job.Type AS JobType,"
162             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
163             " Job.JobBytes AS Bytes,"
164             " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
165             " Job.PurgedFiles AS Purged, FileSet.FileSet"
166             " FROM Job"
167             " JOIN Client ON (Client.ClientId=Job.ClientId)"
168             " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
169             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
170    QStringList conditions;
171    if (m_mediaName != "Any") {
172       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
173                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
174       conditions.append("Media.VolumeName='" + m_mediaName + "'");
175    }
176    int clientIndex = clientComboBox->currentIndex();
177    if (clientIndex != -1)
178       m_clientName = clientComboBox->itemText(clientIndex);
179    if (m_clientName != "Any") {
180       conditions.append("Client.Name='" + m_clientName + "'");
181    }
182    int jobIndex = jobComboBox->currentIndex();
183    if (jobIndex != -1)
184       m_jobName = jobComboBox->itemText(jobIndex);
185    if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
186       conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
187    }
188    int levelIndex = levelComboBox->currentIndex();
189    if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
190       conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
191    }
192    int statusIndex = statusComboBox->currentIndex();
193    if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
194       conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
195    }
196    int purgedIndex = purgedComboBox->currentIndex();
197    if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
198       conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
199    }
200    int fileSetIndex = fileSetComboBox->currentIndex();
201    if (fileSetIndex != -1)
202       m_filesetName = fileSetComboBox->itemText(fileSetIndex);
203    if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
204       conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
205    }
206    /* If Limit check box For limit by days is checked  */
207    if (daysCheckBox->checkState() == Qt::Checked) {
208       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
209       QString since = stamp.toString(Qt::ISODate);
210       conditions.append("Job.Starttime>'" + since + "'");
211    }
212    bool first = true;
213    foreach (QString condition, conditions) {
214       if (first) {
215          query += " WHERE " + condition;
216          first = false;
217       } else {
218          query += " AND " + condition;
219       }
220    }
221    /* Descending */
222    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
223    /* If Limit check box for limit records returned is checked  */
224    if (limitCheckBox->checkState() == Qt::Checked) {
225       QString limit;
226       limit.setNum(limitSpinBox->value());
227       query += " LIMIT " + limit;
228    }
229
230    /* Set up the Header for the table */
231    QStringList headerlist = (QStringList()
232       << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
233       << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
234    m_purgedIndex = headerlist.indexOf("Purged");
235    m_typeIndex = headerlist.indexOf("Job Type");
236    m_statusIndex = headerlist.indexOf("Job Status");
237    m_startIndex = headerlist.indexOf("Job Starttime");
238    m_filesIndex = headerlist.indexOf("Job Files");
239    m_bytesIndex = headerlist.indexOf("Job Bytes");
240
241    /* Initialize the QTableWidget */
242    m_checkCurrentWidget = false;
243    mp_tableWidget->clear();
244    m_checkCurrentWidget = true;
245    mp_tableWidget->setColumnCount(headerlist.size());
246    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
247
248    if (mainWin->m_sqlDebug) {
249       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
250    }
251    if (m_console->sql_cmd(query, results)) {
252       m_resultCount = results.count();
253
254       QTableWidgetItem* p_tableitem;
255       QString field;
256       QStringList fieldlist;
257       mp_tableWidget->setRowCount(results.size());
258
259       int row = 0;
260       /* Iterate through the record returned from the query */
261       foreach (resultline, results) {
262          fieldlist = resultline.split("\t");
263          int column = 0;
264          bool m_statusIndexDone = false;
265          QString statusCode("");
266          /* Iterate through fields in the record */
267          foreach (field, fieldlist) {
268             field = field.trimmed();  /* strip leading & trailing spaces */
269             if ((column == m_statusIndex) && (!m_statusIndexDone)){
270                m_statusIndexDone = true;
271                statusCode = field;
272             } else {
273                p_tableitem = new QTableWidgetItem(field,1);
274                p_tableitem->setFlags(0);
275                p_tableitem->setForeground(blackBrush);
276                mp_tableWidget->setItem(row, column, p_tableitem);
277                if (column == m_statusIndex)
278                   setStatusColor(p_tableitem, statusCode);
279                column++;
280             }
281          }
282          row++;
283       }
284    } 
285    /* Resize the columns */
286    mp_tableWidget->resizeColumnsToContents();
287    mp_tableWidget->resizeRowsToContents();
288    mp_tableWidget->verticalHeader()->hide();
289    if ((m_mediaName != "Any") && (m_resultCount == 0)){
290       /* for context sensitive searches, let the user know if there were no
291        * results */
292       QMessageBox::warning(this, tr("Bat"),
293           tr("The Jobs query returned no results.\n"
294          "Press OK to continue?"), QMessageBox::Ok );
295    }
296 }
297
298 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
299 {
300    QString greenchars("TCR");
301    QString redchars("BEf");
302    QString yellowchars("eDAFSMmsjdctp");
303    if (greenchars.contains(field, Qt::CaseSensitive)) {
304       item->setBackground(Qt::green);
305    } else if (redchars.contains(field, Qt::CaseSensitive)) {
306       item->setBackground(Qt::red);
307    } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
308       item->setBackground(Qt::yellow);
309    }
310 }
311
312 /*
313  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
314  * The tree has been populated.
315  */
316 void JobList::PgSeltreeWidgetClicked()
317 {
318    if (!m_populated) {
319       populateTable();
320       m_populated=true;
321    }
322 }
323
324 /*
325  *  Virtual function override of pages function which is called when this page
326  *  is visible on the stack
327  */
328 void JobList::currentStackItem()
329 {
330    populateTable();
331    if (!m_populated) {
332       m_populated=true;
333    }
334 }
335
336 /*
337  * Virtual Function to return the name for the medialist tree widget
338  */
339 void JobList::treeWidgetName(QString &desc)
340 {
341    if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
342       desc = "JobList";
343    } else {
344       desc = "JobList ";
345       if (m_mediaName != "" ) {
346          desc += "of Volume " + m_mediaName;
347       }
348       if (m_clientName != "" ) {
349          desc += "of Client " + m_clientName;
350       }
351       if (m_jobName != "" ) {
352          desc += "of Job " + m_jobName;
353       }
354       if (m_filesetName != "" ) {
355          desc += "of fileset " + m_filesetName;
356       }
357    }
358 }
359
360 /*
361  * This functions much line tableItemChanged for trees like the page selector,
362  * but I will do much less here
363  */
364 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
365 {
366    if (m_checkCurrentWidget) {
367       int row = currentItem->row();
368       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
369       m_currentJob = jobitem->text();
370
371       /* include purged action or not */
372       jobitem = mp_tableWidget->item(row, m_purgedIndex);
373       QString purged = jobitem->text();
374       mp_tableWidget->removeAction(actionPurgeFiles);
375       if (purged == "0") {
376          mp_tableWidget->addAction(actionPurgeFiles);
377       }
378       /* include restore from time and job action or not */
379       jobitem = mp_tableWidget->item(row, m_typeIndex);
380       QString type = jobitem->text();
381       mp_tableWidget->removeAction(actionRestoreFromJob);
382       mp_tableWidget->removeAction(actionRestoreFromTime);
383       if (type == "B") {
384          mp_tableWidget->addAction(actionRestoreFromJob);
385          mp_tableWidget->addAction(actionRestoreFromTime);
386       }
387       /* include cancel action or not */
388       jobitem = mp_tableWidget->item(row, m_statusIndex);
389       QString status = jobitem->text();
390       mp_tableWidget->removeAction(actionCancelJob);
391       if (status == "Running") {
392          mp_tableWidget->addAction(actionCancelJob);
393       }
394    }
395 }
396
397 /*
398  * Function to create connections for context sensitive menu for this and
399  * the page selector
400  */
401 void JobList::createConnections()
402 {
403    /* connect to the action specific to this pages class that shows up in the 
404     * page selector tree */
405    connect(actionRefreshJobList, SIGNAL(triggered()), this,
406                 SLOT(populateTable()));
407    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
408    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
409    /* for the tableItemChanged to maintain m_currentJob */
410    connect(mp_tableWidget, SIGNAL(
411            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
412            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
413
414    /* Do what is required for the local context sensitive menu */
415
416
417    /* setContextMenuPolicy is required */
418    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
419
420    /* Add Actions */
421    mp_tableWidget->addAction(actionRefreshJobList);
422    mp_tableWidget->addAction(actionListJobid);
423    mp_tableWidget->addAction(actionListFilesOnJob);
424    mp_tableWidget->addAction(actionListJobMedia);
425    mp_tableWidget->addAction(actionListVolumes);
426    mp_tableWidget->addAction(actionDeleteJob);
427    mp_tableWidget->addAction(actionPurgeFiles);
428    mp_tableWidget->addAction(actionRestoreFromJob);
429    mp_tableWidget->addAction(actionRestoreFromTime);
430    mp_tableWidget->addAction(actionShowLogForJob);
431
432    /* Make Connections */
433    connect(actionListJobid, SIGNAL(triggered()), this,
434                 SLOT(consoleListJobid()));
435    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
436                 SLOT(consoleListFilesOnJob()));
437    connect(actionListJobMedia, SIGNAL(triggered()), this,
438                 SLOT(consoleListJobMedia()));
439    connect(actionListVolumes, SIGNAL(triggered()), this,
440                 SLOT(consoleListVolumes()));
441    connect(actionDeleteJob, SIGNAL(triggered()), this,
442                 SLOT(consoleDeleteJob()));
443    connect(actionPurgeFiles, SIGNAL(triggered()), this,
444                 SLOT(consolePurgeFiles()));
445    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
446                 SLOT(preRestoreFromJob()));
447    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
448                 SLOT(preRestoreFromTime()));
449    connect(actionShowLogForJob, SIGNAL(triggered()), this,
450                 SLOT(showLogForJob()));
451    connect(actionCancelJob, SIGNAL(triggered()), this,
452                 SLOT(consoleCancelJob()));
453    connect(actionListJobTotals, SIGNAL(triggered()), this,
454                 SLOT(consoleListJobTotals()));
455
456    m_contextActions.append(actionRefreshJobList);
457    m_contextActions.append(actionListJobTotals);
458 }
459
460 /*
461  * Functions to respond to local context sensitive menu sending console commands
462  * If I could figure out how to make these one function passing a string, Yaaaaaa
463  */
464 void JobList::consoleListJobid()
465 {
466    QString cmd("list jobid=");
467    cmd += m_currentJob;
468    if (mainWin->m_longList) { cmd.prepend("l"); }
469    consoleCommand(cmd);
470 }
471 void JobList::consoleListFilesOnJob()
472 {
473    QString cmd("list files jobid=");
474    cmd += m_currentJob;
475    if (mainWin->m_longList) { cmd.prepend("l"); }
476    consoleCommand(cmd);
477 }
478 void JobList::consoleListJobMedia()
479 {
480    QString cmd("list jobmedia jobid=");
481    cmd += m_currentJob;
482    if (mainWin->m_longList) { cmd.prepend("l"); }
483    consoleCommand(cmd);
484 }
485 void JobList::consoleListVolumes()
486 {
487    QString cmd("list volumes jobid=");
488    cmd += m_currentJob;
489    if (mainWin->m_longList) { cmd.prepend("l"); }
490    consoleCommand(cmd);
491 }
492 void JobList::consoleListJobTotals()
493 {
494    QString cmd("list jobtotals");
495    if (mainWin->m_longList) { cmd.prepend("l"); }
496    consoleCommand(cmd);
497 }
498 void JobList::consoleDeleteJob()
499 {
500    if (QMessageBox::warning(this, tr("Bat"),
501       tr("Are you sure you want to delete??  !!!.\n"
502 "This delete command is used to delete a Job record and all associated catalog"
503 " records that were created. This command operates only on the Catalog"
504 " database and has no effect on the actual data written to a Volume. This"
505 " command can be dangerous and we strongly recommend that you do not use"
506 " it unless you know what you are doing.  The Job and all its associated"
507 " records (File and JobMedia) will be deleted from the catalog."
508       "Press OK to proceed with delete operation.?"),
509       QMessageBox::Ok | QMessageBox::Cancel)
510       == QMessageBox::Cancel) { return; }
511
512    QString cmd("delete job jobid=");
513    cmd += m_currentJob;
514    consoleCommand(cmd);
515 }
516 void JobList::consolePurgeFiles()
517 {
518    if (QMessageBox::warning(this, tr("Bat"),
519       tr("Are you sure you want to purge ??  !!!.\n"
520 "The Purge command will delete associated Catalog database records from Jobs and"
521 " Volumes without considering the retention period. Purge  works only on the"
522 " Catalog database and does not affect data written to Volumes. This command can"
523 " be dangerous because you can delete catalog records associated with current"
524 " backups of files, and we recommend that you do not use it unless you know what"
525 " you are doing.\n"
526       "Press OK to proceed with the purge operation?"),
527       QMessageBox::Ok | QMessageBox::Cancel)
528       == QMessageBox::Cancel) { return; }
529
530    QString cmd("purge files jobid=");
531    cmd += m_currentJob;
532    consoleCommand(cmd);
533 }
534
535 /*
536  * Subroutine to call preRestore to restore from a select job
537  */
538 void JobList::preRestoreFromJob()
539 {
540    new prerestorePage(m_currentJob, R_JOBIDLIST);
541 }
542
543 /*
544  * Subroutine to call preRestore to restore from a select job
545  */
546 void JobList::preRestoreFromTime()
547 {
548    new prerestorePage(m_currentJob, R_JOBDATETIME);
549 }
550
551 /*
552  * Subroutine to call class to show the log in the database from that job
553  */
554 void JobList::showLogForJob()
555 {
556    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
557    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
558 }
559
560 /*
561  * Cancel a running job
562  */
563 void JobList::consoleCancelJob()
564 {
565    QString cmd("cancel jobid=");
566    cmd += m_currentJob;
567    consoleCommand(cmd);
568 }
569
570 /*
571  * Graph this table
572  */
573 void JobList::graphTable()
574 {
575    JobPlotPass pass;
576    pass.recordLimitCheck = limitCheckBox->checkState();
577    pass.daysLimitCheck = daysCheckBox->checkState();
578    pass.recordLimitSpin = limitSpinBox->value();
579    pass.daysLimitSpin = daysSpinBox->value();
580    pass.jobCombo = jobComboBox->currentText();
581    pass.clientCombo = clientComboBox->currentText();
582    pass.volumeCombo = volumeComboBox->currentText();
583    pass.fileSetCombo = fileSetComboBox->currentText();
584    pass.purgedCombo = purgedComboBox->currentText();
585    pass.levelCombo = levelComboBox->currentText();
586    pass.statusCombo = statusComboBox->currentText();
587    pass.use = true;
588    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
589    new JobPlot(pageSelectorTreeWidgetItem, pass);
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 }