]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/qt-console/joblist/joblist.cpp
First cut of bat rerun a Job from Jobs Run
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
index fcfe9025021a4432b8ba29fcb76bb48914b3df86..2cffc47ef170edfa342e8811ded318836f76dd04 100644 (file)
@@ -1,12 +1,12 @@
 /*
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2007-2007 Free Software Foundation Europe e.V.
+   Copyright (C) 2007-2009 Free Software Foundation Europe e.V.
 
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
+   modify it under the terms of version three of the GNU Affero General Public
    License as published by the Free Software Foundation and included
    in the file LICENSE.
 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
 
-   Bacula® is a registered trademark of John Walker.
+   Bacula® is a registered trademark of Kern Sibbald.
    The licensor of Bacula is the Free Software Foundation Europe
    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
    Switzerland, email:ftf@fsfeurope.org.
  *   Dirk Bartley, March 2007
  */
  
+#include "bat.h"
 #include <QAbstractEventDispatcher>
 #include <QTableWidgetItem>
-#include "bat.h"
 #include "joblist.h"
 #include "restore.h"
+#include "job/job.h"
 #include "joblog/joblog.h"
+#ifdef HAVE_QWT
 #include "jobgraphs/jobplot.h"
+#endif
+#include "util/fmtwidgetitem.h"
+#include "util/comboutil.h"
 
 /*
  * Constructor for the class
@@ -46,30 +51,28 @@ JobList::JobList(const QString &mediaName, const QString &clientName,
           const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
 {
    setupUi(this);
-   m_name = ""; /* treeWidgetName has a virtual override in this class */
+   m_name = "Jobs Run"; /* treeWidgetName has a virtual override in this class */
    m_mediaName = mediaName;
    m_clientName = clientName;
    m_jobName = jobName;
    m_filesetName = filesetName;
-   m_filesetName = filesetName;
-   pgInitialize(parentTreeWidgetItem);
+   pgInitialize("", parentTreeWidgetItem);
    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
 
    m_resultCount = 0;
    m_populated = false;
    m_closeable = false;
-   if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
+   if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != "")) {
       m_closeable=true;
+   }
    m_checkCurrentWidget = true;
-   createConnections();
 
    /* Set Defaults for check and spin for limits */
    limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
    limitSpinBox->setValue(mainWin->m_recordLimitVal);
    daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
    daysSpinBox->setValue(mainWin->m_daysLimitVal);
-   dockPage();
 
    QGridLayout *gridLayout = new QGridLayout(this);
    gridLayout->setSpacing(6);
