]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
This change of the regular expressions should fix any restoretree issues
[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    query += "SELECT Job.Jobid AS Id, Job.Name AS JobName, " 
158             " Client.Name AS Client,"
159             " Job.Starttime AS JobStart, Job.Type AS JobType,"
160             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
161             " Job.JobBytes AS Bytes,"
162             " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
163             " Job.PurgedFiles AS Purged, FileSet.FileSet"
164             " FROM Job"
165             " JOIN Client ON (Client.ClientId=Job.ClientId)"
166             " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
167             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
168    QStringList conditions;
169    if (m_mediaName != "Any") {
170       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
171                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
172       conditions.append("Media.VolumeName='" + m_mediaName + "'");
173    }
174    int clientIndex = clientComboBox->currentIndex();
175    if (clientIndex != -1)
176       m_clientName = clientComboBox->itemText(clientIndex);
177    if (m_clientName != "Any") {
178       conditions.append("Client.Name='" + m_clientName + "'");
179    }
180    int jobIndex = jobComboBox->currentIndex();
181    if (jobIndex != -1)
182       m_jobName = jobComboBox->itemText(jobIndex);
183    if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
184       conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
185    }
186    int levelIndex = levelComboBox->currentIndex();
187    if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
188       conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
189    }
190    int statusIndex = statusComboBox->currentIndex();
191    if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
192       conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
193    }
194    int purgedIndex = purgedComboBox->currentIndex();
195    if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
196       conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
197    }
198    int fileSetIndex = fileSetComboBox->currentIndex();
199    if (fileSetIndex != -1)
200       m_filesetName = fileSetComboBox->itemText(fileSetIndex);
201    if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
202       conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
203    }
204    /* If Limit check box For limit by days is checked  */
205    if (daysCheckBox->checkState() == Qt::Checked) {
206       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
207       QString since = stamp.toString(Qt::ISODate);
208       conditions.append("Job.Starttime>'" + since + "'");
209    }
210    bool first = true;
211    foreach (QString condition, conditions) {
212       if (first) {
213          query += " WHERE " + condition;
214          first = false;
215       } else {
216          query += " AND " + condition;
217       }
218    }
219    /* Descending */
220    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
221    /* If Limit check box for limit records returned is checked  */
222    if (limitCheckBox->checkState() == Qt::Checked) {
223       QString limit;
224       limit.setNum(limitSpinBox->value());
225       query += " LIMIT " + limit;
226    }
227
228    /* Set up the Header for the table */
229    QStringList headerlist = (QStringList()
230       << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
231       << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
232    m_purgedIndex = headerlist.indexOf("Purged");
233    m_typeIndex = headerlist.indexOf("Job Type");
234    m_statusIndex = headerlist.indexOf("Job Status");
235    m_startIndex = headerlist.indexOf("Job Starttime");
236    m_filesIndex = headerlist.indexOf("Job Files");
237    m_bytesIndex = headerlist.indexOf("Job Bytes");
238
239    /* Initialize the QTableWidget */
240    m_checkCurrentWidget = false;
241    mp_tableWidget->clear();
242    m_checkCurrentWidget = true;
243    mp_tableWidget->setColumnCount(headerlist.size());
244    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
245
246    if (mainWin->m_sqlDebug) {
247       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
248    }
249    if (m_console->sql_cmd(query, results)) {
250       m_resultCount = results.count();
251
252       QTableWidgetItem* p_tableitem;
253       QString field;
254       QStringList fieldlist;
255       mp_tableWidget->setRowCount(results.size());
256
257       int row = 0;
258       /* Iterate through the record returned from the query */
259       foreach (resultline, results) {
260          fieldlist = resultline.split("\t");
261          int column = 0;
262          bool m_statusIndexDone = false;
263          QString statusCode("");
264          /* Iterate through fields in the record */
265          foreach (field, fieldlist) {
266             field = field.trimmed();  /* strip leading & trailing spaces */
267             if ((column == m_statusIndex) && (!m_statusIndexDone)){
268                m_statusIndexDone = true;
269                statusCode = field;
270             } else {
271                p_tableitem = new QTableWidgetItem(field,1);
272                p_tableitem->setFlags(0);
273                p_tableitem->setForeground(blackBrush);
274                mp_tableWidget->setItem(row, column, p_tableitem);
275                if (column == m_statusIndex)
276                   setStatusColor(p_tableitem, statusCode);
277                column++;
278             }
279          }
280          row++;
281       }
282    } 
283    /* Resize the columns */
284    mp_tableWidget->resizeColumnsToContents();
285    mp_tableWidget->resizeRowsToContents();
286    mp_tableWidget->verticalHeader()->hide();
287    if ((m_mediaName != "Any") && (m_resultCount == 0)){
288       /* for context sensitive searches, let the user know if there were no
289        * results */
290       QMessageBox::warning(this, tr("Bat"),
291           tr("The Jobs query returned no results.\n"
292          "Press OK to continue?"), QMessageBox::Ok );
293    }
294 }
295
296 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
297 {
298    QString greenchars("TCR");
299    QString redchars("BEf");
300    QString yellowchars("eDAFSMmsjdctp");
301    if (greenchars.contains(field, Qt::CaseSensitive)) {
302       item->setBackground(Qt::green);
303    } else if (redchars.contains(field, Qt::CaseSensitive)) {
304       item->setBackground(Qt::red);
305    } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
306       item->setBackground(Qt::yellow);
307    }
308 }
309
310 /*
311  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
312  * The tree has been populated.
313  */
314 void JobList::PgSeltreeWidgetClicked()
315 {
316    if (!m_populated) {
317       populateTable();
318       m_populated=true;
319    }
320 }
321
322 /*
323  *  Virtual function override of pages function which is called when this page
324  *  is visible on the stack
325  */
326 void JobList::currentStackItem()
327 {
328    populateTable();
329    if (!m_populated) {
330       m_populated=true;
331    }
332 }
333
334 /*
335  * Virtual Function to return the name for the medialist tree widget
336  */
337 void JobList::treeWidgetName(QString &desc)
338 {
339    if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
340       desc = "JobList";
341    } else {
342       desc = "JobList ";
343       if (m_mediaName != "" ) {
344          desc += "of Volume " + m_mediaName;
345       }
346       if (m_clientName != "" ) {
347          desc += "of Client " + m_clientName;
348       }
349       if (m_jobName != "" ) {
350          desc += "of Job " + m_jobName;
351       }
352       if (m_filesetName != "" ) {
353          desc += "of fileset " + m_filesetName;
354       }
355    }
356 }
357
358 /*
359  * This functions much line tableItemChanged for trees like the page selector,
360  * but I will do much less here
361  */
362 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
363 {
364    if (m_checkCurrentWidget) {
365       int row = currentItem->row();
366       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
367       m_currentJob = jobitem->text();
368
369       /* include purged action or not */
370       jobitem = mp_tableWidget->item(row, m_purgedIndex);
371       QString purged = jobitem->text();
372       mp_tableWidget->removeAction(actionPurgeFiles);
373       if (purged == "0") {
374          mp_tableWidget->addAction(actionPurgeFiles);
375       }
376       /* include restore from time and job action or not */
377       jobitem = mp_tableWidget->item(row, m_typeIndex);
378       QString type = jobitem->text();
379       mp_tableWidget->removeAction(actionRestoreFromJob);
380       mp_tableWidget->removeAction(actionRestoreFromTime);
381       if (type == "B") {
382          mp_tableWidget->addAction(actionRestoreFromJob);
383          mp_tableWidget->addAction(actionRestoreFromTime);
384       }
385       /* include cancel action or not */
386       jobitem = mp_tableWidget->item(row, m_statusIndex);
387       QString status = jobitem->text();
388       mp_tableWidget->removeAction(actionCancelJob);
389       if (status == "Running") {
390          mp_tableWidget->addAction(actionCancelJob);
391       }
392    }
393 }
394
395 /*
396  * Function to create connections for context sensitive menu for this and
397  * the page selector
398  */
399 void JobList::createConnections()
400 {
401    /* connect to the action specific to this pages class that shows up in the 
402     * page selector tree */
403    connect(actionRefreshJobList, SIGNAL(triggered()), this,
404                 SLOT(populateTable()));
405    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
406    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
407    /* for the tableItemChanged to maintain m_currentJob */
408    connect(mp_tableWidget, SIGNAL(
409            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
410            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
411
412    /* Do what is required for the local context sensitive menu */
413
414
415    /* setContextMenuPolicy is required */
416    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
417
418    /* Add Actions */
419    mp_tableWidget->addAction(actionRefreshJobList);
420    mp_tableWidget->addAction(actionListJobid);
421    mp_tableWidget->addAction(actionListFilesOnJob);
422    mp_tableWidget->addAction(actionListJobMedia);
423    mp_tableWidget->addAction(actionListVolumes);
424    mp_tableWidget->addAction(actionDeleteJob);
425    mp_tableWidget->addAction(actionPurgeFiles);
426    mp_tableWidget->addAction(actionRestoreFromJob);
427    mp_tableWidget->addAction(actionRestoreFromTime);
428    mp_tableWidget->addAction(actionShowLogForJob);
429
430    /* Make Connections */
431    connect(actionListJobid, SIGNAL(triggered()), this,
432                 SLOT(consoleListJobid()));
433    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
434                 SLOT(consoleListFilesOnJob()));
435    connect(actionListJobMedia, SIGNAL(triggered()), this,
436                 SLOT(consoleListJobMedia()));
437    connect(actionListVolumes, SIGNAL(triggered()), this,
438                 SLOT(consoleListVolumes()));
439    connect(actionDeleteJob, SIGNAL(triggered()), this,
440                 SLOT(consoleDeleteJob()));
441    connect(actionPurgeFiles, SIGNAL(triggered()), this,
442                 SLOT(consolePurgeFiles()));
443    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
444                 SLOT(preRestoreFromJob()));
445    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
446                 SLOT(preRestoreFromTime()));
447    connect(actionShowLogForJob, SIGNAL(triggered()), this,
448                 SLOT(showLogForJob()));
449    connect(actionCancelJob, SIGNAL(triggered()), this,
450                 SLOT(consoleCancelJob()));
451    connect(actionListJobTotals, SIGNAL(triggered()), this,
452                 SLOT(consoleListJobTotals()));
453
454    m_contextActions.append(actionRefreshJobList);
455    m_contextActions.append(actionListJobTotals);
456 }
457
458 /*
459  * Functions to respond to local context sensitive menu sending console commands
460  * If I could figure out how to make these one function passing a string, Yaaaaaa
461  */
462 void JobList::consoleListJobid()
463 {
464    QString cmd("list jobid=");
465    cmd += m_currentJob;
466    if (mainWin->m_longList) { cmd.prepend("l"); }
467    consoleCommand(cmd);
468 }
469 void JobList::consoleListFilesOnJob()
470 {
471    QString cmd("list files jobid=");
472    cmd += m_currentJob;
473    if (mainWin->m_longList) { cmd.prepend("l"); }
474    consoleCommand(cmd);
475 }
476 void JobList::consoleListJobMedia()
477 {
478    QString cmd("list jobmedia jobid=");
479    cmd += m_currentJob;
480    if (mainWin->m_longList) { cmd.prepend("l"); }
481    consoleCommand(cmd);
482 }
483 void JobList::consoleListVolumes()
484 {
485    QString cmd("list volumes jobid=");
486    cmd += m_currentJob;
487    if (mainWin->m_longList) { cmd.prepend("l"); }
488    consoleCommand(cmd);
489 }
490 void JobList::consoleListJobTotals()
491 {
492    QString cmd("list jobtotals");
493    cmd += m_currentJob;
494    if (mainWin->m_longList) { cmd.prepend("l"); }
495    consoleCommand(cmd);
496 }
497 void JobList::consoleDeleteJob()
498 {
499    if (QMessageBox::warning(this, tr("Bat"),
500       tr("Are you sure you want to delete??  !!!.\n"
501 "This delete command is used to delete a Job record and all associated catalog"
502 " records that were created. This command operates only on the Catalog"
503 " database and has no effect on the actual data written to a Volume. This"
504 " command can be dangerous and we strongly recommend that you do not use"
505 " it unless you know what you are doing.  The Job and all its associated"
506 " records (File and JobMedia) will be deleted from the catalog."
507       "Press OK to proceed with delete operation.?"),
508       QMessageBox::Ok | QMessageBox::Cancel)
509       == QMessageBox::Cancel) { return; }
510
511    QString cmd("delete job jobid=");
512    cmd += m_currentJob;
513    consoleCommand(cmd);
514 }
515 void JobList::consolePurgeFiles()
516 {
517    if (QMessageBox::warning(this, tr("Bat"),
518       tr("Are you sure you want to purge ??  !!!.\n"
519 "The Purge command will delete associated Catalog database records from Jobs and"
520 " Volumes without considering the retention period. Purge  works only on the"
521 " Catalog database and does not affect data written to Volumes. This command can"
522 " be dangerous because you can delete catalog records associated with current"
523 " backups of files, and we recommend that you do not use it unless you know what"
524 " you are doing.\n"
525       "Press OK to proceed with the purge operation?"),
526       QMessageBox::Ok | QMessageBox::Cancel)
527       == QMessageBox::Cancel) { return; }
528
529    QString cmd("purge files jobid=");
530    cmd += m_currentJob;
531    consoleCommand(cmd);
532 }
533
534 /*
535  * Subroutine to call preRestore to restore from a select job
536  */
537 void JobList::preRestoreFromJob()
538 {
539    new prerestorePage(m_currentJob, R_JOBIDLIST);
540 }
541
542 /*
543  * Subroutine to call preRestore to restore from a select job
544  */
545 void JobList::preRestoreFromTime()
546 {
547    new prerestorePage(m_currentJob, R_JOBDATETIME);
548 }
549
550 /*
551  * Subroutine to call class to show the log in the database from that job
552  */
553 void JobList::showLogForJob()
554 {
555    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
556    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
557 }
558
559 /*
560  * Cancel a running job
561  */
562 void JobList::consoleCancelJob()
563 {
564    QString cmd("cancel jobid=");
565    cmd += m_currentJob;
566    consoleCommand(cmd);
567 }
568
569 /*
570  * Graph this table
571  */
572 void JobList::graphTable()
573 {
574    JobPlotPass pass;
575    pass.recordLimitCheck = limitCheckBox->checkState();
576    pass.daysLimitCheck = daysCheckBox->checkState();
577    pass.recordLimitSpin = limitSpinBox->value();
578    pass.daysLimitSpin = daysSpinBox->value();
579    pass.jobCombo = jobComboBox->currentText();
580    pass.clientCombo = clientComboBox->currentText();
581    pass.volumeCombo = volumeComboBox->currentText();
582    pass.fileSetCombo = fileSetComboBox->currentText();
583    pass.purgedCombo = purgedComboBox->currentText();
584    pass.levelCombo = levelComboBox->currentText();
585    pass.statusCombo = statusComboBox->currentText();
586    pass.use = true;
587    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
588    new JobPlot(pageSelectorTreeWidgetItem, pass);
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 }