Make TX volatge for simu more flexible (#7124)
[opentx.git] / companion / src / logsdialog.cpp
blobe60318c69d2d3eda7234c1b13afb4af9e824c9a5
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <math.h>
22 #include "logsdialog.h"
23 #include "appdata.h"
24 #include "ui_logsdialog.h"
25 #include "helpers.h"
26 #if defined _MSC_VER || !defined __GNUC__
27 #include <windows.h>
28 #else
29 #include <unistd.h>
30 #endif
32 LogsDialog::LogsDialog(QWidget *parent) :
33 QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),
34 ui(new Ui::LogsDialog),
35 tracerMaxAlt(0),
36 cursorA(0),
37 cursorB(0),
38 cursorLine(0)
40 csvlog.clear();
42 ui->setupUi(this);
43 setWindowIcon(CompanionIcon("logs.png"));
45 plotLock=false;
47 colors.append(Qt::green);
48 colors.append(Qt::red);
49 colors.append(Qt::yellow);
50 colors.append(Qt::magenta);
51 colors.append(Qt::cyan);
52 colors.append(Qt::darkBlue);
53 colors.append(Qt::darkGreen);
54 colors.append(Qt::darkRed);
55 colors.append(Qt::darkYellow);
56 colors.append(Qt::darkMagenta);
57 colors.append(Qt::darkCyan);
58 colors.append(Qt::blue);
59 pen.setWidthF(1.0);
61 // create and prepare a plot title layout element
62 QCPPlotTitle *title = new QCPPlotTitle(ui->customPlot);
63 title->setText(tr("Telemetry logs"));
65 // add it to the main plot layout
66 ui->customPlot->plotLayout()->insertRow(0);
67 ui->customPlot->plotLayout()->addElement(0, 0, title);
68 ui->customPlot->setNoAntialiasingOnDrag(true);
69 ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | QCP::iSelectLegend | QCP::iSelectPlottables);
71 axisRect = ui->customPlot->axisRect();
72 axisRect->axis(QCPAxis::atBottom)->setLabel(tr("Time (hh:mm:ss)"));
73 axisRect->axis(QCPAxis::atBottom)->setTickLabelType(QCPAxis::ltDateTime);
74 axisRect->axis(QCPAxis::atBottom)->setDateTimeFormat("hh:mm:ss");
75 QDateTime now = QDateTime::currentDateTime();
76 axisRect->axis(QCPAxis::atBottom)->setRange(now.addSecs(-60*60*2).toTime_t(), now.toTime_t());
77 axisRect->axis(QCPAxis::atLeft)->setTickLabels(false);
78 axisRect->addAxis(QCPAxis::atLeft);
79 axisRect->addAxis(QCPAxis::atRight);
80 axisRect->axis(QCPAxis::atLeft, 1)->setVisible(false);
81 axisRect->axis(QCPAxis::atRight, 1)->setVisible(false);
83 QFont legendFont = font();
84 legendFont.setPointSize(10);
85 ui->customPlot->legend->setFont(legendFont);
86 ui->customPlot->legend->setSelectedFont(legendFont);
87 axisRect->insetLayout()->setInsetAlignment(0, Qt::AlignTop | Qt::AlignLeft);
89 rightLegend = new QCPLegend;
90 axisRect->insetLayout()->addElement(rightLegend, Qt::AlignTop | Qt::AlignRight);
91 rightLegend->setLayer("legend");
92 rightLegend->setFont(legendFont);
93 rightLegend->setSelectedFont(legendFont);
94 rightLegend->setVisible(false);
96 ui->customPlot->setAutoAddPlottableToLegend(false);
98 QString path = g.gePath();
99 if (path.isEmpty() || !QFile(path).exists()) {
100 ui->mapsButton->hide();
103 ui->SaveSession_PB->setEnabled(false);
105 // connect slot that ties some axis selections together (especially opposite axes):
106 connect(ui->customPlot, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged()));
107 // connect slots that takes care that when an axis is selected, only that direction can be dragged and zoomed:
108 connect(ui->customPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress(QMouseEvent*)));
109 connect(ui->customPlot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel()));
111 // make left axes transfer its range to right axes:
112 connect(axisRect->axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)), this, SLOT(yAxisChangeRanges(QCPRange)));
114 // connect some interaction slots:
115 connect(ui->customPlot, SIGNAL(titleDoubleClick(QMouseEvent*, QCPPlotTitle*)), this, SLOT(titleDoubleClick(QMouseEvent*, QCPPlotTitle*)));
116 connect(ui->customPlot, SIGNAL(axisDoubleClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)), this, SLOT(axisLabelDoubleClick(QCPAxis*,QCPAxis::SelectablePart)));
117 connect(ui->customPlot, SIGNAL(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*,QMouseEvent*)), this, SLOT(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*)));
118 connect(ui->FieldsTW, SIGNAL(itemSelectionChanged()), this, SLOT(plotLogs()));
119 connect(ui->logTable, SIGNAL(itemSelectionChanged()), this, SLOT(plotLogs()));
120 connect(ui->Reset_PB, SIGNAL(clicked()), this, SLOT(plotLogs()));
121 connect(ui->SaveSession_PB, SIGNAL(clicked()), this, SLOT(saveSession()));
124 LogsDialog::~LogsDialog()
126 delete ui;
129 void LogsDialog::titleDoubleClick(QMouseEvent *evt, QCPPlotTitle *title)
131 // Set the plot title by double clicking on it
132 bool ok;
133 QString newTitle = QInputDialog::getText(this, tr("Plot Title Change"), tr("New plot title:"), QLineEdit::Normal, title->text(), &ok);
134 if (ok) {
135 title->setText(newTitle);
136 ui->customPlot->replot();
140 void LogsDialog::axisLabelDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part)
142 // Set an axis label by double clicking on it
143 if (part == QCPAxis::spAxisLabel) {
144 // only react when the actual axis label is clicked, not tick label or axis backbone
145 bool ok;
146 QString newLabel = QInputDialog::getText(this, tr("Axis Label Change"), tr("New axis label:"), QLineEdit::Normal, axis->label(), &ok);
147 if (ok) {
148 axis->setLabel(newLabel);
149 ui->customPlot->replot();
154 void LogsDialog::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item)
156 // Rename a graph by double clicking on its legend item
157 if (item) {
158 // only react if item was clicked (user could have clicked on border padding of legend where there is no item, then item is 0)
159 QCPPlottableLegendItem *plItem = qobject_cast<QCPPlottableLegendItem*>(item);
160 bool ok;
161 QString newName = QInputDialog::getText(this, tr("Graph Name Change"), tr("New graph name:"), QLineEdit::Normal, plItem->plottable()->name(), &ok);
162 if (ok) {
163 plItem->plottable()->setName(newName);
164 ui->customPlot->replot();
169 void LogsDialog::selectionChanged()
172 normally, axis base line, axis tick labels and axis labels are selectable separately, but we want
173 the user only to be able to select the axis as a whole, so we tie the selected states of the tick labels
174 and the axis base line together. However, the axis label shall be selectable individually.
176 The selection state of the left and right axes shall be synchronized as well as the state of the
177 bottom and top axes.
179 Further, we want to synchronize the selection of the graphs with the selection state of the respective
180 legend item belonging to that graph. So the user can select a graph by either clicking on the graph itself
181 or on its legend item.
184 if (plotLock) return;
186 // handle bottom axis and tick labels as one selectable object:
187 if (axisRect->axis(QCPAxis::atBottom)->selectedParts().testFlag(QCPAxis::spAxis) ||
188 axisRect->axis(QCPAxis::atBottom)->selectedParts().testFlag(QCPAxis::spTickLabels))
190 axisRect->axis(QCPAxis::atBottom)->setSelectedParts(QCPAxis::spAxis |
191 QCPAxis::spTickLabels);
193 // make left and right axes be selected synchronously,
194 // and handle axis and tick labels as one selectable object:
195 if (axisRect->axis(QCPAxis::atLeft)->selectedParts().testFlag(QCPAxis::spAxis) ||
196 axisRect->axis(QCPAxis::atLeft)->selectedParts().testFlag(QCPAxis::spTickLabels) ||
198 axisRect->axis(QCPAxis::atRight)->visible() &&
199 (axisRect->axis(QCPAxis::atRight)->selectedParts().testFlag(QCPAxis::spAxis) ||
200 axisRect->axis(QCPAxis::atRight)->selectedParts().testFlag(QCPAxis::spTickLabels))
201 ) || (
202 axisRect->axis(QCPAxis::atLeft, 1)->visible() &&
203 (axisRect->axis(QCPAxis::atLeft, 1)->selectedParts().testFlag(QCPAxis::spAxis) ||
204 axisRect->axis(QCPAxis::atLeft, 1)->selectedParts().testFlag(QCPAxis::spTickLabels))
205 ) || (
206 axisRect->axis(QCPAxis::atRight)->visible() &&
207 (axisRect->axis(QCPAxis::atRight, 1)->selectedParts().testFlag(QCPAxis::spAxis) ||
208 axisRect->axis(QCPAxis::atRight, 1)->selectedParts().testFlag(QCPAxis::spTickLabels))
211 axisRect->axis(QCPAxis::atLeft)->setSelectedParts(QCPAxis::spAxis |
212 QCPAxis::spTickLabels);
213 if (axisRect->axis(QCPAxis::atRight)->visible()) {
214 axisRect->axis(QCPAxis::atRight)->setSelectedParts(QCPAxis::spAxis |
215 QCPAxis::spTickLabels);
216 if (axisRect->axis(QCPAxis::atLeft, 1)->visible()) {
217 axisRect->axis(QCPAxis::atLeft, 1)->setSelectedParts(QCPAxis::spAxis |
218 QCPAxis::spTickLabels);
219 if (axisRect->axis(QCPAxis::atRight, 1)->visible()) {
220 axisRect->axis(QCPAxis::atRight, 1)->setSelectedParts(QCPAxis::spAxis |
221 QCPAxis::spTickLabels);
227 // synchronize selection of graphs with selection of corresponding legend items:
228 for (int i=0; i<ui->customPlot->graphCount(); ++i) {
229 QCPGraph *graph = ui->customPlot->graph(i);
230 QCPPlottableLegendItem *item = ui->customPlot->legend->itemWithPlottable(graph);
231 if (item == NULL) item = rightLegend->itemWithPlottable(graph);
232 if (item->selected() || graph->selected()) {
233 item->setSelected(true);
234 graph->setSelected(true);
239 QList<QStringList> LogsDialog::filterGePoints(const QList<QStringList> & input)
241 QList<QStringList> result;
243 int n = input.count();
244 if (n == 0) {
245 return result;
248 int gpscol = 0;
249 for (int i=1; i<input.at(0).count(); i++) {
250 if (input.at(0).at(i) == "GPS") {
251 gpscol=i;
254 if (gpscol == 0) {
255 QMessageBox::critical(this, tr("Error: no GPS data found"),
256 tr("The column containing GPS coordinates must be named \"GPS\".\n\n\
257 The columns for altitude \"GAlt\" and for speed \"GSpd\" are optional"));
258 return result;
261 result.append(input.at(0));
262 bool rangeSelected = ui->logTable->selectionModel()->selectedRows().length() > 0;
264 GpsGlitchFilter glitchFilter;
265 GpsLatLonFilter latLonFilter;
267 for (int i = 1; i < n; i++) {
268 if ((ui->logTable->item(i-1, 1)->isSelected() && rangeSelected) || !rangeSelected) {
270 GpsCoord coord = extractGpsCoordinates(input.at(i).at(gpscol));
272 // glitch filter
273 if ( glitchFilter.isGlitch(coord) ) {
274 // qDebug() << "filterGePoints(): GPS glitch detected at" << i << coord.latitude << coord.longitude;
275 continue;
278 // lat long pair filter
279 if ( !latLonFilter.isValid(coord) ) {
280 // qDebug() << "filterGePoints(): Lat-Lon pair wrong, skipping at" << i << coord.latitude << coord.longitude;
281 continue;
284 // qDebug() << "point " << latitude << longitude;
285 result.append(input.at(i));
289 // qDebug() << "filterGePoints(): filtered from" << input.count() << "to " << result.count() << "points";
290 return result;
293 void LogsDialog::exportToGoogleEarth()
295 // filter data points
296 QList<QStringList> dataPoints = filterGePoints(csvlog);
297 int n = dataPoints.count(); // number of points to export
298 if (n==0) return;
300 int gpscol=0, altcol=0, speedcol=0;
301 double altMultiplier = 1.0;
303 QSet<int> nondataCols;
304 for (int i=1; i<dataPoints.at(0).count(); i++) {
305 // Long,Lat,Course,GPS Speed,GPS Alt
306 if (dataPoints.at(0).at(i) == "GPS") {
307 gpscol=i;
309 if (dataPoints.at(0).at(i).contains("GAlt")) {
310 altcol = i;
311 nondataCols << i;
312 if (dataPoints.at(0).at(i).contains("(ft)")) {
313 altMultiplier = 0.3048; // feet to meters
316 if (dataPoints.at(0).at(i).contains("GSpd")) {
317 speedcol = i;
318 nondataCols << i;
322 if (gpscol==0 ) {
323 return;
326 // qDebug() << "gpscol" << gpscol << "altcol" << altcol << "speedcol" << speedcol << "altMultiplier" << altMultiplier;
327 const QString geFilename = generateProcessUniqueTempFileName("flight.kml");
328 QFile geFile(geFilename);
329 if (!geFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
330 QMessageBox::warning(this, CPN_STR_TTL_ERROR,
331 tr("Cannot write file %1:\n%2.")
332 .arg(geFilename)
333 .arg(geFile.errorString()));
334 return;
337 const QString geIconFilename = QStringLiteral("track0.png");
338 const QString geIconPath = QFileInfo(geFile).absolutePath() + "/" + geIconFilename;
339 if (!QFile::exists(geIconPath)) {
340 QFile::copy(":/images/" + geIconFilename, geIconPath);
343 QTextStream outputStream(&geFile);
345 // file header
346 outputStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n";
347 outputStream << "\t<Document>\n\t\t<name>" << logFilename << "</name>\n";
348 outputStream << "\t\t<Style id=\"multiTrack_n\">\n\t\t\t<IconStyle>\n\t\t\t\t<Icon>\n\t\t\t\t\t<href>" << geIconFilename << "</href>\n\t\t\t\t</Icon>\n\t\t\t</IconStyle>\n\t\t\t<LineStyle>\n\t\t\t\t<color>991081f4</color>\n\t\t\t\t<width>6</width>\n\t\t\t</LineStyle>\n\t\t</Style>\n";
349 outputStream << "\t\t<Style id=\"multiTrack_h\">\n\t\t\t<IconStyle>\n\t\t\t\t<scale>0</scale>\n\t\t\t\t<Icon>\n\t\t\t\t\t<href>" << geIconFilename << "</href>\n\t\t\t\t</Icon>\n\t\t\t</IconStyle>\n\t\t\t<LineStyle>\n\t\t\t\t<color>991081f4</color>\n\t\t\t\t<width>8</width>\n\t\t\t</LineStyle>\n\t\t</Style>\n";
350 outputStream << "\t\t<StyleMap id=\"multiTrack\">\n\t\t\t<Pair>\n\t\t\t\t<key>normal</key>\n\t\t\t\t<styleUrl>#multiTrack_n</styleUrl>\n\t\t\t</Pair>\n\t\t\t<Pair>\n\t\t\t\t<key>highlight</key>\n\t\t\t\t<styleUrl>#multiTrack_h</styleUrl>\n\t\t\t</Pair>\n\t\t</StyleMap>\n";
351 outputStream << "\t\t<Style id=\"lineStyle\">\n\t\t\t<LineStyle>\n\t\t\t\t<color>991081f4</color>\n\t\t\t\t<width>6</width>\n\t\t\t</LineStyle>\n\t\t</Style>\n";
352 outputStream << "\t\t<Schema id=\"schema\">\n";
353 outputStream << "\t\t\t<gx:SimpleArrayField name=\"GPSSpeed\" type=\"float\">\n\t\t\t\t<displayName>GPS Speed</displayName>\n\t\t\t</gx:SimpleArrayField>\n";
355 // declare additional fields
356 for (int i=0; i<dataPoints.at(0).count()-2; i++) {
357 if (ui->FieldsTW->item(i, 0) && ui->FieldsTW->item(i, 0)->isSelected() && !nondataCols.contains(i+2)) {
358 QString origName = dataPoints.at(0).at(i+2);
359 QString safeName = origName;
360 safeName.replace(" ","_");
361 outputStream << "\t\t\t<gx:SimpleArrayField name=\""<< safeName <<"\" ";
362 outputStream << "type=\"string\""; // additional fields have fixed type: string
363 outputStream << ">\n\t\t\t\t<displayName>" << origName << "</displayName>\n\t\t\t</gx:SimpleArrayField>\n";
367 QString planeName;
368 if (logFilename.indexOf("-")>0) {
369 planeName=logFilename.left(logFilename.indexOf("-"));
370 } else {
371 planeName=logFilename;
374 outputStream << "\t\t</Schema>\n";
375 outputStream << "\t\t<Folder>\n\t\t\t<name>Log Data</name>\n\t\t\t<Placemark>\n\t\t\t\t<name>" << planeName << "</name>";
376 outputStream << "\n\t\t\t\t<styleUrl>#multiTrack</styleUrl>";
377 outputStream << "\n\t\t\t\t<gx:Track>\n";
378 outputStream << "\n\t\t\t\t\t<altitudeMode>absolute</altitudeMode>\n";
380 // time data points
381 for (int i=1; i<n; i++) {
382 QString tstamp=dataPoints.at(i).at(0)+QString("T")+dataPoints.at(i).at(1)+QString("Z");
383 outputStream << "\t\t\t\t\t<when>"<< tstamp <<"</when>\n";
386 // coordinate data points
387 outputStream.setRealNumberNotation(QTextStream::FixedNotation);
388 outputStream.setRealNumberPrecision(8);
389 for (int i=1; i<n; i++) {
390 GpsCoord coord = extractGpsCoordinates(dataPoints.at(i).at(gpscol));
391 int altitude = altcol ? (dataPoints.at(i).at(altcol).toFloat() * altMultiplier) : 0;
392 outputStream << "\t\t\t\t\t<gx:coord>" << coord.longitude << " " << coord.latitude << " " << altitude << " </gx:coord>\n" ;
395 // additional data for data points
396 outputStream << "\t\t\t\t\t<ExtendedData>\n\t\t\t\t\t\t<SchemaData schemaUrl=\"#schema\">\n";
398 if (speedcol) {
399 // gps speed data points
400 outputStream << "\t\t\t\t\t\t\t<gx:SimpleArrayData name=\"GPSSpeed\">\n";
401 for (int i=1; i<n; i++) {
402 outputStream << "\t\t\t\t\t\t\t\t<gx:value>"<< dataPoints.at(i).at(speedcol) <<"</gx:value>\n";
404 outputStream << "\t\t\t\t\t\t\t</gx:SimpleArrayData>\n";
407 // add values for additional fields
408 for (int i=0; i<dataPoints.at(0).count()-2; i++) {
409 if (ui->FieldsTW->item(i, 0) && ui->FieldsTW->item(i, 0)->isSelected() && !nondataCols.contains(i+2)) {
410 QString safeName = dataPoints.at(0).at(i+2);;
411 safeName.replace(" ","_");
412 outputStream << "\t\t\t\t\t\t\t<gx:SimpleArrayData name=\""<< safeName <<"\">\n";
413 for (int j=1; j<n; j++) {
414 outputStream << "\t\t\t\t\t\t\t\t<gx:value>"<< dataPoints.at(j).at(i+2) <<"</gx:value>\n";
416 outputStream << "\t\t\t\t\t\t\t</gx:SimpleArrayData>\n";
420 outputStream << "\t\t\t\t\t\t</SchemaData>\n\t\t\t\t\t</ExtendedData>\n\t\t\t\t</gx:Track>\n\t\t\t</Placemark>\n\t\t</Folder>\n\t</Document>\n</kml>";
421 geFile.close();
423 QString gePath = g.gePath();
424 QStringList parameters;
425 #ifdef __APPLE__
426 parameters << "-a";
427 parameters << gePath;
428 gePath = "/usr/bin/open";
429 #endif
430 parameters << geFilename;
431 QProcess::startDetached(gePath, parameters);
434 void LogsDialog::on_mapsButton_clicked()
436 ui->FieldsTW->setDisabled(true);
437 ui->logTable->setDisabled(true);
439 exportToGoogleEarth();
441 ui->FieldsTW->setDisabled(false);
442 ui->logTable->setDisabled(false);
445 void LogsDialog::mousePress(QMouseEvent * event)
447 // if an axis is selected, only allow the direction of that axis to be dragged
448 // if no axis is selected, both directions may be dragged
450 if (axisRect->axis(QCPAxis::atBottom)->selectedParts().testFlag(QCPAxis::spAxis))
451 axisRect->setRangeDrag(axisRect->axis(QCPAxis::atBottom)->orientation());
452 else if (axisRect->axis(QCPAxis::atLeft)->selectedParts().testFlag(QCPAxis::spAxis))
453 axisRect->setRangeDrag(axisRect->axis(QCPAxis::atLeft)->orientation());
454 else
455 axisRect->setRangeDrag(Qt::Horizontal | Qt::Vertical);
457 if (event->button() == Qt::RightButton) {
458 double x = axisRect->axis(QCPAxis::atBottom)->pixelToCoord(event->pos().x());
459 placeCursor(x, event->modifiers() & Qt::ShiftModifier);
463 void LogsDialog::placeCursor(double x, bool second)
465 QCPItemTracer * cursor = second ? cursorB : cursorA;
467 if (cursor) {
468 cursor->setGraphKey(x);
469 cursor->updatePosition();
470 cursor->setVisible(true);
473 if (cursorA && cursorB) {
474 updateCursorsLabel();
478 void LogsDialog::updateCursorsLabel()
480 QString text = QString("Max Altitude: %1 m\n").arg(tracerMaxAlt->position->value(), 0, 'f', 1);
481 if (cursorA->visible() ) {
482 text += tr("Cursor A: %1 m").arg(cursorA->position->value(), 0, 'f', 1) + "\n";
484 if (cursorB->visible() ) {
485 text += tr("Cursor B: %1 m").arg(cursorB->position->value(), 0, 'f', 1) + "\n";
487 if (cursorA->visible() && cursorB->visible()) {
488 cursorLine->setVisible(true);
489 // calc deltas
490 double deltaX = cursorB->position->key() - cursorA->position->key();
491 double deltaY = cursorB->position->value() - cursorA->position->value();
492 double slope = 0;
493 if (deltaX != 0) {
494 slope = deltaY / deltaX;
496 qDebug() << "Cursors: dt:" << formatTimeDelta(deltaX) << "dy:" << deltaY << "rate:" << slope;
497 text += tr("Time delta: %1").arg(formatTimeDelta(deltaX)) + "\n";
498 text += tr("Climb rate: %1 m/s").arg(slope, 0, 'f', fabs(slope)<1.0 ? 2 : 1) + "\n";
500 ui->labelCursors->setText(text);
503 QString LogsDialog::formatTimeDelta(double timeDelta)
505 if (abs(int(timeDelta)) < 10) {
506 return QString("%1 s").arg(timeDelta, 1, 'f', 1);
509 int seconds = (int)round(fabs(timeDelta));
510 int hours = seconds / 3600;
511 seconds %= 3600;
512 int minutes = seconds / 60;
513 seconds %= 60;
515 if (hours) {
516 return QString("%1h:%2:%3").arg(hours).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'));
518 else if (minutes) {
519 return QString("%1m:%2").arg(minutes).arg(seconds, 2, 10, QChar('0'));
521 else {
522 return QString("%1 s").arg(seconds);
526 void LogsDialog::mouseWheel()
528 // if an axis is selected, only allow the direction of that axis to be zoomed
529 // if no axis is selected, both directions may be zoomed
530 int orientation=0;
531 if (ui->ZoomX_ChkB->isChecked()) {
532 orientation|=Qt::Horizontal;
534 if (ui->ZoomY_ChkB->isChecked()) {
535 orientation|=Qt::Vertical;
537 if (orientation) {
538 axisRect->setRangeZoom((Qt::Orientation)orientation);
539 } else {
540 axisRect->setRangeZoom(Qt::Horizontal|Qt::Vertical);
544 void LogsDialog::removeAllGraphs()
546 ui->customPlot->clearGraphs();
547 ui->customPlot->clearItems();
548 ui->customPlot->legend->setVisible(false);
549 rightLegend->clearItems();
550 rightLegend->setVisible(false);
551 axisRect->axis(QCPAxis::atRight)->setSelectedParts(QCPAxis::spNone);
552 axisRect->axis(QCPAxis::atRight)->setVisible(false);
553 axisRect->axis(QCPAxis::atLeft)->setSelectedParts(QCPAxis::spNone);
554 axisRect->axis(QCPAxis::atLeft)->setTickLabels(false);
555 axisRect->axis(QCPAxis::atLeft, 1)->setVisible(false);
556 axisRect->axis(QCPAxis::atLeft, 1)->setSelectedParts(QCPAxis::spNone);
557 axisRect->axis(QCPAxis::atRight, 1)->setVisible(false);
558 axisRect->axis(QCPAxis::atRight, 1)->setSelectedParts(QCPAxis::spNone);
559 axisRect->axis(QCPAxis::atBottom)->setSelectedParts(QCPAxis::spNone);
560 ui->customPlot->replot();
561 tracerMaxAlt = 0;
562 cursorA = 0;
563 cursorB = 0;
564 cursorLine = 0;
565 ui->labelCursors->setText("");
568 void LogsDialog::on_fileOpen_BT_clicked()
570 QString fileName = QFileDialog::getOpenFileName(this, tr("Select your log file"), g.logDir());
571 if (!fileName.isEmpty()) {
572 g.logDir(fileName);
573 ui->FileName_LE->setText(fileName);
574 if (cvsFileParse()) {
575 ui->FieldsTW->clear();
576 ui->logTable->clear();
577 ui->FieldsTW->setShowGrid(false);
578 ui->FieldsTW->setContentsMargins(0,0,0,0);
579 ui->FieldsTW->setRowCount(csvlog.at(0).count()-2);
580 ui->FieldsTW->setColumnCount(1);
581 ui->FieldsTW->setHorizontalHeaderLabels(QStringList(tr("Available fields")));
582 ui->logTable->setSelectionBehavior(QAbstractItemView::SelectRows);
583 for (int i=2; i<csvlog.at(0).count(); i++) {
584 QTableWidgetItem* item= new QTableWidgetItem(csvlog.at(0).at(i));
585 ui->FieldsTW->setItem(i-2, 0, item);
587 ui->FieldsTW->resizeRowsToContents();
588 ui->logTable->setColumnCount(csvlog.at(0).count());
589 ui->logTable->setRowCount(csvlog.count()-1);
590 ui->logTable->setHorizontalHeaderLabels(csvlog.at(0));
592 QAbstractItemModel *model = ui->logTable->model();
593 for (int i=1; i<csvlog.count(); i++) {
594 for (int j=0; j<csvlog.at(0).count(); j++) {
595 model->setData(model->index(i - 1, j, QModelIndex()), csvlog.at(i).at(j));
599 ui->logTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
600 QVarLengthArray<int> sizes;
601 for (int i = 0; i < ui->logTable->columnCount(); i++) {
602 sizes.append(ui->logTable->columnWidth(i));
604 ui->logTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
605 for (int i = 0; i < ui->logTable->columnCount(); i++) {
606 ui->logTable->setColumnWidth(i, sizes.at(i));
612 void LogsDialog::saveSession()
614 int index = ui->sessions_CB->currentIndex();
615 // ignore index 0 is its all sessions combined
616 if(index > 0) {
617 int n = csvlog.count();
618 QList<QStringList> sessionCsvLog;
619 // add CSV headers from first row of source file
620 sessionCsvLog.push_back(csvlog[0]);
621 // find session breaks
622 int currentSession = 0;
623 QDateTime lastvalue;
624 for (int i = 1; i < n; i++) {
625 QDateTime tmp = getRecordTimeStamp(i);
626 if (!lastvalue.isValid() || lastvalue.secsTo(tmp) > 60) {
627 currentSession++;
629 lastvalue = tmp;
630 if(currentSession == index) {
631 // add records to filtered list
632 sessionCsvLog.push_back(csvlog[i]);
634 else if (currentSession > index) {
635 break;
638 // save the filtered records to a new file
639 QString newFilename = logFilename;
640 newFilename.append(QString("-Session%1.csv").arg(index));
641 QString filename = QFileDialog::getSaveFileName(this, "Save log", newFilename, "CSV files (.csv);", 0, 0); // getting the filename (full path)
642 QFile data(filename);
643 if(data.open(QFile::WriteOnly |QFile::Truncate)) {
644 QTextStream output(&data);
645 int numRecords = sessionCsvLog.count();
646 for(int i = 0; i < numRecords; i++){
647 output << sessionCsvLog[i].join(",") << '\n';
650 sessionCsvLog.clear();
654 bool LogsDialog::cvsFileParse()
656 QFile file(ui->FileName_LE->text());
657 int errors=0;
658 int lines=-1;
660 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // reading HEX TEXT file
661 return false;
663 else {
664 csvlog.clear();
665 logFilename.clear();
666 QTextStream inputStream(&file);
667 QString buffer = file.readLine();
669 if (buffer.startsWith("Date,Time")) {
670 file.reset();
672 else {
673 return false;
676 int numfields=-1;
677 while (!file.atEnd()) {
678 QString line = file.readLine().trimmed();
679 QStringList columns = line.split(',');
680 if (numfields==-1) {
681 numfields=columns.count();
683 if (columns.count()==numfields) {
684 csvlog.append(columns);
686 else {
687 errors++;
689 lines++;
692 logFilename = QFileInfo(file.fileName()).baseName();
695 file.close();
696 if (errors > 1) {
697 QMessageBox::warning(this, CPN_STR_APP_NAME, tr("The selected logfile contains %1 invalid lines out of %2 total lines").arg(errors).arg(lines));
700 int n = csvlog.count();
701 if (n == 1) {
702 csvlog.clear();
703 return false;
706 plotLock = true;
707 setFlightSessions();
708 plotLock = false;
710 return true;
713 struct FlightSession {
714 QDateTime start;
715 QDateTime end;
718 QDateTime LogsDialog::getRecordTimeStamp(int index)
720 QString tstamp = csvlog.at(index).at(0) + " " + csvlog.at(index).at(1);
721 if (csvlog.at(index).at(1).contains("."))
722 return QDateTime::fromString(tstamp, "yyyy-MM-dd HH:mm:ss.zzz");
723 return QDateTime::fromString(tstamp, "yyyy-MM-dd HH:mm:ss");
726 QString LogsDialog::generateDuration(const QDateTime & start, const QDateTime & end)
728 int secs = start.secsTo(end);
729 QString durationString;
730 if (secs >= 3600) {
731 durationString = QString("%1:").arg(secs/3600);
732 secs %= 3600;
734 durationString += QString("%1:%2").arg(secs/60, 2, 10, QChar('0')).arg(secs%60, 2, 10, QChar('0'));
735 return durationString;
738 void LogsDialog::setFlightSessions()
740 ui->sessions_CB->clear();
741 ui->SaveSession_PB->setEnabled(false);
743 int n = csvlog.count();
744 // qDebug() << "records" << n;
746 // find session breaks
747 QList<int> sessions;
748 QDateTime lastvalue;
749 for (int i = 1; i < n; i++) {
750 QDateTime tmp = getRecordTimeStamp(i);
751 if (!lastvalue.isValid() || lastvalue.secsTo(tmp) > 60) {
752 sessions.push_back(i-1);
753 // qDebug() << "session index" << i-1;
755 lastvalue = tmp;
757 sessions.push_back(n-1);
759 //now construct a list of sessions with their times
760 //total time
761 int noSesions = sessions.size()-1;
762 QString label = QString("%1 ").arg(noSesions);
763 label += tr(noSesions > 1 ? "sessions" : "session");
764 label += " <" + tr("total duration ") + generateDuration(getRecordTimeStamp(1), getRecordTimeStamp(n-1)) + ">";
765 ui->sessions_CB->addItem(label);
767 // add individual sessions
768 if (sessions.size() > 2) {
769 for (int i = 1; i < sessions.size(); i++) {
770 QDateTime sessionStart = getRecordTimeStamp(sessions.at(i-1)+1);
771 QDateTime sessionEnd = getRecordTimeStamp(sessions.at(i));
772 QString label = sessionStart.toString("HH:mm:ss") + " <" + tr("duration ") + generateDuration(sessionStart, sessionEnd) + ">";
773 ui->sessions_CB->addItem(label, sessions.at(i-1));
774 // qDebug() << "added label" << label << sessions.at(i-1);
779 void LogsDialog::on_sessions_CB_currentIndexChanged(int index)
781 if (plotLock) return;
782 plotLock = true;
784 ui->logTable->clearSelection();
786 if (index != 0) {
787 int bottom;
788 if (index < ui->sessions_CB->count() - 1) {
789 bottom = ui->sessions_CB->itemData(index + 1, Qt::UserRole).toInt();
790 } else {
791 bottom = ui->logTable->rowCount();
794 QModelIndex topLeft = ui->logTable->model()->index(
795 ui->sessions_CB->itemData(index, Qt::UserRole).toInt(), 0 , QModelIndex());
796 QModelIndex bottomRight = ui->logTable->model()->index(
797 bottom - 1, ui->logTable->columnCount() - 1, QModelIndex());
799 QItemSelection selection(topLeft, bottomRight);
800 ui->logTable->selectionModel()->select(selection, QItemSelectionModel::Select);
802 ui->SaveSession_PB->setEnabled(true);
804 else {
805 ui->SaveSession_PB->setEnabled(false);
808 plotLock = false;
809 plotLogs();
812 void LogsDialog::plotLogs()
814 if (plotLock) return;
816 if (!ui->FieldsTW->selectedItems().length()) {
817 removeAllGraphs();
818 return;
821 plotsCollection plots;
823 QModelIndexList selection = ui->logTable->selectionModel()->selectedRows();
824 int rowCount = selection.length();
825 bool hasLogSelection;
826 QVarLengthArray<int> selectedRows;
828 if (rowCount) {
829 hasLogSelection = true;
830 foreach (QModelIndex index, selection) {
831 selectedRows.append(index.row());
833 std::sort(selectedRows.begin(), selectedRows.end());
834 } else {
835 hasLogSelection = false;
836 rowCount = ui->logTable->rowCount();
839 plots.min_x = QDateTime::currentDateTime().toTime_t();
840 plots.max_x = 0;
842 foreach (QTableWidgetItem *plot, ui->FieldsTW->selectedItems()) {
843 coords_t plotCoords;
844 int plotColumn = plot->row() + 2; // Date and Time first
846 plotCoords.min_y = INVALID_MIN;
847 plotCoords.max_y = INVALID_MAX;
848 plotCoords.yaxis = firstLeft;
849 plotCoords.name = plot->text();
851 for (int row = 0; row < rowCount; row++) {
852 QTableWidgetItem *logValue;
853 double y;
854 double time;
855 QString time_str;
857 if (hasLogSelection) {
858 logValue = ui->logTable->item(selectedRows.at(row), plotColumn);
859 time_str = ui->logTable->item(selectedRows.at(row), 0)->text() +
860 QString(" ") + ui->logTable->item(selectedRows.at(row), 1)->text();
861 } else {
862 logValue = ui->logTable->item(row, plotColumn);
863 time_str = ui->logTable->item(row, 0)->text() + QString(" ") +
864 ui->logTable->item(row, 1)->text();
867 y = logValue->text().toDouble();
868 plotCoords.y.push_back(y);
870 if (plotCoords.min_y > y) plotCoords.min_y = y;
871 if (plotCoords.max_y < y) plotCoords.max_y = y;
873 if (time_str.contains('.')) {
874 time = QDateTime::fromString(time_str, "yyyy-MM-dd HH:mm:ss.zzz")
875 .toTime_t();
876 time += time_str.mid(time_str.indexOf('.')).toDouble();
877 } else {
878 time = QDateTime::fromString(time_str, "yyyy-MM-dd HH:mm:ss")
879 .toTime_t();
881 plotCoords.x.push_back(time);
883 if (plots.min_x > time) plots.min_x = time;
884 if (plots.max_x < time) plots.max_x = time;
887 double range_inc = (plotCoords.max_y - plotCoords.min_y) / 100;
888 if (range_inc == 0) range_inc = 1;
889 plotCoords.max_y += range_inc;
890 plotCoords.min_y -= range_inc;
892 plots.coords.append(plotCoords);
895 yAxesRanges[firstLeft].min = plots.coords.at(0).min_y;
896 yAxesRanges[firstLeft].max = plots.coords.at(0).max_y;
897 for (int i = firstRight; i < AXES_LIMIT; i++) {
898 yAxesRanges[i].min = INVALID_MIN;
899 yAxesRanges[i].max = INVALID_MAX;
901 plots.tooManyRanges = false;
903 for (int i = 1; i < plots.coords.size(); i++) {
904 double actualRange = yAxesRanges[firstLeft].max - yAxesRanges[firstLeft].min;
905 double thisRange = plots.coords.at(i).max_y - plots.coords.at(i).min_y;
907 while (yAxesRanges[plots.coords.at(i).yaxis].max != INVALID_MAX &&
908 (thisRange > actualRange * 1.3 || thisRange * 1.3 < actualRange ||
909 plots.coords.at(i).min_y > yAxesRanges[plots.coords.at(i).yaxis].max ||
910 plots.coords.at(i).max_y < yAxesRanges[plots.coords.at(i).yaxis].min)
913 switch (plots.coords[i].yaxis) {
914 case firstLeft:
915 plots.coords[i].yaxis = firstRight;
916 break;
917 case firstRight:
918 plots.coords[i].yaxis = secondLeft;
919 break;
920 case secondLeft:
921 plots.coords[i].yaxis = secondRight;
922 break;
923 case secondRight:
924 plots.tooManyRanges = true;
925 break;
926 default:
927 break;
929 if (plots.tooManyRanges) break;
931 actualRange = yAxesRanges[plots.coords.at(i).yaxis].max
932 - yAxesRanges[plots.coords.at(i).yaxis].min;
935 if (plots.tooManyRanges) {
936 break;
937 } else {
938 if (plots.coords.at(i).min_y < yAxesRanges[plots.coords.at(i).yaxis].min) {
939 yAxesRanges[plots.coords.at(i).yaxis].min = plots.coords.at(i).min_y;
941 if (plots.coords.at(i).max_y > yAxesRanges[plots.coords.at(i).yaxis].max) {
942 yAxesRanges[plots.coords.at(i).yaxis].max = plots.coords.at(i).max_y;
947 if (plots.tooManyRanges) {
948 yAxesRanges[firstLeft].max = 101;
949 yAxesRanges[firstLeft].min = -1;
950 yAxesRanges[firstRight].max = INVALID_MAX;
951 yAxesRanges[firstRight].min = INVALID_MIN;
952 yAxesRanges[secondLeft].max = INVALID_MAX;
953 yAxesRanges[secondLeft].min = INVALID_MIN;
954 yAxesRanges[secondRight].max = INVALID_MAX;
955 yAxesRanges[secondRight].min = INVALID_MIN;
957 for (int i = 0; i < plots.coords.size(); i++) {
958 plots.coords[i].yaxis = firstLeft;
960 double factor = 100 / (plots.coords.at(i).max_y - plots.coords.at(i).min_y);
961 for (int j = 0; j < plots.coords.at(i).y.count(); j++) {
962 plots.coords[i].y[j] = factor * (plots.coords.at(i).y.at(j) - plots.coords.at(i).min_y);
965 } else {
966 for (int i = firstRight; i < AXES_LIMIT; i++) {
967 if (yAxesRanges[i].max == INVALID_MAX) break;
969 yAxesRatios[i] = (yAxesRanges[i].max - yAxesRanges[i].min) /
970 (yAxesRanges[firstLeft].max - yAxesRanges[firstLeft].min);
974 removeAllGraphs();
976 axisRect->axis(QCPAxis::atBottom)->setRange(plots.min_x, plots.max_x);
978 axisRect->axis(QCPAxis::atLeft)->setRange(yAxesRanges[firstLeft].min,
979 yAxesRanges[firstLeft].max);
981 if (plots.tooManyRanges) {
982 axisRect->axis(QCPAxis::atLeft)->setTickLabels(false);
983 } else {
984 axisRect->axis(QCPAxis::atLeft)->setTickLabels(true);
987 if (yAxesRanges[firstRight].max != INVALID_MAX) {
988 axisRect->axis(QCPAxis::atRight)->setRange(yAxesRanges[firstRight].min,
989 yAxesRanges[firstRight].max);
990 axisRect->axis(QCPAxis::atRight)->setVisible(true);
992 rightLegend->setVisible(true);
994 if (yAxesRanges[secondLeft].max != INVALID_MAX) {
995 axisRect->axis(QCPAxis::atLeft, 1)->setVisible(true);
996 axisRect->axis(QCPAxis::atLeft, 1)->setRange(yAxesRanges[secondLeft].min,
997 yAxesRanges[secondLeft].max);
999 if (yAxesRanges[secondRight].max != INVALID_MAX) {
1000 axisRect->axis(QCPAxis::atRight, 1)->setVisible(true);
1001 axisRect->axis(QCPAxis::atRight, 1)->setRange(yAxesRanges[secondRight].min,
1002 yAxesRanges[secondRight].max);
1007 for (int i = 0; i < plots.coords.size(); i++) {
1008 switch (plots.coords[i].yaxis) {
1009 case firstLeft:
1010 ui->customPlot->addGraph();
1011 if (yAxesRanges[secondLeft].max != INVALID_MAX) {
1012 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (L1)"));
1013 } else {
1014 ui->customPlot->graph(i)->setName(plots.coords.at(i).name);
1016 ui->customPlot->legend->addItem(
1017 new QCPPlottableLegendItem(ui->customPlot->legend, ui->customPlot->graph(i)));
1018 break;
1019 case firstRight:
1020 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1021 axisRect->axis(QCPAxis::atRight));
1022 if (yAxesRanges[secondRight].max != INVALID_MAX) {
1023 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (R1)"));
1024 } else {
1025 ui->customPlot->graph(i)->setName(plots.coords.at(i).name);
1027 rightLegend->addItem(
1028 new QCPPlottableLegendItem(rightLegend, ui->customPlot->graph(i)));
1029 break;
1030 case secondLeft:
1031 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1032 axisRect->axis(QCPAxis::atLeft, 1));
1033 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (L2)"));
1034 ui->customPlot->legend->addItem(
1035 new QCPPlottableLegendItem(ui->customPlot->legend, ui->customPlot->graph(i)));
1036 break;
1037 case secondRight:
1038 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1039 axisRect->axis(QCPAxis::atRight, 1));
1040 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (R2)"));
1041 rightLegend->addItem(
1042 new QCPPlottableLegendItem(rightLegend, ui->customPlot->graph(i)));
1043 break;
1044 default:
1045 break;
1048 ui->customPlot->graph(i)->setData(plots.coords.at(i).x,
1049 plots.coords.at(i).y);
1050 pen.setColor(colors.at(i % colors.size()));
1051 ui->customPlot->graph(i)->setPen(pen);
1053 if (!tracerMaxAlt && (plots.coords.at(i).name.endsWith("(m)") ||
1054 plots.coords.at(i).name.endsWith(" Alt") ||
1055 plots.coords.at(i).name.endsWith("(ft)"))) {
1056 addMaxAltitudeMarker(plots.coords.at(i), ui->customPlot->graph(i));
1057 countNumberOfThrows(plots.coords.at(i), ui->customPlot->graph(i));
1058 addCursor(&cursorA, ui->customPlot->graph(i), Qt::blue);
1059 addCursor(&cursorB, ui->customPlot->graph(i), Qt::red);
1060 addCursorLine(&cursorLine, ui->customPlot->graph(i), Qt::black);
1061 updateCursorsLabel();
1065 ui->customPlot->legend->setVisible(true);
1066 ui->customPlot->replot();
1069 void LogsDialog::yAxisChangeRanges(QCPRange range)
1071 if (axisRect->axis(QCPAxis::atRight)->visible()) {
1072 double lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1073 yAxesRatios[firstRight];
1074 double upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1075 yAxesRatios[firstRight];
1077 yAxesRanges[firstRight].min += lowerChange;
1078 yAxesRanges[firstRight].max += upperChange;
1079 axisRect->axis(QCPAxis::atRight)->setRange(yAxesRanges[firstRight].min,
1080 yAxesRanges[firstRight].max);
1082 if (axisRect->axisCount(QCPAxis::atLeft) == 2) {
1083 lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1084 yAxesRatios[secondLeft];
1085 upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1086 yAxesRatios[secondLeft];
1088 yAxesRanges[secondLeft].min += lowerChange;
1089 yAxesRanges[secondLeft].max += upperChange;
1090 axisRect->axis(QCPAxis::atLeft, 1)->setRange(yAxesRanges[secondLeft].min,
1091 yAxesRanges[secondLeft].max);
1093 if (axisRect->axisCount(QCPAxis::atRight) == 2) {
1094 lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1095 yAxesRatios[secondRight];
1096 upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1097 yAxesRatios[secondRight];
1099 yAxesRanges[secondRight].min += lowerChange;
1100 yAxesRanges[secondRight].max += upperChange;
1101 axisRect->axis(QCPAxis::atRight, 1)->setRange(yAxesRanges[secondRight].min,
1102 yAxesRanges[secondRight].max);
1106 yAxesRanges[firstLeft].min = range.lower;
1107 yAxesRanges[firstLeft].max = range.upper;
1112 void LogsDialog::addMaxAltitudeMarker(const coords_t & c, QCPGraph * graph) {
1113 // find max altitude
1114 int positionIndex = 0;
1115 double maxAlt = -100000;
1117 for(int i=0; i<c.x.count(); ++i) {
1118 double alt = c.y.at(i);
1119 if (alt > maxAlt) {
1120 maxAlt = alt;
1121 positionIndex = i;
1122 // qDebug() << "max alt: " << maxAlt << "@" << result;
1125 // qDebug() << "max alt: " << maxAlt << "@" << positionIndex;
1127 // add max altitude marker
1128 tracerMaxAlt = new QCPItemTracer(ui->customPlot);
1129 ui->customPlot->addItem(tracerMaxAlt);
1130 tracerMaxAlt->setGraph(graph);
1131 tracerMaxAlt->setInterpolating(true);
1132 tracerMaxAlt->setStyle(QCPItemTracer::tsSquare);
1133 tracerMaxAlt->setPen(QPen(Qt::blue));
1134 tracerMaxAlt->setBrush(Qt::NoBrush);
1135 tracerMaxAlt->setSize(7);
1136 tracerMaxAlt->setGraphKey(c.x.at(positionIndex));
1137 tracerMaxAlt->updatePosition();
1140 void LogsDialog::countNumberOfThrows(const coords_t & c, QCPGraph * graph)
1142 #if 0
1143 // find all launches
1144 // TODO
1145 double startTime = c.x.at(0);
1147 for(int i=0; i<c.x.count(); ++i) {
1148 double alt = c.y.at(i);
1149 double time = c.x.at(i);
1151 #endif
1154 void LogsDialog::addCursor(QCPItemTracer ** cursor, QCPGraph * graph, const QColor & color) {
1155 QCPItemTracer * c = new QCPItemTracer(ui->customPlot);
1156 ui->customPlot->addItem(c);
1157 c->setGraph(graph);
1158 c->setInterpolating(false);
1159 c->setStyle(QCPItemTracer::tsCrosshair);
1160 QPen pen(color);
1161 pen.setStyle(Qt::DashLine);
1162 c->setPen(pen);
1163 c->setBrush(color);
1164 c->setSize(7);
1165 c->setVisible(false);
1166 *cursor = c;
1169 void LogsDialog::addCursorLine(QCPItemStraightLine ** line, QCPGraph * graph, const QColor & color) {
1170 QCPItemStraightLine * l = new QCPItemStraightLine(ui->customPlot);
1171 ui->customPlot->addItem(l);
1172 l->point1->setParentAnchor(cursorA->position);
1173 l->point2->setParentAnchor(cursorB->position);
1174 QPen pen(color);
1175 pen.setStyle(Qt::DashLine);
1176 l->setPen(pen);
1177 l->setVisible(false);
1178 *line = l;