@@ -81,11 +84,13 @@ JobList::JobList(const QString &mediaName, const QString &clientName,
    area->setObjectName(QString::fromUtf8("area"));
    area->setWidget(frame);
    area->setWidgetResizable(true);
-   m_splitter->addWidget(mp_tableWidget);
    m_splitter->addWidget(area);
+   m_splitter->addWidget(mp_tableWidget);
 
    gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
+   createConnections();
    readSettings();
+   if (m_closeable) { dockPage(); }
 }
 
 /*
@@ -104,136 +109,34 @@ JobList::~JobList()
  */
 void JobList::populateTable()
 {
-   QStringList results;
-   QString resultline;
-   QBrush blackBrush(Qt::black);
-
-   if (!m_console->preventInUseConnect())
-       return;
-
    /* Can't do this in constructor because not neccesarily conected in constructor */
-   if (!m_populated) {
-      clientComboBox->addItem("Any");
-      clientComboBox->addItems(m_console->client_list);
-      int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
-      if (clientIndex != -1)
-         clientComboBox->setCurrentIndex(clientIndex);
+   prepareFilterWidgets();
+   m_populated = true;
 
-      QStringList volumeList;
-      m_console->getVolumeList(volumeList);
-      volumeComboBox->addItem("Any");
-      volumeComboBox->addItems(volumeList);
-      int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
-      if (volumeIndex != -1) {
-         volumeComboBox->setCurrentIndex(volumeIndex);
-      }
-      jobComboBox->addItem("Any");
-      jobComboBox->addItems(m_console->job_list);
-      int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
-      if (jobIndex != -1) {
-         jobComboBox->setCurrentIndex(jobIndex);
-      }
-      levelComboBox->addItem("Any");
-      levelComboBox->addItems( QStringList() << "F" << "D" << "I");
-      purgedComboBox->addItem("Any");
-      purgedComboBox->addItems( QStringList() << "0" << "1");
-      fileSetComboBox->addItem("Any");
-      fileSetComboBox->addItems(m_console->fileset_list);
-      int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
-      if (filesetIndex != -1) {
-         fileSetComboBox->setCurrentIndex(filesetIndex);
-      }
-      QStringList statusLongList;
-      m_console->getStatusList(statusLongList);
-      statusComboBox->addItem("Any");
-      statusComboBox->addItems(statusLongList);
-   }
+   Freeze frz(*mp_tableWidget); /* disable updating*/
 
    /* Set up query */
-   QString query("");
-   int volumeIndex = volumeComboBox->currentIndex();
-   if (volumeIndex != -1)
-      m_mediaName = volumeComboBox->itemText(volumeIndex);
-   query += "SELECT DISTINCT Job.Jobid AS Id, Job.Name AS JobName, Client.Name AS Client,"
-            " Job.Starttime AS JobStart, Job.Type AS JobType,"
-            " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
-            " Job.JobBytes AS Bytes,"
-            " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
-            " Job.PurgedFiles AS Purged, FileSet.FileSet"
-            " FROM Job"
-            " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)"
-            " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)"
-            " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)"
-            " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
-            " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
-   QStringList conditions;
-   if (m_mediaName != "Any") {
-      conditions.append("Media.VolumeName='" + m_mediaName + "'");
-   }
-   int clientIndex = clientComboBox->currentIndex();
-   if (clientIndex != -1)
-      m_clientName = clientComboBox->itemText(clientIndex);
-   if (m_clientName != "Any") {
-      conditions.append("Client.Name='" + m_clientName + "'");
-   }
-   int jobIndex = jobComboBox->currentIndex();
-   if (jobIndex != -1)
-      m_jobName = jobComboBox->itemText(jobIndex);
-   if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
-      conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
-   }
-   int levelIndex = levelComboBox->currentIndex();
-   if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
-      conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
-   }
-   int statusIndex = statusComboBox->currentIndex();
-   if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
-      conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
-   }
-   int purgedIndex = purgedComboBox->currentIndex();
-   if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
-      conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
-   }
-   int fileSetIndex = fileSetComboBox->currentIndex();
-   if (fileSetIndex != -1)
-      m_filesetName = fileSetComboBox->itemText(fileSetIndex);
-   if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
-      conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
-   }
-   /* If Limit check box For limit by days is checked  */
-   if (daysCheckBox->checkState() == Qt::Checked) {
-      QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
-      QString since = stamp.toString(Qt::ISODate);
-      conditions.append("Job.Starttime>'" + since + "'");
-   }
-   bool first = true;
-   foreach (QString condition, conditions) {
-      if (first) {
-         query += " WHERE " + condition;
-         first = false;
-      } else {
-         query += " AND " + condition;
-      }
-   }
-   /* Descending */
-   query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
-   /* If Limit check box for limit records returned is checked  */
-   if (limitCheckBox->checkState() == Qt::Checked) {
-      QString limit;
-      limit.setNum(limitSpinBox->value());
-      query += " LIMIT " + limit;
-   }
+   QString query;
+   fillQueryString(query);
 
    /* Set up the Header for the table */
    QStringList headerlist = (QStringList()
-      << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
-      << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
-   m_purgedIndex = headerlist.indexOf("Purged");
-   m_typeIndex = headerlist.indexOf("Job Type");
-   m_statusIndex = headerlist.indexOf("Job Status");
-   m_startIndex = headerlist.indexOf("Job Starttime");
-   m_filesIndex = headerlist.indexOf("Job Files");
-   m_bytesIndex = headerlist.indexOf("Job Bytes");
+      << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime") 
+      << tr("Job Type") << tr("Job Level") << tr("Job Files") 
+      << tr("Job Bytes") << tr("Job Status")  << tr("Purged") << tr("File Set")
+      << tr("Pool Name") << tr("First Volume") << tr("VolCount"));
+
+   m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
+   m_purgedIndex = headerlist.indexOf(tr("Purged"));
+   m_typeIndex = headerlist.indexOf(tr("Job Type"));
+   m_statusIndex = headerlist.indexOf(tr("Job Status"));
+   m_startIndex = headerlist.indexOf(tr("Job Starttime"));
+   m_filesIndex = headerlist.indexOf(tr("Job Files"));
+   m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
+   m_levelIndex = headerlist.indexOf(tr("Job Level"));
+   m_nameIndex = headerlist.indexOf(tr("Job Name"));
+   m_filesetIndex = headerlist.indexOf(tr("File Set"));
+   m_clientIndex = headerlist.indexOf(tr("Client"));
 
    /* Initialize the QTableWidget */
    m_checkCurrentWidget = false;
