Updated French Translation (#5484)
[opentx.git] / companion / src / logsdialog.cpp
blobb2feb37ae89c0c104da4d721071252dd1157a6f5
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(on_saveSession_BT_clicked()));
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 not 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 QString geIconFilename = generateProcessUniqueTempFileName("track0.png");
328 if (QFile::exists(geIconFilename)) {
329 QFile::remove(geIconFilename);
331 QFile::copy(":/images/track0.png", geIconFilename);
333 QString geFilename = generateProcessUniqueTempFileName("flight.kml");
334 if (QFile::exists(geFilename)) {
335 QFile::remove(geFilename);
337 QFile geFile(geFilename);
338 if (!geFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
339 QMessageBox::warning(this, tr("Error"),
340 tr("Cannot write file %1:\n%2.")
341 .arg(geFilename)
342 .arg(geFile.errorString()));
343 return;
346 QTextStream outputStream(&geFile);
348 // file header
349 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";
350 outputStream << "\t<Document>\n\t\t<name>" << logFilename << "</name>\n";
351 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>file://" << 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";
352 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>file://" << 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";
353 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";
354 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";
355 outputStream << "\t\t<Schema id=\"schema\">\n";
356 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";
358 // declare additional fields
359 for (int i=0; i<dataPoints.at(0).count()-2; i++) {
360 if (ui->FieldsTW->item(i, 0) && ui->FieldsTW->item(i, 0)->isSelected() && !nondataCols.contains(i+2)) {
361 QString origName = dataPoints.at(0).at(i+2);
362 QString safeName = origName;
363 safeName.replace(" ","_");
364 outputStream << "\t\t\t<gx:SimpleArrayField name=\""<< safeName <<"\" ";
365 outputStream << "type=\"string\""; // additional fields have fixed type: string
366 outputStream << ">\n\t\t\t\t<displayName>" << origName << "</displayName>\n\t\t\t</gx:SimpleArrayField>\n";
370 QString planeName;
371 if (logFilename.indexOf("-")>0) {
372 planeName=logFilename.left(logFilename.indexOf("-"));
373 } else {
374 planeName=logFilename;
377 outputStream << "\t\t</Schema>\n";
378 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>";
379 outputStream << "\n\t\t\t\t<styleUrl>#multiTrack</styleUrl>";
380 outputStream << "\n\t\t\t\t<gx:Track>\n";
381 outputStream << "\n\t\t\t\t\t<altitudeMode>absolute</altitudeMode>\n";
383 // time data points
384 for (int i=1; i<n; i++) {
385 QString tstamp=dataPoints.at(i).at(0)+QString("T")+dataPoints.at(i).at(1)+QString("Z");
386 outputStream << "\t\t\t\t\t<when>"<< tstamp <<"</when>\n";
389 // coordinate data points
390 outputStream.setRealNumberNotation(QTextStream::FixedNotation);
391 outputStream.setRealNumberPrecision(8);
392 for (int i=1; i<n; i++) {
393 GpsCoord coord = extractGpsCoordinates(dataPoints.at(i).at(gpscol));
394 int altitude = altcol ? (dataPoints.at(i).at(altcol).toFloat() * altMultiplier) : 0;
395 outputStream << "\t\t\t\t\t<gx:coord>" << coord.longitude << " " << coord.latitude << " " << altitude << " </gx:coord>\n" ;
398 // additional data for data points
399 outputStream << "\t\t\t\t\t<ExtendedData>\n\t\t\t\t\t\t<SchemaData schemaUrl=\"#schema\">\n";
401 if (speedcol) {
402 // gps speed data points
403 outputStream << "\t\t\t\t\t\t\t<gx:SimpleArrayData name=\"GPSSpeed\">\n";
404 for (int i=1; i<n; i++) {
405 outputStream << "\t\t\t\t\t\t\t\t<gx:value>"<< dataPoints.at(i).at(speedcol) <<"</gx:value>\n";
407 outputStream << "\t\t\t\t\t\t\t</gx:SimpleArrayData>\n";
410 // add values for additional fields
411 for (int i=0; i<dataPoints.at(0).count()-2; i++) {
412 if (ui->FieldsTW->item(i, 0) && ui->FieldsTW->item(i, 0)->isSelected() && !nondataCols.contains(i+2)) {
413 QString safeName = dataPoints.at(0).at(i+2);;
414 safeName.replace(" ","_");
415 outputStream << "\t\t\t\t\t\t\t<gx:SimpleArrayData name=\""<< safeName <<"\">\n";
416 for (int j=1; j<n; j++) {
417 outputStream << "\t\t\t\t\t\t\t\t<gx:value>"<< dataPoints.at(j).at(i+2) <<"</gx:value>\n";
419 outputStream << "\t\t\t\t\t\t\t</gx:SimpleArrayData>\n";
423 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>";
424 geFile.close();
426 QString gePath = g.gePath();
427 QStringList parameters;
428 #ifdef __APPLE__
429 parameters << "-a";
430 parameters << gePath;
431 gePath = "/usr/bin/open";
432 #endif
433 parameters << geFilename;
434 QProcess *process = new QProcess(this);
435 process->start(gePath, parameters);
438 void LogsDialog::on_mapsButton_clicked()
440 ui->FieldsTW->setDisabled(true);
441 ui->logTable->setDisabled(true);
443 exportToGoogleEarth();
445 ui->FieldsTW->setDisabled(false);
446 ui->logTable->setDisabled(false);
449 void LogsDialog::mousePress(QMouseEvent * event)
451 // if an axis is selected, only allow the direction of that axis to be dragged
452 // if no axis is selected, both directions may be dragged
454 if (axisRect->axis(QCPAxis::atBottom)->selectedParts().testFlag(QCPAxis::spAxis))
455 axisRect->setRangeDrag(axisRect->axis(QCPAxis::atBottom)->orientation());
456 else if (axisRect->axis(QCPAxis::atLeft)->selectedParts().testFlag(QCPAxis::spAxis))
457 axisRect->setRangeDrag(axisRect->axis(QCPAxis::atLeft)->orientation());
458 else
459 axisRect->setRangeDrag(Qt::Horizontal | Qt::Vertical);
461 if (event->button() == Qt::RightButton) {
462 double x = axisRect->axis(QCPAxis::atBottom)->pixelToCoord(event->pos().x());
463 placeCursor(x, event->modifiers() & Qt::ShiftModifier);
467 void LogsDialog::placeCursor(double x, bool second)
469 QCPItemTracer * cursor = second ? cursorB : cursorA;
471 if (cursor) {
472 cursor->setGraphKey(x);
473 cursor->updatePosition();
474 cursor->setVisible(true);
477 if (cursorA && cursorB) {
478 updateCursorsLabel();
482 void LogsDialog::updateCursorsLabel()
484 QString text = QString("Max Altitude: %1 m\n").arg(tracerMaxAlt->position->value(), 0, 'f', 1);
485 if (cursorA->visible() ) {
486 text += tr("Cursor A: %1 m").arg(cursorA->position->value(), 0, 'f', 1) + "\n";
488 if (cursorB->visible() ) {
489 text += tr("Cursor B: %1 m").arg(cursorB->position->value(), 0, 'f', 1) + "\n";
491 if (cursorA->visible() && cursorB->visible()) {
492 cursorLine->setVisible(true);
493 // calc deltas
494 double deltaX = cursorB->position->key() - cursorA->position->key();
495 double deltaY = cursorB->position->value() - cursorA->position->value();
496 double slope = 0;
497 if (deltaX != 0) {
498 slope = deltaY / deltaX;
500 qDebug() << "Cursors: dt:" << formatTimeDelta(deltaX) << "dy:" << deltaY << "rate:" << slope;
501 text += tr("Time delta: %1").arg(formatTimeDelta(deltaX)) + "\n";
502 text += tr("Climb rate: %1 m/s").arg(slope, 0, 'f', fabs(slope)<1.0 ? 2 : 1) + "\n";
504 ui->labelCursors->setText(text);
507 QString LogsDialog::formatTimeDelta(double timeDelta)
509 if (abs(int(timeDelta)) < 10) {
510 return QString("%1 s").arg(timeDelta, 1, 'f', 1);
513 int seconds = (int)round(fabs(timeDelta));
514 int hours = seconds / 3600;
515 seconds %= 3600;
516 int minutes = seconds / 60;
517 seconds %= 60;
519 if (hours) {
520 return QString("%1h:%2:%3").arg(hours).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'));
522 else if (minutes) {
523 return QString("%1m:%2").arg(minutes).arg(seconds, 2, 10, QChar('0'));
525 else {
526 return QString("%1 s").arg(seconds);
530 void LogsDialog::mouseWheel()
532 // if an axis is selected, only allow the direction of that axis to be zoomed
533 // if no axis is selected, both directions may be zoomed
534 int orientation=0;
535 if (ui->ZoomX_ChkB->isChecked()) {
536 orientation|=Qt::Horizontal;
538 if (ui->ZoomY_ChkB->isChecked()) {
539 orientation|=Qt::Vertical;
541 if (orientation) {
542 axisRect->setRangeZoom((Qt::Orientation)orientation);
543 } else {
544 axisRect->setRangeZoom(Qt::Horizontal|Qt::Vertical);
548 void LogsDialog::removeAllGraphs()
550 ui->customPlot->clearGraphs();
551 ui->customPlot->clearItems();
552 ui->customPlot->legend->setVisible(false);
553 rightLegend->clearItems();
554 rightLegend->setVisible(false);
555 axisRect->axis(QCPAxis::atRight)->setSelectedParts(QCPAxis::spNone);
556 axisRect->axis(QCPAxis::atRight)->setVisible(false);
557 axisRect->axis(QCPAxis::atLeft)->setSelectedParts(QCPAxis::spNone);
558 axisRect->axis(QCPAxis::atLeft)->setTickLabels(false);
559 axisRect->axis(QCPAxis::atLeft, 1)->setVisible(false);
560 axisRect->axis(QCPAxis::atLeft, 1)->setSelectedParts(QCPAxis::spNone);
561 axisRect->axis(QCPAxis::atRight, 1)->setVisible(false);
562 axisRect->axis(QCPAxis::atRight, 1)->setSelectedParts(QCPAxis::spNone);
563 axisRect->axis(QCPAxis::atBottom)->setSelectedParts(QCPAxis::spNone);
564 ui->customPlot->replot();
565 tracerMaxAlt = 0;
566 cursorA = 0;
567 cursorB = 0;
568 cursorLine = 0;
569 ui->labelCursors->setText("");
572 void LogsDialog::on_fileOpen_BT_clicked()
574 QString fileName = QFileDialog::getOpenFileName(this, tr("Select your log file"), g.logDir());
575 if (!fileName.isEmpty()) {
576 g.logDir(fileName);
577 ui->FileName_LE->setText(fileName);
578 if (cvsFileParse()) {
579 ui->FieldsTW->clear();
580 ui->logTable->clear();
581 ui->FieldsTW->setShowGrid(false);
582 ui->FieldsTW->setContentsMargins(0,0,0,0);
583 ui->FieldsTW->setRowCount(csvlog.at(0).count()-2);
584 ui->FieldsTW->setColumnCount(1);
585 ui->FieldsTW->setHorizontalHeaderLabels(QStringList(tr("Available fields")));
586 ui->logTable->setSelectionBehavior(QAbstractItemView::SelectRows);
587 for (int i=2; i<csvlog.at(0).count(); i++) {
588 QTableWidgetItem* item= new QTableWidgetItem(csvlog.at(0).at(i));
589 ui->FieldsTW->setItem(i-2, 0, item);
591 ui->FieldsTW->resizeRowsToContents();
592 ui->logTable->setColumnCount(csvlog.at(0).count());
593 ui->logTable->setRowCount(csvlog.count()-1);
594 ui->logTable->setHorizontalHeaderLabels(csvlog.at(0));
596 QAbstractItemModel *model = ui->logTable->model();
597 for (int i=1; i<csvlog.count(); i++) {
598 for (int j=0; j<csvlog.at(0).count(); j++) {
599 model->setData(model->index(i - 1, j, QModelIndex()), csvlog.at(i).at(j));
603 ui->logTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
604 QVarLengthArray<int> sizes;
605 for (int i = 0; i < ui->logTable->columnCount(); i++) {
606 sizes.append(ui->logTable->columnWidth(i));
608 ui->logTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
609 for (int i = 0; i < ui->logTable->columnCount(); i++) {
610 ui->logTable->setColumnWidth(i, sizes.at(i));
616 void LogsDialog::on_saveSession_BT_clicked()
618 int index = ui->sessions_CB->currentIndex();
619 // ignore index 0 is its all sessions combined
620 if(index > 0) {
621 int n = csvlog.count();
622 QList<QStringList> sessionCsvLog;
623 // add CSV headers from first row of source file
624 sessionCsvLog.push_back(csvlog[0]);
625 // find session breaks
626 int currentSession = 0;
627 QDateTime lastvalue;
628 for (int i = 1; i < n; i++) {
629 QDateTime tmp = getRecordTimeStamp(i);
630 if (!lastvalue.isValid() || lastvalue.secsTo(tmp) > 60) {
631 currentSession++;
633 lastvalue = tmp;
634 if(currentSession == index) {
635 // add records to filtered list
636 sessionCsvLog.push_back(csvlog[i]);
638 else if (currentSession > index) {
639 break;
642 // save the filtered records to a new file
643 QString newFilename = logFilename;
644 newFilename.append(QString("-Session%1.csv").arg(index));
645 QString filename = QFileDialog::getSaveFileName(this, "Save log", newFilename, "CSV files (.csv);", 0, 0); // getting the filename (full path)
646 QFile data(filename);
647 if(data.open(QFile::WriteOnly |QFile::Truncate)) {
648 QTextStream output(&data);
649 int numRecords = sessionCsvLog.count();
650 for(int i = 0; i < numRecords; i++){
651 output << sessionCsvLog[i].join(",") << '\n';
654 sessionCsvLog.clear();
658 bool LogsDialog::cvsFileParse()
660 QFile file(ui->FileName_LE->text());
661 int errors=0;
662 int lines=-1;
664 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // reading HEX TEXT file
665 return false;
667 else {
668 csvlog.clear();
669 logFilename.clear();
670 QTextStream inputStream(&file);
671 QString buffer = file.readLine();
673 if (buffer.startsWith("Date,Time")) {
674 file.reset();
676 else {
677 return false;
680 int numfields=-1;
681 while (!file.atEnd()) {
682 QString line = file.readLine().trimmed();
683 QStringList columns = line.split(',');
684 if (numfields==-1) {
685 numfields=columns.count();
687 if (columns.count()==numfields) {
688 csvlog.append(columns);
690 else {
691 errors++;
693 lines++;
696 logFilename = QFileInfo(file.fileName()).baseName();
699 file.close();
700 if (errors > 1) {
701 QMessageBox::warning(this, "Companion", tr("The selected logfile contains %1 invalid lines out of %2 total lines").arg(errors).arg(lines));
704 int n = csvlog.count();
705 if (n == 1) {
706 csvlog.clear();
707 return false;
710 plotLock = true;
711 setFlightSessions();
712 plotLock = false;
714 return true;
717 struct FlightSession {
718 QDateTime start;
719 QDateTime end;
722 QDateTime LogsDialog::getRecordTimeStamp(int index)
724 QString tstamp = csvlog.at(index).at(0) + " " + csvlog.at(index).at(1);
725 if (csvlog.at(index).at(1).contains("."))
726 return QDateTime::fromString(tstamp, "yyyy-MM-dd HH:mm:ss.zzz");
727 return QDateTime::fromString(tstamp, "yyyy-MM-dd HH:mm:ss");
730 QString LogsDialog::generateDuration(const QDateTime & start, const QDateTime & end)
732 int secs = start.secsTo(end);
733 QString durationString;
734 if (secs >= 3600) {
735 durationString = QString("%1:").arg(secs/3600);
736 secs %= 3600;
738 durationString += QString("%1:%2").arg(secs/60, 2, 10, QChar('0')).arg(secs%60, 2, 10, QChar('0'));
739 return durationString;
742 void LogsDialog::setFlightSessions()
744 ui->sessions_CB->clear();
745 ui->SaveSession_PB->setEnabled(false);
747 int n = csvlog.count();
748 // qDebug() << "records" << n;
750 // find session breaks
751 QList<int> sessions;
752 QDateTime lastvalue;
753 for (int i = 1; i < n; i++) {
754 QDateTime tmp = getRecordTimeStamp(i);
755 if (!lastvalue.isValid() || lastvalue.secsTo(tmp) > 60) {
756 sessions.push_back(i-1);
757 // qDebug() << "session index" << i-1;
759 lastvalue = tmp;
761 sessions.push_back(n-1);
763 //now construct a list of sessions with their times
764 //total time
765 int noSesions = sessions.size()-1;
766 QString label = QString("%1 ").arg(noSesions);
767 label += tr(noSesions > 1 ? "sessions" : "session");
768 label += " <" + tr("total duration ") + generateDuration(getRecordTimeStamp(1), getRecordTimeStamp(n-1)) + ">";
769 ui->sessions_CB->addItem(label);
771 // add individual sessions
772 if (sessions.size() > 2) {
773 for (int i = 1; i < sessions.size(); i++) {
774 QDateTime sessionStart = getRecordTimeStamp(sessions.at(i-1)+1);
775 QDateTime sessionEnd = getRecordTimeStamp(sessions.at(i));
776 QString label = sessionStart.toString("HH:mm:ss") + " <" + tr("duration ") + generateDuration(sessionStart, sessionEnd) + ">";
777 ui->sessions_CB->addItem(label, sessions.at(i-1));
778 // qDebug() << "added label" << label << sessions.at(i-1);
783 void LogsDialog::on_sessions_CB_currentIndexChanged(int index)
785 if (plotLock) return;
786 plotLock = true;
788 ui->logTable->clearSelection();
790 if (index != 0) {
791 int bottom;
792 if (index < ui->sessions_CB->count() - 1) {
793 bottom = ui->sessions_CB->itemData(index + 1, Qt::UserRole).toInt();
794 } else {
795 bottom = ui->logTable->rowCount();
798 QModelIndex topLeft = ui->logTable->model()->index(
799 ui->sessions_CB->itemData(index, Qt::UserRole).toInt(), 0 , QModelIndex());
800 QModelIndex bottomRight = ui->logTable->model()->index(
801 bottom - 1, ui->logTable->columnCount() - 1, QModelIndex());
803 QItemSelection selection(topLeft, bottomRight);
804 ui->logTable->selectionModel()->select(selection, QItemSelectionModel::Select);
806 ui->SaveSession_PB->setEnabled(true);
808 else {
809 ui->SaveSession_PB->setEnabled(false);
812 plotLock = false;
813 plotLogs();
816 void LogsDialog::plotLogs()
818 if (plotLock) return;
820 if (!ui->FieldsTW->selectedItems().length()) {
821 removeAllGraphs();
822 return;
825 plotsCollection plots;
827 QModelIndexList selection = ui->logTable->selectionModel()->selectedRows();
828 int rowCount = selection.length();
829 bool hasLogSelection;
830 QVarLengthArray<int> selectedRows;
832 if (rowCount) {
833 hasLogSelection = true;
834 foreach (QModelIndex index, selection) {
835 selectedRows.append(index.row());
837 qSort(selectedRows.begin(), selectedRows.end());
838 } else {
839 hasLogSelection = false;
840 rowCount = ui->logTable->rowCount();
843 plots.min_x = QDateTime::currentDateTime().toTime_t();
844 plots.max_x = 0;
846 foreach (QTableWidgetItem *plot, ui->FieldsTW->selectedItems()) {
847 coords plotCoords;
848 int plotColumn = plot->row() + 2; // Date and Time first
850 plotCoords.min_y = INVALID_MIN;
851 plotCoords.max_y = INVALID_MAX;
852 plotCoords.yaxis = firstLeft;
853 plotCoords.name = plot->text();
855 for (int row = 0; row < rowCount; row++) {
856 QTableWidgetItem *logValue;
857 double y;
858 double time;
859 QString time_str;
861 if (hasLogSelection) {
862 logValue = ui->logTable->item(selectedRows.at(row), plotColumn);
863 time_str = ui->logTable->item(selectedRows.at(row), 0)->text() +
864 QString(" ") + ui->logTable->item(selectedRows.at(row), 1)->text();
865 } else {
866 logValue = ui->logTable->item(row, plotColumn);
867 time_str = ui->logTable->item(row, 0)->text() + QString(" ") +
868 ui->logTable->item(row, 1)->text();
871 y = logValue->text().toDouble();
872 plotCoords.y.push_back(y);
874 if (plotCoords.min_y > y) plotCoords.min_y = y;
875 if (plotCoords.max_y < y) plotCoords.max_y = y;
877 if (time_str.contains('.')) {
878 time = QDateTime::fromString(time_str, "yyyy-MM-dd HH:mm:ss.zzz")
879 .toTime_t();
880 time += time_str.mid(time_str.indexOf('.')).toDouble();
881 } else {
882 time = QDateTime::fromString(time_str, "yyyy-MM-dd HH:mm:ss")
883 .toTime_t();
885 plotCoords.x.push_back(time);
887 if (plots.min_x > time) plots.min_x = time;
888 if (plots.max_x < time) plots.max_x = time;
891 double range_inc = (plotCoords.max_y - plotCoords.min_y) / 100;
892 if (range_inc == 0) range_inc = 1;
893 plotCoords.max_y += range_inc;
894 plotCoords.min_y -= range_inc;
896 plots.coords.append(plotCoords);
899 yAxesRanges[firstLeft].min = plots.coords.at(0).min_y;
900 yAxesRanges[firstLeft].max = plots.coords.at(0).max_y;
901 for (int i = firstRight; i < AXES_LIMIT; i++) {
902 yAxesRanges[i].min = INVALID_MIN;
903 yAxesRanges[i].max = INVALID_MAX;
905 plots.tooManyRanges = false;
907 for (int i = 1; i < plots.coords.size(); i++) {
908 double actualRange = yAxesRanges[firstLeft].max - yAxesRanges[firstLeft].min;
909 double thisRange = plots.coords.at(i).max_y - plots.coords.at(i).min_y;
911 while (yAxesRanges[plots.coords.at(i).yaxis].max != INVALID_MAX &&
912 (thisRange > actualRange * 1.3 || thisRange * 1.3 < actualRange ||
913 plots.coords.at(i).min_y > yAxesRanges[plots.coords.at(i).yaxis].max ||
914 plots.coords.at(i).max_y < yAxesRanges[plots.coords.at(i).yaxis].min)
917 switch (plots.coords[i].yaxis) {
918 case firstLeft:
919 plots.coords[i].yaxis = firstRight;
920 break;
921 case firstRight:
922 plots.coords[i].yaxis = secondLeft;
923 break;
924 case secondLeft:
925 plots.coords[i].yaxis = secondRight;
926 break;
927 case secondRight:
928 plots.tooManyRanges = true;
929 break;
930 default:
931 break;
933 if (plots.tooManyRanges) break;
935 actualRange = yAxesRanges[plots.coords.at(i).yaxis].max
936 - yAxesRanges[plots.coords.at(i).yaxis].min;
939 if (plots.tooManyRanges) {
940 break;
941 } else {
942 if (plots.coords.at(i).min_y < yAxesRanges[plots.coords.at(i).yaxis].min) {
943 yAxesRanges[plots.coords.at(i).yaxis].min = plots.coords.at(i).min_y;
945 if (plots.coords.at(i).max_y > yAxesRanges[plots.coords.at(i).yaxis].max) {
946 yAxesRanges[plots.coords.at(i).yaxis].max = plots.coords.at(i).max_y;
951 if (plots.tooManyRanges) {
952 yAxesRanges[firstLeft].max = 101;
953 yAxesRanges[firstLeft].min = -1;
954 yAxesRanges[firstRight].max = INVALID_MAX;
955 yAxesRanges[firstRight].min = INVALID_MIN;
956 yAxesRanges[secondLeft].max = INVALID_MAX;
957 yAxesRanges[secondLeft].min = INVALID_MIN;
958 yAxesRanges[secondRight].max = INVALID_MAX;
959 yAxesRanges[secondRight].min = INVALID_MIN;
961 for (int i = 0; i < plots.coords.size(); i++) {
962 plots.coords[i].yaxis = firstLeft;
964 double factor = 100 / (plots.coords.at(i).max_y - plots.coords.at(i).min_y);
965 for (int j = 0; j < plots.coords.at(i).y.count(); j++) {
966 plots.coords[i].y[j] = factor * (plots.coords.at(i).y.at(j) - plots.coords.at(i).min_y);
969 } else {
970 for (int i = firstRight; i < AXES_LIMIT; i++) {
971 if (yAxesRanges[i].max == INVALID_MAX) break;
973 yAxesRatios[i] = (yAxesRanges[i].max - yAxesRanges[i].min) /
974 (yAxesRanges[firstLeft].max - yAxesRanges[firstLeft].min);
978 removeAllGraphs();
980 axisRect->axis(QCPAxis::atBottom)->setRange(plots.min_x, plots.max_x);
982 axisRect->axis(QCPAxis::atLeft)->setRange(yAxesRanges[firstLeft].min,
983 yAxesRanges[firstLeft].max);
985 if (plots.tooManyRanges) {
986 axisRect->axis(QCPAxis::atLeft)->setTickLabels(false);
987 } else {
988 axisRect->axis(QCPAxis::atLeft)->setTickLabels(true);
991 if (yAxesRanges[firstRight].max != INVALID_MAX) {
992 axisRect->axis(QCPAxis::atRight)->setRange(yAxesRanges[firstRight].min,
993 yAxesRanges[firstRight].max);
994 axisRect->axis(QCPAxis::atRight)->setVisible(true);
996 rightLegend->setVisible(true);
998 if (yAxesRanges[secondLeft].max != INVALID_MAX) {
999 axisRect->axis(QCPAxis::atLeft, 1)->setVisible(true);
1000 axisRect->axis(QCPAxis::atLeft, 1)->setRange(yAxesRanges[secondLeft].min,
1001 yAxesRanges[secondLeft].max);
1003 if (yAxesRanges[secondRight].max != INVALID_MAX) {
1004 axisRect->axis(QCPAxis::atRight, 1)->setVisible(true);
1005 axisRect->axis(QCPAxis::atRight, 1)->setRange(yAxesRanges[secondRight].min,
1006 yAxesRanges[secondRight].max);
1011 for (int i = 0; i < plots.coords.size(); i++) {
1012 switch (plots.coords[i].yaxis) {
1013 case firstLeft:
1014 ui->customPlot->addGraph();
1015 if (yAxesRanges[secondLeft].max != INVALID_MAX) {
1016 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (L1)"));
1017 } else {
1018 ui->customPlot->graph(i)->setName(plots.coords.at(i).name);
1020 ui->customPlot->legend->addItem(
1021 new QCPPlottableLegendItem(ui->customPlot->legend, ui->customPlot->graph(i)));
1022 break;
1023 case firstRight:
1024 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1025 axisRect->axis(QCPAxis::atRight));
1026 if (yAxesRanges[secondRight].max != INVALID_MAX) {
1027 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (R1)"));
1028 } else {
1029 ui->customPlot->graph(i)->setName(plots.coords.at(i).name);
1031 rightLegend->addItem(
1032 new QCPPlottableLegendItem(rightLegend, ui->customPlot->graph(i)));
1033 break;
1034 case secondLeft:
1035 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1036 axisRect->axis(QCPAxis::atLeft, 1));
1037 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (L2)"));
1038 ui->customPlot->legend->addItem(
1039 new QCPPlottableLegendItem(ui->customPlot->legend, ui->customPlot->graph(i)));
1040 break;
1041 case secondRight:
1042 ui->customPlot->addGraph(axisRect->axis(QCPAxis::atBottom),
1043 axisRect->axis(QCPAxis::atRight, 1));
1044 ui->customPlot->graph(i)->setName(plots.coords.at(i).name + tr(" (R2)"));
1045 rightLegend->addItem(
1046 new QCPPlottableLegendItem(rightLegend, ui->customPlot->graph(i)));
1047 break;
1048 default:
1049 break;
1052 ui->customPlot->graph(i)->setData(plots.coords.at(i).x,
1053 plots.coords.at(i).y);
1054 pen.setColor(colors.at(i % colors.size()));
1055 ui->customPlot->graph(i)->setPen(pen);
1057 if (!tracerMaxAlt && (plots.coords.at(i).name.endsWith("(m)") ||
1058 plots.coords.at(i).name.endsWith(" Alt") ||
1059 plots.coords.at(i).name.endsWith("(ft)"))) {
1060 addMaxAltitudeMarker(plots.coords.at(i), ui->customPlot->graph(i));
1061 countNumberOfThrows(plots.coords.at(i), ui->customPlot->graph(i));
1062 addCursor(&cursorA, ui->customPlot->graph(i), Qt::blue);
1063 addCursor(&cursorB, ui->customPlot->graph(i), Qt::red);
1064 addCursorLine(&cursorLine, ui->customPlot->graph(i), Qt::black);
1065 updateCursorsLabel();
1069 ui->customPlot->legend->setVisible(true);
1070 ui->customPlot->replot();
1073 void LogsDialog::yAxisChangeRanges(QCPRange range)
1075 if (axisRect->axis(QCPAxis::atRight)->visible()) {
1076 double lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1077 yAxesRatios[firstRight];
1078 double upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1079 yAxesRatios[firstRight];
1081 yAxesRanges[firstRight].min += lowerChange;
1082 yAxesRanges[firstRight].max += upperChange;
1083 axisRect->axis(QCPAxis::atRight)->setRange(yAxesRanges[firstRight].min,
1084 yAxesRanges[firstRight].max);
1086 if (axisRect->axisCount(QCPAxis::atLeft) == 2) {
1087 lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1088 yAxesRatios[secondLeft];
1089 upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1090 yAxesRatios[secondLeft];
1092 yAxesRanges[secondLeft].min += lowerChange;
1093 yAxesRanges[secondLeft].max += upperChange;
1094 axisRect->axis(QCPAxis::atLeft, 1)->setRange(yAxesRanges[secondLeft].min,
1095 yAxesRanges[secondLeft].max);
1097 if (axisRect->axisCount(QCPAxis::atRight) == 2) {
1098 lowerChange = (range.lower - yAxesRanges[firstLeft].min) *
1099 yAxesRatios[secondRight];
1100 upperChange = (range.upper - yAxesRanges[firstLeft].max) *
1101 yAxesRatios[secondRight];
1103 yAxesRanges[secondRight].min += lowerChange;
1104 yAxesRanges[secondRight].max += upperChange;
1105 axisRect->axis(QCPAxis::atRight, 1)->setRange(yAxesRanges[secondRight].min,
1106 yAxesRanges[secondRight].max);
1110 yAxesRanges[firstLeft].min = range.lower;
1111 yAxesRanges[firstLeft].max = range.upper;
1116 void LogsDialog::addMaxAltitudeMarker(const coords & c, QCPGraph * graph) {
1117 // find max altitude
1118 int positionIndex = 0;
1119 double maxAlt = -100000;
1121 for(int i=0; i<c.x.count(); ++i) {
1122 double alt = c.y.at(i);
1123 if (alt > maxAlt) {
1124 maxAlt = alt;
1125 positionIndex = i;
1126 // qDebug() << "max alt: " << maxAlt << "@" << result;
1129 // qDebug() << "max alt: " << maxAlt << "@" << positionIndex;
1131 // add max altitude marker
1132 tracerMaxAlt = new QCPItemTracer(ui->customPlot);
1133 ui->customPlot->addItem(tracerMaxAlt);
1134 tracerMaxAlt->setGraph(graph);
1135 tracerMaxAlt->setInterpolating(true);
1136 tracerMaxAlt->setStyle(QCPItemTracer::tsSquare);
1137 tracerMaxAlt->setPen(QPen(Qt::blue));
1138 tracerMaxAlt->setBrush(Qt::NoBrush);
1139 tracerMaxAlt->setSize(7);
1140 tracerMaxAlt->setGraphKey(c.x.at(positionIndex));
1141 tracerMaxAlt->updatePosition();
1144 void LogsDialog::countNumberOfThrows(const coords & c, QCPGraph * graph)
1146 #if 0
1147 // find all launches
1148 // TODO
1149 double startTime = c.x.at(0);
1151 for(int i=0; i<c.x.count(); ++i) {
1152 double alt = c.y.at(i);
1153 double time = c.x.at(i);
1155 #endif
1158 void LogsDialog::addCursor(QCPItemTracer ** cursor, QCPGraph * graph, const QColor & color) {
1159 QCPItemTracer * c = new QCPItemTracer(ui->customPlot);
1160 ui->customPlot->addItem(c);
1161 c->setGraph(graph);
1162 c->setInterpolating(false);
1163 c->setStyle(QCPItemTracer::tsCrosshair);
1164 QPen pen(color);
1165 pen.setStyle(Qt::DashLine);
1166 c->setPen(pen);
1167 c->setBrush(color);
1168 c->setSize(7);
1169 c->setVisible(false);
1170 *cursor = c;
1173 void LogsDialog::addCursorLine(QCPItemStraightLine ** line, QCPGraph * graph, const QColor & color) {
1174 QCPItemStraightLine * l = new QCPItemStraightLine(ui->customPlot);
1175 ui->customPlot->addItem(l);
1176 l->point1->setParentAnchor(cursorA->position);
1177 l->point2->setParentAnchor(cursorB->position);
1178 QPen pen(color);
1179 pen.setStyle(Qt::DashLine);
1180 l->setPen(pen);
1181 l->setVisible(false);
1182 *line = l;