]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/jobgraphs/jobplot.cpp
kes Apply Marco van Wieringen's set of patches, cleans up Migration/Copy
[bacula/bacula] / bacula / src / qt-console / jobgraphs / jobplot.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 Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28  
29 /*
30  *   Version $Id$
31  *
32  *  JobPlots Class
33  *
34  *   Dirk Bartley, March 2007
35  *
36  */ 
37
38 #include <QtGui>
39 #include "bat.h"
40 #include "util/comboutil.h"
41 #include "jobgraphs/jobplot.h"
42
43
44 JobPlotPass::JobPlotPass()
45 {
46    use = false;
47 }
48
49 JobPlotPass& JobPlotPass::operator=(const JobPlotPass &cp)
50 {
51    use = cp.use;
52    recordLimitCheck = cp.recordLimitCheck;
53    daysLimitCheck = cp.daysLimitCheck;
54    recordLimitSpin = cp.recordLimitSpin;
55    daysLimitSpin = cp.daysLimitSpin;
56    jobCombo = cp.jobCombo;
57    clientCombo = cp.clientCombo;
58    volumeCombo = cp.volumeCombo;
59    fileSetCombo = cp.fileSetCombo;
60    purgedCombo = cp.purgedCombo;
61    levelCombo = cp.levelCombo;
62    statusCombo = cp.statusCombo;
63    return *this;
64 }
65
66 /*
67  * Constructor for the controls class which inherits QScrollArea and a ui header
68  */
69 JobPlotControls::JobPlotControls()
70 {
71    setupUi(this);
72 }
73
74 /*
75  * Constructor, this class does not inherit anything but pages.
76  */
77 JobPlot::JobPlot(QTreeWidgetItem *parentTreeWidgetItem, JobPlotPass &passVals)
78 {
79    setupUserInterface();
80    pgInitialize(tr("JobPlot"), parentTreeWidgetItem);
81    readSplitterSettings();
82    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
83    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/applications-graphics.png")));
84    m_drawn = false;
85
86    /* this invokes the pass values = operator function */
87    m_pass = passVals;
88    m_closeable = true;
89    dockPage();
90    /* If the values of the controls are predetermined (from joblist), then set
91     * this class as current window at the front of the stack */
92    if (m_pass.use)
93       setCurrent();
94    m_jobPlot->replot();
95 }
96
97 /*
98  * Kill, crush Destroy
99  */
100 JobPlot::~JobPlot()
101 {
102    if (m_drawn)
103       writeSettings();
104    m_pjd.clear();
105 }
106
107 /*
108  * This is called when the page selector has this page selected
109  */
110 void JobPlot::currentStackItem()
111 {
112    if (!m_drawn) {
113       setupControls();
114       reGraph();
115       m_drawn=true;
116    }
117 }
118
119 /*
120  * Slot for the refresh push button, also called from constructor.
121  */
122 void JobPlot::reGraph()
123 {
124    /* clear m_pjd */
125    m_pjd.clear();
126    runQuery();
127    m_jobPlot->clear();
128    addCurve();
129    m_jobPlot->replot();
130 }
131
132 /*
133  * Setup the control widgets for the graph, this are the objects from JobPlotControls
134  */
135 void JobPlot::setupControls()
136 {
137    QStringList graphType = QStringList() << /* tr("Fitted") <<*/ tr("Sticks")
138                                          << tr("Lines") << tr("Steps") << tr("None");
139    controls->plotTypeCombo->addItems(graphType);
140
141    fillSymbolCombo(controls->fileSymbolTypeCombo);
142    fillSymbolCombo(controls->byteSymbolTypeCombo);
143
144    readControlSettings();
145
146    controls->fileCheck->setCheckState(Qt::Checked);
147    controls->byteCheck->setCheckState(Qt::Checked);
148    connect(controls->plotTypeCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(setPlotType(QString)));
149    connect(controls->fileSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setFileSymbolType(int)));
150    connect(controls->byteSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setByteSymbolType(int)));
151    connect(controls->fileCheck, SIGNAL(stateChanged(int)), this, SLOT(fileCheckChanged(int)));
152    connect(controls->byteCheck, SIGNAL(stateChanged(int)), this, SLOT(byteCheckChanged(int)));
153    connect(controls->refreshButton, SIGNAL(pressed()), this, SLOT(reGraph()));
154
155    controls->clientComboBox->addItem(tr("Any"));
156    controls->clientComboBox->addItems(m_console->client_list);
157
158    QStringList volumeList;
159    m_console->getVolumeList(volumeList);
160    controls->volumeComboBox->addItem(tr("Any"));
161    controls->volumeComboBox->addItems(volumeList);
162    controls->jobComboBox->addItem(tr("Any"));
163    controls->jobComboBox->addItems(m_console->job_list);
164
165    levelComboFill(controls->levelComboBox);
166
167    boolComboFill(controls->purgedComboBox);
168
169    controls->fileSetComboBox->addItem(tr("Any"));
170    controls->fileSetComboBox->addItems(m_console->fileset_list);
171    QStringList statusLongList;
172    m_console->getStatusList(statusLongList);
173    controls->statusComboBox->addItem(tr("Any"));
174    controls->statusComboBox->addItems(statusLongList);
175
176    if (m_pass.use) {
177       controls->limitCheckBox->setCheckState(m_pass.recordLimitCheck);
178       controls->limitSpinBox->setValue(m_pass.recordLimitSpin);
179       controls->daysCheckBox->setCheckState(m_pass.daysLimitCheck);
180       controls->daysSpinBox->setValue(m_pass.daysLimitSpin);
181
182       comboSel(controls->jobComboBox, m_pass.jobCombo);
183       comboSel(controls->clientComboBox, m_pass.clientCombo);
184       comboSel(controls->volumeComboBox, m_pass.volumeCombo);
185       comboSel(controls->fileSetComboBox, m_pass.fileSetCombo);
186       comboSel(controls->purgedComboBox, m_pass.purgedCombo);
187       comboSel(controls->levelComboBox, m_pass.levelCombo);
188       comboSel(controls->statusComboBox, m_pass.statusCombo);
189
190    } else {
191       /* Set Defaults for check and spin for limits */
192       controls->limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
193       controls->limitSpinBox->setValue(mainWin->m_recordLimitVal);
194       controls->daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
195       controls->daysSpinBox->setValue(mainWin->m_daysLimitVal);
196    } 
197 }
198
199 /*
200  * Setup the control widgets for the graph, this are the objects from JobPlotControls
201  */
202 void JobPlot::runQuery()
203 {
204    /* Set up query */
205    QString query("");
206    query += "SELECT DISTINCT "
207             " Job.Starttime AS JobStart,"
208             " Job.Jobfiles AS FileCount,"
209             " Job.JobBytes AS Bytes,"
210             " Job.JobId AS JobId"
211             " FROM Job"
212             " JOIN Client ON (Client.ClientId=Job.ClientId)"
213             " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
214             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)";
215
216    QStringList conditions;
217    comboCond(conditions, controls->jobComboBox, "Job.Name");
218    comboCond(conditions, controls->clientComboBox, "Client.Name");
219    int volumeIndex = controls->volumeComboBox->currentIndex();
220    if ((volumeIndex != -1) && (controls->volumeComboBox->itemText(volumeIndex) != tr("Any"))) {
221       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
222                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
223       conditions.append("Media.VolumeName='" + controls->volumeComboBox->itemText(volumeIndex) + "'");
224    }
225    comboCond(conditions, controls->fileSetComboBox, "FileSet.FileSet");
226    boolComboCond(conditions, controls->purgedComboBox, "Job.PurgedFiles");
227    levelComboCond(conditions, controls->levelComboBox, "Job.Level");
228    comboCond(conditions, controls->statusComboBox, "Status.JobStatusLong");
229
230    /* If Limit check box For limit by days is checked  */
231    if (controls->daysCheckBox->checkState() == Qt::Checked) {
232       QDateTime stamp = QDateTime::currentDateTime().addDays(-controls->daysSpinBox->value());
233       QString since = stamp.toString(Qt::ISODate);
234       conditions.append("Job.Starttime>'" + since + "'");
235    }
236    bool first = true;
237    foreach (QString condition, conditions) {
238       if (first) {
239          query += " WHERE " + condition;
240          first = false;
241       } else {
242          query += " AND " + condition;
243       }
244    }
245    /* Descending */
246    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
247    /* If Limit check box for limit records returned is checked  */
248    if (controls->limitCheckBox->checkState() == Qt::Checked) {
249       QString limit;
250       limit.setNum(controls->limitSpinBox->value());
251       query += " LIMIT " + limit;
252    }
253
254    if (mainWin->m_sqlDebug) {
255       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
256    }
257    QString resultline;
258    QStringList results;
259    if (m_console->sql_cmd(query, results)) {
260
261       QString field;
262       QStringList fieldlist;
263    
264       int row = 0;
265       /* Iterate through the record returned from the query */
266       foreach (resultline, results) {
267          PlotJobData *plotJobData = new PlotJobData();
268          fieldlist = resultline.split("\t");
269          int column = 0;
270          QString statusCode("");
271          /* Iterate through fields in the record */
272          foreach (field, fieldlist) {
273             field = field.trimmed();  /* strip leading & trailing spaces */
274             if (column == 0) {
275                plotJobData->dt = QDateTime::fromString(field, mainWin->m_dtformat);
276             } else if (column == 1) {
277                plotJobData->files = field.toDouble();
278             } else if (column == 2) {
279                plotJobData->bytes = field.toDouble();
280             }
281             column++;
282             m_pjd.prepend(plotJobData);
283          }
284          row++;
285       }
286    } 
287    if ((controls->volumeComboBox->itemText(volumeIndex) != tr("Any")) && (results.count() == 0)){
288       /* for context sensitive searches, let the user know if there were no
289        *        * results */
290       QMessageBox::warning(this, "Bat",
291           tr("The Jobs query returned no results.\n"
292          "Press OK to continue?"), QMessageBox::Ok );
293    }
294 }
295
296 /*
297  * The user interface that used to be in the ui header.  I wanted to have a
298  * scroll area which is not in designer.
299  */
300 void JobPlot::setupUserInterface()
301 {
302    QSizePolicy sizePolicy(static_cast<QSizePolicy::Policy>(1), static_cast<QSizePolicy::Policy>(5));
303    sizePolicy.setHorizontalStretch(0);
304    sizePolicy.setVerticalStretch(0);
305    sizePolicy.setVerticalStretch(0);
306    sizePolicy.setVerticalPolicy(QSizePolicy::Ignored);
307    sizePolicy.setHorizontalPolicy(QSizePolicy::Ignored);
308    m_gridLayout = new QGridLayout(this);
309    m_gridLayout->setSpacing(6);
310    m_gridLayout->setMargin(9);
311    m_gridLayout->setObjectName(QString::fromUtf8("m_gridLayout"));
312    m_splitter = new QSplitter(this);
313    m_splitter->setObjectName(QString::fromUtf8("m_splitter"));
314    m_splitter->setOrientation(Qt::Horizontal);
315    m_jobPlot = new QwtPlot(m_splitter);
316    m_jobPlot->setObjectName(QString::fromUtf8("m_jobPlot"));
317    m_jobPlot->setSizePolicy(sizePolicy);
318    m_jobPlot->setMinimumSize(QSize(0, 0));
319    QScrollArea *area = new QScrollArea(m_splitter);
320    area->setObjectName(QString::fromUtf8("area"));
321    controls = new JobPlotControls();
322    area->setWidget(controls);
323    
324    m_splitter->addWidget(m_jobPlot);
325    m_splitter->addWidget(area);
326
327    m_gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
328 }
329
330 /*
331  * Add the curves to the plot
332  */
333 void JobPlot::addCurve()
334 {
335    m_jobPlot->setTitle(tr("Files and Bytes backed up"));
336    m_jobPlot->insertLegend(new QwtLegend(), QwtPlot::RightLegend);
337
338    // Set axis titles
339    m_jobPlot->enableAxis(QwtPlot::yRight);
340    m_jobPlot->setAxisTitle(QwtPlot::yRight, tr("<-- Bytes Kb"));
341    m_jobPlot->setAxisTitle(m_jobPlot->xBottom, tr("date of backup -->"));
342    m_jobPlot->setAxisTitle(m_jobPlot->yLeft, tr("Number of Files -->"));
343    m_jobPlot->setAxisScaleDraw(QwtPlot::xBottom, new DateTimeScaleDraw());
344
345    // Insert new curves
346    m_fileCurve = new QwtPlotCurve( tr("Files") );
347    m_fileCurve->setPen(QPen(Qt::red));
348    m_fileCurve->setCurveType(m_fileCurve->Yfx);
349    m_fileCurve->setYAxis(QwtPlot::yLeft);
350
351    m_byteCurve = new QwtPlotCurve(tr("Bytes"));
352    m_byteCurve->setPen(QPen(Qt::blue));
353    m_byteCurve->setCurveType(m_byteCurve->Yfx);
354    m_byteCurve->setYAxis(QwtPlot::yRight);
355    setPlotType(controls->plotTypeCombo->currentText());
356    setFileSymbolType(controls->fileSymbolTypeCombo->currentIndex());
357    setByteSymbolType(controls->byteSymbolTypeCombo->currentIndex());
358
359    m_fileCurve->attach(m_jobPlot);
360    m_byteCurve->attach(m_jobPlot);
361
362    // attach data
363    int size = m_pjd.count();
364    int j = 0;
365 #if defined(__GNU_C)
366    double tval[size];
367    double fval[size];
368    double bval[size];
369 #else
370    double *tval;
371    double *fval;
372    double *bval;
373
374    tval = (double *)malloc(size * sizeof(double));
375    fval = (double *)malloc(size * sizeof(double));
376    bval = (double *)malloc(size * sizeof(double));
377 #endif
378
379    foreach (PlotJobData* plotJobData, m_pjd) {
380 //      printf("%.0f %.0f %s\n", plotJobData->bytes, plotJobData->files,
381 //              plotJobData->dt.toString(mainWin->m_dtformat).toUtf8().data());
382       fval[j] = plotJobData->files;
383       bval[j] = plotJobData->bytes / 1024;
384       tval[j] = plotJobData->dt.toTime_t();
385 //      printf("%i %.0f %.0f %.0f\n", j, tval[j], fval[j], bval[j]);
386       j++;
387    }
388    m_fileCurve->setData(tval,fval,size);
389    m_byteCurve->setData(tval,bval,size);
390
391    for (int year=2000; year<2010; year++) {
392       for (int month=1; month<=12; month++) {
393          QString monthBegin;
394          if (month > 9) {
395             QTextStream(&monthBegin) << year << "-" << month << "-01 00:00:00";
396          } else {
397             QTextStream(&monthBegin) << year << "-0" << month << "-01 00:00:00";
398          }
399          QDateTime mdt = QDateTime::fromString(monthBegin, mainWin->m_dtformat);
400          double monbeg = mdt.toTime_t();
401    
402          //  ...a vertical line at the first of each month
403          QwtPlotMarker *mX = new QwtPlotMarker();
404          mX->setLabel(mdt.toString("MMM-d"));
405          mX->setLabelAlignment(Qt::AlignRight|Qt::AlignTop);
406          mX->setLineStyle(QwtPlotMarker::VLine);
407          QPen pen(Qt::darkGray);
408          pen.setStyle(Qt::DashDotDotLine);
409          mX->setLinePen(pen);
410          mX->setXValue(monbeg);
411          mX->attach(m_jobPlot);
412       }
413    }
414
415 #if !defined(__GNU_C)
416    free(tval);
417    free(fval);
418    free(bval);
419 #endif
420 }
421
422 /*
423  * slot to respond to the plot type combo changing
424  */
425 void JobPlot::setPlotType(QString currentText)
426 {
427    QwtPlotCurve::CurveStyle style = QwtPlotCurve::NoCurve;
428    if (currentText == tr("Fitted")) {
429       style = QwtPlotCurve::Lines;
430       m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted);
431       m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted);
432    } else if (currentText == tr("Sticks")) {
433       style = QwtPlotCurve::Sticks;
434    } else if (currentText == tr("Lines")) {
435       style = QwtPlotCurve::Lines;
436       m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted);
437       m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted);
438    } else if (currentText == tr("Steps")) {
439       style = QwtPlotCurve::Steps;
440    } else if (currentText == tr("None")) {
441       style = QwtPlotCurve::NoCurve;
442    }
443    m_fileCurve->setStyle(style);
444    m_byteCurve->setStyle(style);
445    m_jobPlot->replot();
446 }
447
448 void JobPlot::fillSymbolCombo(QComboBox *q)
449 {
450   q->addItem( tr("Ellipse"), (int)QwtSymbol::Ellipse);
451   q->addItem( tr("Rect"), (int)QwtSymbol::Rect); 
452   q->addItem( tr("Diamond"), (int)QwtSymbol::Diamond);
453   q->addItem( tr("Triangle"), (int)QwtSymbol::Triangle);
454   q->addItem( tr("DTrianle"), (int)QwtSymbol::DTriangle);
455   q->addItem( tr("UTriangle"), (int)QwtSymbol::UTriangle);
456   q->addItem( tr("LTriangle"), (int)QwtSymbol::LTriangle);
457   q->addItem( tr("RTriangle"), (int)QwtSymbol::RTriangle);
458   q->addItem( tr("Cross"), (int)QwtSymbol::Cross);
459   q->addItem( tr("XCross"), (int)QwtSymbol::XCross);
460   q->addItem( tr("HLine"), (int)QwtSymbol::HLine);
461   q->addItem( tr("Vline"), (int)QwtSymbol::VLine);
462   q->addItem( tr("Star1"), (int)QwtSymbol::Star1);
463   q->addItem( tr("Star2"), (int)QwtSymbol::Star2);
464   q->addItem( tr("Hexagon"), (int)QwtSymbol::Hexagon); 
465   q->addItem( tr("None"), (int)QwtSymbol::NoSymbol);
466 }
467
468
469 /*
470  * slot to respond to the symbol type combo changing
471  */
472 void JobPlot::setFileSymbolType(int index)
473 {
474    setSymbolType(index, 0);
475 }
476
477 void JobPlot::setByteSymbolType(int index)
478 {
479    setSymbolType(index, 1);
480 }
481 void JobPlot::setSymbolType(int index, int type)
482 {
483    QwtSymbol sym;
484    sym.setPen(QColor(Qt::black));
485    sym.setSize(7);
486
487    QVariant style;
488    if (0 == type) {
489       style = controls->fileSymbolTypeCombo->itemData(index);
490       sym.setStyle( (QwtSymbol::Style)style.toInt() );
491       sym.setBrush(QColor(Qt::yellow));
492       m_fileCurve->setSymbol(sym);
493    
494    } else {
495       style = controls->byteSymbolTypeCombo->itemData(index);
496       sym.setStyle( (QwtSymbol::Style)style.toInt() );
497       sym.setBrush(QColor(Qt::blue));
498       m_byteCurve->setSymbol(sym);
499
500    }
501    m_jobPlot->replot();
502 }
503
504 /*
505  * slot to respond to the file check box changing state
506  */
507 void JobPlot::fileCheckChanged(int newstate)
508 {
509    if (newstate == Qt::Unchecked) {
510       m_fileCurve->detach();
511       m_jobPlot->enableAxis(QwtPlot::yLeft, false);
512    } else {
513       m_fileCurve->attach(m_jobPlot);
514       m_jobPlot->enableAxis(QwtPlot::yLeft);
515    }
516    m_jobPlot->replot();
517 }
518
519 /*
520  * slot to respond to the byte check box changing state
521  */
522 void JobPlot::byteCheckChanged(int newstate)
523 {
524    if (newstate == Qt::Unchecked) {
525       m_byteCurve->detach();
526       m_jobPlot->enableAxis(QwtPlot::yRight, false);
527    } else {
528       m_byteCurve->attach(m_jobPlot);
529       m_jobPlot->enableAxis(QwtPlot::yRight);
530    }
531    m_jobPlot->replot();
532 }
533
534 /*
535  * Save user settings associated with this page
536  */
537 void JobPlot::writeSettings()
538 {
539    QSettings settings(m_console->m_dir->name(), "bat");
540    settings.beginGroup("JobPlot");
541    settings.setValue("m_splitterSizes", m_splitter->saveState());
542    settings.setValue("fileSymbolTypeCombo", controls->fileSymbolTypeCombo->currentText());
543    settings.setValue("byteSymbolTypeCombo", controls->byteSymbolTypeCombo->currentText());
544    settings.setValue("plotTypeCombo", controls->plotTypeCombo->currentText());
545    settings.endGroup();
546 }
547
548 /* 
549  * Read settings values for Controls
550  */
551 void JobPlot::readControlSettings()
552 {
553    QSettings settings(m_console->m_dir->name(), "bat");
554    settings.beginGroup("JobPlot");
555    int fileSymbolTypeIndex = controls->fileSymbolTypeCombo->findText(settings.value("fileSymbolTypeCombo").toString(), Qt::MatchExactly);
556    if (fileSymbolTypeIndex == -1) fileSymbolTypeIndex = 2;
557       controls->fileSymbolTypeCombo->setCurrentIndex(fileSymbolTypeIndex);
558    int byteSymbolTypeIndex = controls->byteSymbolTypeCombo->findText(settings.value("byteSymbolTypeCombo").toString(), Qt::MatchExactly);
559    if (byteSymbolTypeIndex == -1) byteSymbolTypeIndex = 3;
560       controls->byteSymbolTypeCombo->setCurrentIndex(byteSymbolTypeIndex);
561    int plotTypeIndex = controls->plotTypeCombo->findText(settings.value("plotTypeCombo").toString(), Qt::MatchExactly);
562    if (plotTypeIndex == -1) plotTypeIndex = 2;
563       controls->plotTypeCombo->setCurrentIndex(plotTypeIndex);
564    settings.endGroup();
565 }
566
567 /*
568  * Read and restore user settings associated with this page
569  */
570 void JobPlot::readSplitterSettings()
571 {
572    QSettings settings(m_console->m_dir->name(), "bat");
573    settings.beginGroup("JobPlot");
574    m_splitter->restoreState(settings.value("m_splitterSizes").toByteArray());
575    settings.endGroup();
576 }