@@ -241,68 +144,195 @@ void JobList::populateTable()
    m_checkCurrentWidget = true;
    mp_tableWidget->setColumnCount(headerlist.size());
    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
+   mp_tableWidget->horizontalHeader()->setHighlightSections(false);
+   mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
+   mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
 
    if (mainWin->m_sqlDebug) {
       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
    }
+
+   QStringList results;
    if (m_console->sql_cmd(query, results)) {
       m_resultCount = results.count();
 
-      QTableWidgetItem* p_tableitem;
-      QString field;
       QStringList fieldlist;
       mp_tableWidget->setRowCount(results.size());
 
       int row = 0;
       /* Iterate through the record returned from the query */
+      QString resultline;
       foreach (resultline, results) {
          fieldlist = resultline.split("\t");
-         int column = 0;
-         bool m_statusIndexDone = false;
-         QString statusCode("");
+         if (fieldlist.size() < 13)
+            continue; /* some fields missing, ignore row */
+
+         TableItemFormatter jobitem(*mp_tableWidget, row);
+  
          /* Iterate through fields in the record */
-         foreach (field, fieldlist) {
-            field = field.trimmed();  /* strip leading & trailing spaces */
-            if ((column == m_statusIndex) && (!m_statusIndexDone)){
-               m_statusIndexDone = true;
-               statusCode = field;
-            } else {
-               p_tableitem = new QTableWidgetItem(field,1);
-               p_tableitem->setFlags(0);
-               p_tableitem->setForeground(blackBrush);
-               mp_tableWidget->setItem(row, column, p_tableitem);
-               if (column == m_statusIndex)
-                  setStatusColor(p_tableitem, statusCode);
-               column++;
-            }
-         }
+         QStringListIterator fld(fieldlist);
+         int col = 0;
+
+         /* job id */
+         jobitem.setNumericFld(col++, fld.next());
+
+         /* job name */
+         jobitem.setTextFld(col++, fld.next());
+
+         /* client */
+         jobitem.setTextFld(col++, fld.next());
+
+         /* job starttime */
+         jobitem.setTextFld(col++, fld.next(), true);
+
+         /* job type */
+         jobitem.setJobTypeFld(col++, fld.next());
+
+         /* job level */
+         jobitem.setJobLevelFld(col++, fld.next());
+
+         /* job files */
+         jobitem.setNumericFld(col++, fld.next());
+
+         /* job bytes */
+         jobitem.setBytesFld(col++, fld.next());
+
+         /* job status */
+         jobitem.setJobStatusFld(col++, fld.next());
+
+         /* purged */
+         jobitem.setBoolFld(col++, fld.next());
+
+         /* fileset */
+         jobitem.setTextFld(col++, fld.next());
+
+         /* pool name */
+         jobitem.setTextFld(col++, fld.next());
+
+         /* First Media */
+         jobitem.setTextFld(col++, fld.next());
+
+         /* Medias count */
+         jobitem.setNumericFld(col++, fld.next());
          row++;
       }
    } 
+   /* set default sorting */
+   mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
+   mp_tableWidget->setSortingEnabled(true);
+   
    /* Resize the columns */
    mp_tableWidget->resizeColumnsToContents();
    mp_tableWidget->resizeRowsToContents();
    mp_tableWidget->verticalHeader()->hide();
-   if ((m_mediaName != "Any") && (m_resultCount == 0)){
+   if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
       /* for context sensitive searches, let the user know if there were no
        * results */
-      QMessageBox::warning(this, tr("Bat"),
+      QMessageBox::warning(this, "Bat",
           tr("The Jobs query returned no results.\n"
          "Press OK to continue?"), QMessageBox::Ok );
    }
+
+   /* make read only */
+   mp_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
 }
 
