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.
22 #include "logsdialog.h"
24 #include "ui_logsdialog.h"
26 #if defined _MSC_VER || !defined __GNUC__
32 LogsDialog::LogsDialog(QWidget
*parent
) :
33 QDialog(parent
, Qt::WindowTitleHint
| Qt::WindowSystemMenuHint
),
34 ui(new Ui::LogsDialog
),
43 setWindowIcon(CompanionIcon("logs.png"));
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
);
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()
129 void LogsDialog::titleDoubleClick(QMouseEvent
*evt
, QCPPlotTitle
*title
)
131 // Set the plot title by double clicking on it
133 QString newTitle
= QInputDialog::getText(this, tr("Plot Title Change"), tr("New plot title:"), QLineEdit::Normal
, title
->text(), &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
146 QString newLabel
= QInputDialog::getText(this, tr("Axis Label Change"), tr("New axis label:"), QLineEdit::Normal
, axis
->label(), &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
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
);
161 QString newName
= QInputDialog::getText(this, tr("Graph Name Change"), tr("New graph name:"), QLineEdit::Normal
, plItem
->plottable()->name(), &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
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
))
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
))
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();
249 for (int i
=1; i
<input
.at(0).count(); i
++) {
250 if (input
.at(0).at(i
) == "GPS") {
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"));
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
));
273 if ( glitchFilter
.isGlitch(coord
) ) {
274 // qDebug() << "filterGePoints(): GPS glitch detected at" << i << coord.latitude << coord.longitude;
278 // lat long pair filter
279 if ( !latLonFilter
.isValid(coord
) ) {
280 // qDebug() << "filterGePoints(): Lat-Lon pair wrong, skipping at" << i << coord.latitude << coord.longitude;
284 // qDebug() << "point " << latitude << longitude;
285 result
.append(input
.at(i
));
289 // qDebug() << "filterGePoints(): filtered from" << input.count() << "to " << result.count() << "points";
293 void LogsDialog::exportToGoogleEarth()
295 // filter data points
296 QList
<QStringList
> dataPoints
= filterGePoints(csvlog
);
297 int n
= dataPoints
.count(); // number of points to export
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") {
309 if (dataPoints
.at(0).at(i
).contains("GAlt")) {
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")) {
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.")
333 .arg(geFile
.errorString()));
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
);
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";
368 if (logFilename
.indexOf("-")>0) {
369 planeName
=logFilename
.left(logFilename
.indexOf("-"));
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";
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";
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>";
423 QString gePath
= g
.gePath();
424 QStringList parameters
;
427 parameters
<< gePath
;
428 gePath
= "/usr/bin/open";
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());
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
;
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);
490 double deltaX
= cursorB
->position
->key() - cursorA
->position
->key();
491 double deltaY
= cursorB
->position
->value() - cursorA
->position
->value();
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;
512 int minutes
= seconds
/ 60;
516 return QString("%1h:%2:%3").arg(hours
).arg(minutes
, 2, 10, QChar('0')).arg(seconds
, 2, 10, QChar('0'));
519 return QString("%1m:%2").arg(minutes
).arg(seconds
, 2, 10, QChar('0'));
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
531 if (ui
->ZoomX_ChkB
->isChecked()) {
532 orientation
|=Qt::Horizontal
;
534 if (ui
->ZoomY_ChkB
->isChecked()) {
535 orientation
|=Qt::Vertical
;
538 axisRect
->setRangeZoom((Qt::Orientation
)orientation
);
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();
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()) {
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
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;
624 for (int i
= 1; i
< n
; i
++) {
625 QDateTime tmp
= getRecordTimeStamp(i
);
626 if (!lastvalue
.isValid() || lastvalue
.secsTo(tmp
) > 60) {
630 if(currentSession
== index
) {
631 // add records to filtered list
632 sessionCsvLog
.push_back(csvlog
[i
]);
634 else if (currentSession
> index
) {
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());
660 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) { // reading HEX TEXT file
666 QTextStream
inputStream(&file
);
667 QString buffer
= file
.readLine();
669 if (buffer
.startsWith("Date,Time")) {
677 while (!file
.atEnd()) {
678 QString line
= file
.readLine().trimmed();
679 QStringList columns
= line
.split(',');
681 numfields
=columns
.count();
683 if (columns
.count()==numfields
) {
684 csvlog
.append(columns
);
692 logFilename
= QFileInfo(file
.fileName()).baseName();
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();
713 struct FlightSession
{
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
;
731 durationString
= QString("%1:").arg(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
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;
757 sessions
.push_back(n
-1);
759 //now construct a list of sessions with their times
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;
784 ui
->logTable
->clearSelection();
788 if (index
< ui
->sessions_CB
->count() - 1) {
789 bottom
= ui
->sessions_CB
->itemData(index
+ 1, Qt::UserRole
).toInt();
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);
805 ui
->SaveSession_PB
->setEnabled(false);
812 void LogsDialog::plotLogs()
814 if (plotLock
) return;
816 if (!ui
->FieldsTW
->selectedItems().length()) {
821 plotsCollection plots
;
823 QModelIndexList selection
= ui
->logTable
->selectionModel()->selectedRows();
824 int rowCount
= selection
.length();
825 bool hasLogSelection
;
826 QVarLengthArray
<int> selectedRows
;
829 hasLogSelection
= true;
830 foreach (QModelIndex index
, selection
) {
831 selectedRows
.append(index
.row());
833 qSort(selectedRows
.begin(), selectedRows
.end());
835 hasLogSelection
= false;
836 rowCount
= ui
->logTable
->rowCount();
839 plots
.min_x
= QDateTime::currentDateTime().toTime_t();
842 foreach (QTableWidgetItem
*plot
, ui
->FieldsTW
->selectedItems()) {
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
;
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();
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")
876 time
+= time_str
.mid(time_str
.indexOf('.')).toDouble();
878 time
= QDateTime::fromString(time_str
, "yyyy-MM-dd HH:mm:ss")
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
) {
915 plots
.coords
[i
].yaxis
= firstRight
;
918 plots
.coords
[i
].yaxis
= secondLeft
;
921 plots
.coords
[i
].yaxis
= secondRight
;
924 plots
.tooManyRanges
= true;
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
) {
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
);
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
);
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);
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
) {
1010 ui
->customPlot
->addGraph();
1011 if (yAxesRanges
[secondLeft
].max
!= INVALID_MAX
) {
1012 ui
->customPlot
->graph(i
)->setName(plots
.coords
.at(i
).name
+ tr(" (L1)"));
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
)));
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)"));
1025 ui
->customPlot
->graph(i
)->setName(plots
.coords
.at(i
).name
);
1027 rightLegend
->addItem(
1028 new QCPPlottableLegendItem(rightLegend
, ui
->customPlot
->graph(i
)));
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
)));
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
)));
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
);
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
)
1143 // find all launches
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
);
1154 void LogsDialog::addCursor(QCPItemTracer
** cursor
, QCPGraph
* graph
, const QColor
& color
) {
1155 QCPItemTracer
* c
= new QCPItemTracer(ui
->customPlot
);
1156 ui
->customPlot
->addItem(c
);
1158 c
->setInterpolating(false);
1159 c
->setStyle(QCPItemTracer::tsCrosshair
);
1161 pen
.setStyle(Qt::DashLine
);
1165 c
->setVisible(false);
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
);
1175 pen
.setStyle(Qt::DashLine
);
1177 l
->setVisible(false);