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