-void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
+void JobList::prepareFilterWidgets()
 {
-   QString greenchars("TCR");
-   QString redchars("BEf");
-   QString yellowchars("eDAFSMmsjdctp");
-   if (greenchars.contains(field, Qt::CaseSensitive)) {
-      item->setBackground(Qt::green);
-   } else if (redchars.contains(field, Qt::CaseSensitive)) {
-      item->setBackground(Qt::red);
-   } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
-      item->setBackground(Qt::yellow);
+   if (!m_populated) {
+      clientComboBox->addItem(tr("Any"));
+      clientComboBox->addItems(m_console->client_list);
+      comboSel(clientComboBox, m_clientName);
+
+      QStringList volumeList;
+      getVolumeList(volumeList);
+      volumeComboBox->addItem(tr("Any"));
+      volumeComboBox->addItems(volumeList);
+      comboSel(volumeComboBox, m_mediaName);
+
+      jobComboBox->addItem(tr("Any"));
+      jobComboBox->addItems(m_console->job_list);
+      comboSel(jobComboBox, m_jobName);
+
+      levelComboFill(levelComboBox);
+
+      boolComboFill(purgedComboBox);
+
+      fileSetComboBox->addItem(tr("Any"));
+      fileSetComboBox->addItems(m_console->fileset_list);
+      comboSel(fileSetComboBox, m_filesetName);
+
+      poolComboBox->addItem(tr("Any"));
+      poolComboBox->addItems(m_console->pool_list);
+
+      jobStatusComboFill(statusComboBox);
+   }
+}
+
+void JobList::fillQueryString(QString &query)
+{
+   query = "";
+   int volumeIndex = volumeComboBox->currentIndex();
+   if (volumeIndex != -1)
+      m_mediaName = volumeComboBox->itemText(volumeIndex);
+   QString distinct = "";
+   if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
+   query += "SELECT " + distinct + "Job.JobId AS JobId, Job.Name AS JobName, " 
+            " Client.Name AS Client,"
+            " Job.Starttime AS JobStart, Job.Type AS JobType,"
+            " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
+            " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
+            " Job.PurgedFiles AS Purged, FileSet.FileSet,"
+            " Pool.Name AS Pool,"
+            " (SELECT Media.VolumeName FROM JobMedia JOIN Media ON JobMedia.MediaId=Media.MediaId WHERE JobMedia.JobId=Job.JobId ORDER BY JobMediaId LIMIT 1) AS FirstVolume,"
+            " (SELECT count(DISTINCT MediaId) FROM JobMedia WHERE JobMedia.JobId=Job.JobId) AS Volumes"
+            " FROM Job"
+            " JOIN Client ON (Client.ClientId=Job.ClientId)"
+            " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) "
+            " LEFT OUTER JOIN Pool ON Job.PoolId = Pool.PoolId ";
+   QStringList conditions;
+   if (m_mediaName != tr("Any")) {
+      query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
+               " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
+      conditions.append("Media.VolumeName='" + m_mediaName + "'");
+   }
+
+   comboCond(conditions, clientComboBox, "Client.Name");
+   comboCond(conditions, jobComboBox, "Job.Name");
+   levelComboCond(conditions, levelComboBox, "Job.Level");
+   jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
+   boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
+   comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
+   comboCond(conditions, poolComboBox, "Pool.Name");
+
+   /* If Limit check box For limit by days is checked  */
+   if (daysCheckBox->checkState() == Qt::Checked) {
+      QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
+      QString since = stamp.toString(Qt::ISODate);
+      conditions.append("Job.Starttime > '" + since + "'");
+   }
+   if (filterCopyCheckBox->checkState() == Qt::Checked) {
+      conditions.append("Job.Type != 'c'" );
+   }
+   if (filterMigrationCheckBox->checkState() == Qt::Checked) {
+      conditions.append("Job.Type != 'g'" );
+   }
+   bool first = true;
+   foreach (QString condition, conditions) {
+      if (first) {
+         query += " WHERE " + condition;
+         first = false;
+      } else {
+         query += " AND " + condition;
+      }
+   }
+   /* Descending */
+   query += " ORDER BY Job.JobId DESC";
+   /* If Limit check box for limit records returned is checked  */
+   if (limitCheckBox->checkState() == Qt::Checked) {
+      QString limit;
+      limit.setNum(limitSpinBox->value());
+      query += " LIMIT " + limit;
    }
 }
 
@@ -314,7 +344,20 @@ void JobList::PgSeltreeWidgetClicked()
 {
    if (!m_populated) {
       populateTable();
-      m_populated=true;
+      /* Lets make sure the splitter is not all the way to size index 0 == 0 */
+      QList<int> sizes = m_splitter->sizes();
+      if (sizes[0] == 0) {
+         int frameMax = frame->maximumHeight();
+         int sizeSum = 0;
+         foreach(int size, sizes) { sizeSum += size; }
+         int tabHeight = mainWin->tabWidget->geometry().height();
+         sizes[0] = frameMax;
+         sizes[1] = tabHeight - frameMax;
+         m_splitter->setSizes(sizes);
+      }
+   }
+   if (!isOnceDocked()) {
+      dockPage();
    }
 }
 
@@ -324,10 +367,8 @@ void JobList::PgSeltreeWidgetClicked()
  */
 void JobList::currentStackItem()
 {
-   populateTable();
-   if (!m_populated) {
-      m_populated=true;
-   }
+/*   if (!m_populated) populate every time user comes back to this object */
+      populateTable();
 }
 
 /*
@@ -335,59 +376,16 @@ void JobList::currentStackItem()
  */
 void JobList::treeWidgetName(QString &desc)
 {
-   if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
-      desc = "JobList";
+   if (m_mediaName != "" ) {
+     desc = tr("Jobs Run on Volume %1").arg(m_mediaName);
+   } else if (m_clientName != "" ) {
+     desc = tr("Jobs Run from Client %1").arg(m_clientName);
+   } else if (m_jobName != "" ) {
+     desc = tr("Jobs Run of Job %1").arg(m_jobName);
+   } else if (m_filesetName != "" ) {
+     desc = tr("Jobs Run with fileset %1").arg(m_filesetName);
    } else {
-      desc = "JobList ";
-      if (m_mediaName != "" ) {
-         desc += "of Volume " + m_mediaName;
-      }
-      if (m_clientName != "" ) {
-         desc += "of Client " + m_clientName;
-      }
-      if (m_jobName != "" ) {
-         desc += "of Job " + m_jobName;
-      }
-      if (m_filesetName != "" ) {
-         desc += "of fileset " + m_filesetName;
-      }
-   }
-}
-
-/*
- * This functions much line tableItemChanged for trees like the page selector,
- * but I will do much less here
- */
-void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
-{
-   if (m_checkCurrentWidget) {
-      int row = currentItem->row();
-      QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
-      m_currentJob = jobitem->text();
-
-      /* include purged action or not */
-      jobitem = mp_tableWidget->item(row, m_purgedIndex);
-      QString purged = jobitem->text();
-      mp_tableWidget->removeAction(actionPurgeFiles);
-      if (purged == "0") {
-         mp_tableWidget->addAction(actionPurgeFiles);
-      }
-      /* include restore from time and job action or not */
-      jobitem = mp_tableWidget->item(row, m_typeIndex);
-      QString type = jobitem->text();
-      mp_tableWidget->removeAction(actionRestoreFromJob);
-      mp_tableWidget->removeAction(actionRestoreFromTime);
-      if (type == "B") {
-         mp_tableWidget->addAction(actionRestoreFromJob);
-         mp_tableWidget->addAction(actionRestoreFromTime);
-      }
-      /* include cancel action or not */
-      jobitem = mp_tableWidget->item(row, m_statusIndex);
-      QString status = jobitem->text();
-      mp_tableWidget->removeAction(actionCancelJob);
-      if (status == "Running") {
-         mp_tableWidget->addAction(actionCancelJob);
-      }
+     desc = tr("Jobs Run");
    }
 }
 
@@ -399,14 +397,17 @@ void JobList::createConnections()
 {
    /* connect to the action specific to this pages class that shows up in the 
     * page selector tree */
-   connect(actionRefreshJobList, SIGNAL(triggered()), this,
-                SLOT(populateTable()));
+   connect(actionRefreshJobList, SIGNAL(triggered()), this, SLOT(populateTable()));
    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
+#ifdef HAVE_QWT
    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
-   /* for the tableItemChanged to maintain m_currentJob */
-   connect(mp_tableWidget, SIGNAL(
-           currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
-           this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
+#else
+   graphButton->setEnabled(false);
+   graphButton->setVisible(false);
+#endif
+   /* for the selectionChanged to maintain m_currentJob and a delete selection */
+   connect(mp_tableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
+   connect(mp_tableWidget, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this, SLOT(showInfoForJob()));
 
    /* Do what is required for the local context sensitive menu */
 
@@ -414,41 +415,18 @@ void JobList::createConnections()
    /* setContextMenuPolicy is required */
    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
 
-   /* Add Actions */
-   mp_tableWidget->addAction(actionRefreshJobList);
-   mp_tableWidget->addAction(actionListJobid);
-   mp_tableWidget->addAction(actionListFilesOnJob);
-   mp_tableWidget->addAction(actionListJobMedia);
-   mp_tableWidget->addAction(actionListVolumes);
-   mp_tableWidget->addAction(actionDeleteJob);
-   mp_tableWidget->addAction(actionPurgeFiles);
-   mp_tableWidget->addAction(actionRestoreFromJob);
-   mp_tableWidget->addAction(actionRestoreFromTime);
-   mp_tableWidget->addAction(actionShowLogForJob);
-
-   /* Make Connections */
-   connect(actionListJobid, SIGNAL(triggered()), this,
-                SLOT(consoleListJobid()));
-   connect(actionListFilesOnJob, SIGNAL(triggered()), this,
-                SLOT(consoleListFilesOnJob()));
-   connect(actionListJobMedia, SIGNAL(triggered()), this,
-                SLOT(consoleListJobMedia()));
-   connect(actionListVolumes, SIGNAL(triggered()), this,
-                SLOT(consoleListVolumes()));
-   connect(actionDeleteJob, SIGNAL(triggered()), this,
-                SLOT(consoleDeleteJob()));
-   connect(actionPurgeFiles, SIGNAL(triggered()), this,
-                SLOT(consolePurgeFiles()));
-   connect(actionRestoreFromJob, SIGNAL(triggered()), this,
-                SLOT(preRestoreFromJob()));
-   connect(actionRestoreFromTime, SIGNAL(triggered()), this,
-                SLOT(preRestoreFromTime()));
-   connect(actionShowLogForJob, SIGNAL(triggered()), this,
-                SLOT(showLogForJob()));
-   connect(actionCancelJob, SIGNAL(triggered()), this,
-                SLOT(consoleCancelJob()));
-   connect(actionListJobTotals, SIGNAL(triggered()), this,
-                SLOT(consoleListJobTotals()));
+   connect(actionListFilesOnJob, SIGNAL(triggered()), this, SLOT(consoleListFilesOnJob()));
+   connect(actionListJobMedia, SIGNAL(triggered()), this, SLOT(consoleListJobMedia()));
+   connect(actionDeleteJob, SIGNAL(triggered()), this, SLOT(consoleDeleteJob()));
+   connect(actionRestartJob, SIGNAL(triggered()), this, SLOT(consoleRestartJob()));
+   connect(actionPurgeFiles, SIGNAL(triggered()), this, SLOT(consolePurgeFiles()));
+   connect(actionRestoreFromJob, SIGNAL(triggered()), this, SLOT(preRestoreFromJob()));
+   connect(actionRestoreFromTime, SIGNAL(triggered()), this, SLOT(preRestoreFromTime()));
+   connect(actionShowLogForJob, SIGNAL(triggered()), this, SLOT(showLogForJob()));
+   connect(actionShowInfoForJob, SIGNAL(triggered()), this, SLOT(showInfoForJob()));
+   connect(actionCancelJob, SIGNAL(triggered()), this, SLOT(consoleCancelJob()));
+   connect(actionListJobTotals, SIGNAL(triggered()), this, SLOT(consoleListJobTotals()));
+   connect(m_splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(splitterMoved(int, int)));
 
    m_contextActions.append(actionRefreshJobList);
    m_contextActions.append(actionListJobTotals);
@@ -458,13 +436,6 @@ void JobList::createConnections()
  * Functions to respond to local context sensitive menu sending console commands
  * If I could figure out how to make these one function passing a string, Yaaaaaa
  */
-void JobList::consoleListJobid()
-{
-   QString cmd("list jobid=");
-   cmd += m_currentJob;
-   if (mainWin->m_longList) { cmd.prepend("l"); }
-   consoleCommand(cmd);
-}
 void JobList::consoleListFilesOnJob()
 {
    QString cmd("list files jobid=");
@@ -479,23 +450,17 @@ void JobList::consoleListJobMedia()
    if (mainWin->m_longList) { cmd.prepend("l"); }
    consoleCommand(cmd);
 }
-void JobList::consoleListVolumes()
-{
-   QString cmd("list volumes jobid=");
-   cmd += m_currentJob;
-   if (mainWin->m_longList) { cmd.prepend("l"); }
-   consoleCommand(cmd);
-}
+
 void JobList::consoleListJobTotals()
 {
    QString cmd("list jobtotals");
-   cmd += m_currentJob;
    if (mainWin->m_longList) { cmd.prepend("l"); }
    consoleCommand(cmd);
 }
+
 void JobList::consoleDeleteJob()
 {
-   if (QMessageBox::warning(this, tr("Bat"),
+   if (QMessageBox::warning(this, "Bat",
       tr("Are you sure you want to delete??  !!!.\n"
 "This delete command is used to delete a Job record and all associated catalog"
 " records that were created. This command operates only on the Catalog"
@@ -508,12 +473,30 @@ void JobList::consoleDeleteJob()
       == QMessageBox::Cancel) { return; }
 
    QString cmd("delete job jobid=");
-   cmd += m_currentJob;
-   consoleCommand(cmd);
+   cmd += m_selectedJobs;
+   consoleCommand(cmd, false);
+   populateTable();
 }
+
+void JobList::consoleRestartJob()
+{
+   QString cmd;
+
+   cmd = tr("run job=\"%1\" client=\"%2\" level=%3").arg(m_jobName).arg(m_clientName).arg(m_levelName);
+   if (m_filesetName != "" && m_filesetName != "*None*") {
+      cmd += tr(" fileset=\"%1\"").arg(m_filesetName);
+   }
+
+   if (mainWin->m_commandDebug) Pmsg1(000, "Run cmd : %s\n",cmd.toUtf8().data());
+   consoleCommand(cmd, false);
+   populateTable();
+}
+
+
+
 void JobList::consolePurgeFiles()
 {
-   if (QMessageBox::warning(this, tr("Bat"),
+   if (QMessageBox::warning(this, "Bat",
       tr("Are you sure you want to purge ??  !!!.\n"
 "The Purge command will delete associated Catalog database records from Jobs and"
 " Volumes without considering the retention period. Purge  works only on the"
@@ -525,9 +508,14 @@ void JobList::consolePurgeFiles()
       QMessageBox::Ok | QMessageBox::Cancel)
       == QMessageBox::Cancel) { return; }
 
-   QString cmd("purge files jobid=");
-   cmd += m_currentJob;
-   consoleCommand(cmd);
+   m_console->m_warningPrevent = true;
+   foreach(QString job, m_selectedJobsList) {
+      QString cmd("purge files jobid=");
+      cmd += job;
+      consoleCommand(cmd, false);
+   }
+   m_console->m_warningPrevent = false;
+   populateTable();
 }
 
 /*
@@ -555,6 +543,15 @@ void JobList::showLogForJob()
    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
 }
 
+/*
+ * Subroutine to call class to show the log in the database from that job
+ */
+void JobList::showInfoForJob(QTableWidgetItem * /*item*/)
+{
+   QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
+   new Job(m_currentJob, pageSelectorTreeWidgetItem);
+}
+
 /*
  * Cancel a running job
  */
@@ -570,6 +567,7 @@ void JobList::consoleCancelJob()
  */
 void JobList::graphTable()
 {
+#ifdef HAVE_QWT
    JobPlotPass pass;
    pass.recordLimitCheck = limitCheckBox->checkState();
    pass.daysLimitCheck = daysCheckBox->checkState();
@@ -585,7 +583,9 @@ void JobList::graphTable()
    pass.use = true;
    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
    new JobPlot(pageSelectorTreeWidgetItem, pass);
+#endif
 }
+
 /*
  * Save user settings associated with this page
  */
@@ -594,6 +594,8 @@ void JobList::writeSettings()
    QSettings settings(m_console->m_dir->name(), "bat");
    settings.beginGroup(m_groupText);
    settings.setValue(m_splitText, m_splitter->saveState());
+   settings.setValue("FilterCopyCheckState", filterCopyCheckBox->checkState());
+   settings.setValue("FilterMigrationCheckState", filterMigrationCheckBox->checkState());
    settings.endGroup();
 }
 
@@ -603,9 +605,134 @@ void JobList::writeSettings()
 void JobList::readSettings()
 {
    m_groupText = "JobListPage";
-   m_splitText = "splitterSizes_1";
+   m_splitText = "splitterSizes_2";
    QSettings settings(m_console->m_dir->name(), "bat");
    settings.beginGroup(m_groupText);
-   m_splitter->restoreState(settings.value(m_splitText).toByteArray());
+   if (settings.contains(m_splitText)) {
+      m_splitter->restoreState(settings.value(m_splitText).toByteArray());
+   }
+   filterCopyCheckBox->setCheckState((Qt::CheckState)settings.value("FilterCopyCheckState").toInt());
+   filterMigrationCheckBox->setCheckState((Qt::CheckState)settings.value("FilterMigrationCheckState").toInt());
    settings.endGroup();
 }
+
+/*
+ * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
+ */
+void JobList::selectionChanged()
+{
+   QList<int> rowList;
+   QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
+   foreach (QTableWidgetItem *sitem, sitems) {
+      int row = sitem->row();
+      if (!rowList.contains(row)) {
+         rowList.append(row);
+      }
+   }
+
+   m_selectedJobs = "";
+   m_selectedJobsList.clear();
+   bool first = true;
+   foreach(int row, rowList) {
+      QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
+      if (!first) m_selectedJobs.append(",");
+      else first = false;
+      m_selectedJobs.append(sitem->text());
+      m_selectedJobsList.append(sitem->text());
+   }
+   m_selectedJobsCount = rowList.count();
+   if (m_selectedJobsCount > 1) {
+      QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
+      actionDeleteJob->setText(text);
+      text = QString( tr("Purge Files from list of %1 Jobs")).arg(m_selectedJobsCount);
+      actionPurgeFiles->setText(text);
+   } else {
+      actionDeleteJob->setText(tr("Delete Single Job"));
+      actionPurgeFiles->setText(tr("Purge Files from single job"));
+   }
+
+   /* remove all actions */
+   foreach(QAction* mediaAction, mp_tableWidget->actions()) {
+      mp_tableWidget->removeAction(mediaAction);
+   }
+
+   /* Add Actions */
+   mp_tableWidget->addAction(actionRefreshJobList);
+   if (m_selectedJobsCount == 1) {
+      mp_tableWidget->addAction(actionListFilesOnJob);
+      mp_tableWidget->addAction(actionListJobMedia);
+      mp_tableWidget->addAction(actionRestartJob);
+      mp_tableWidget->addAction(actionRestoreFromJob);
+      mp_tableWidget->addAction(actionRestoreFromTime);
+      mp_tableWidget->addAction(actionShowLogForJob);
+      mp_tableWidget->addAction(actionShowInfoForJob);
+   }
+   if (m_selectedJobsCount >= 1) {
+      mp_tableWidget->addAction(actionDeleteJob);
+      mp_tableWidget->addAction(actionPurgeFiles);
+   }
+
+   /* Make Connections */
+   if (m_checkCurrentWidget) {
+      int row = mp_tableWidget->currentRow();
+      QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
+      m_currentJob = jobitem->text();    /* get JobId */
+      jobitem = mp_tableWidget->item(row, m_clientIndex);
+      m_clientName = jobitem->text();    /* get Client Name */
+      jobitem = mp_tableWidget->item(row, m_nameIndex);
+      m_jobName = jobitem->text();    /* get Job Name */
+      jobitem = mp_tableWidget->item(row, m_levelIndex);
+      m_levelName = jobitem->text();    /* get level */
+      jobitem = mp_tableWidget->item(row, m_filesetIndex);
+      if (jobitem) {
+         m_filesetName = jobitem->text();    /* get FileSet Name */
+      } else {
+         m_filesetName = "";
+      }
+
+      /* include purged action or not */
+      jobitem = mp_tableWidget->item(row, m_purgedIndex);
+      QString purged = jobitem->text();
+/*      mp_tableWidget->removeAction(actionPurgeFiles);
+      if (purged == tr("No") ) {
+         mp_tableWidget->addAction(actionPurgeFiles);
+      }*/
+
+      /* include restore from time and job action or not */
+      jobitem = mp_tableWidget->item(row, m_typeIndex);
+      QString type = jobitem->text();
+      if (m_selectedJobsCount == 1) {
+         mp_tableWidget->removeAction(actionRestoreFromJob);
+         mp_tableWidget->removeAction(actionRestoreFromTime);
+         if (type == tr("Backup")) {
+            mp_tableWidget->addAction(actionRestoreFromJob);
+            mp_tableWidget->addAction(actionRestoreFromTime);
+         }
+      }
+
+      /* include cancel action or not */
+      jobitem = mp_tableWidget->item(row, m_statusIndex);
+      QString status = jobitem->text();
+      mp_tableWidget->removeAction(actionCancelJob);
+      if (status == tr("Running") || status == tr("Created, not yet running")) {
+         mp_tableWidget->addAction(actionCancelJob);
+      }
+   }
+}
+
+/*
+ *  Function to prevent the splitter from making index 0 of the size larger than it
+ *  needs to be
+ */
+void JobList::splitterMoved(int /*pos*/, int /*index*/)
+{
+   int frameMax = frame->maximumHeight();
+   QList<int> sizes = m_splitter->sizes();
+   int sizeSum = 0;
+   foreach(int size, sizes) { sizeSum += size; }
+   if (sizes[0] > frameMax) {
+      sizes[0] = frameMax;
+      sizes[1] = sizeSum - frameMax;
+      m_splitter->setSizes(sizes);
+   }
+}