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(on_saveSession_BT_clicked()));
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 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"));
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 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.")
342 .arg(geFile
.errorString()));
346 QTextStream
outputStream(&geFile
);
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";
371 if (logFilename
.indexOf("-")>0) {
372 planeName
=logFilename
.left(logFilename
.indexOf("-"));
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";
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";
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>";
426 QString gePath
= g
.gePath();
427 QStringList parameters
;
430 parameters
<< gePath
;
431 gePath
= "/usr/bin/open";
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());
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
;
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);
494 double deltaX
= cursorB
->position
->key() - cursorA
->position
->key();
495 double deltaY
= cursorB
->position
->value() - cursorA
->position
->value();
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;
516 int minutes
= seconds
/ 60;
520 return QString("%1h:%2:%3").arg(hours
).arg(minutes
, 2, 10, QChar('0')).arg(seconds
, 2, 10, QChar('0'));
523 return QString("%1m:%2").arg(minutes
).arg(seconds
, 2, 10, QChar('0'));
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
535 if (ui
->ZoomX_ChkB
->isChecked()) {
536 orientation
|=Qt::Horizontal
;
538 if (ui
->ZoomY_ChkB
->isChecked()) {
539 orientation
|=Qt::Vertical
;
542 axisRect
->setRangeZoom((Qt::Orientation
)orientation
);
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();
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()) {
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
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;
628 for (int i
= 1; i
< n
; i
++) {
629 QDateTime tmp
= getRecordTimeStamp(i
);
630 if (!lastvalue
.isValid() || lastvalue
.secsTo(tmp
) > 60) {
634 if(currentSession
== index
) {
635 // add records to filtered list
636 sessionCsvLog
.push_back(csvlog
[i
]);
638 else if (currentSession
> index
) {
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());
664 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) { // reading HEX TEXT file
670 QTextStream
inputStream(&file
);
671 QString buffer
= file
.readLine();
673 if (buffer
.startsWith("Date,Time")) {
681 while (!file
.atEnd()) {
682 QString line
= file
.readLine().trimmed();
683 QStringList columns
= line
.split(',');
685 numfields
=columns
.count();
687 if (columns
.count()==numfields
) {
688 csvlog
.append(columns
);
696 logFilename
= QFileInfo(file
.fileName()).baseName();
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();
717 struct FlightSession
{
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
;
735 durationString
= QString("%1:").arg(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
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;
761 sessions
.push_back(n
-1);
763 //now construct a list of sessions with their times
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;
788 ui
->logTable
->clearSelection();
792 if (index
< ui
->sessions_CB
->count() - 1) {
793 bottom
= ui
->sessions_CB
->itemData(index
+ 1, Qt::UserRole
).toInt();
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);
809 ui
->SaveSession_PB
->setEnabled(false);
816 void LogsDialog::plotLogs()
818 if (plotLock
) return;
820 if (!ui
->FieldsTW
->selectedItems().length()) {
825 plotsCollection plots
;
827 QModelIndexList selection
= ui
->logTable
->selectionModel()->selectedRows();
828 int rowCount
= selection
.length();
829 bool hasLogSelection
;
830 QVarLengthArray
<int> selectedRows
;
833 hasLogSelection
= true;
834 foreach (QModelIndex index
, selection
) {
835 selectedRows
.append(index
.row());
837 qSort(selectedRows
.begin(), selectedRows
.end());
839 hasLogSelection
= false;
840 rowCount
= ui
->logTable
->rowCount();
843 plots
.min_x
= QDateTime::currentDateTime().toTime_t();
846 foreach (QTableWidgetItem
*plot
, ui
->FieldsTW
->selectedItems()) {
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
;
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();
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")
880 time
+= time_str
.mid(time_str
.indexOf('.')).toDouble();
882 time
= QDateTime::fromString(time_str
, "yyyy-MM-dd HH:mm:ss")
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
) {
919 plots
.coords
[i
].yaxis
= firstRight
;
922 plots
.coords
[i
].yaxis
= secondLeft
;
925 plots
.coords
[i
].yaxis
= secondRight
;
928 plots
.tooManyRanges
= true;
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
) {
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
);
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
);
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);
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
) {
1014 ui
->customPlot
->addGraph();
1015 if (yAxesRanges
[secondLeft
].max
!= INVALID_MAX
) {
1016 ui
->customPlot
->graph(i
)->setName(plots
.coords
.at(i
).name
+ tr(" (L1)"));
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
)));
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)"));
1029 ui
->customPlot
->graph(i
)->setName(plots
.coords
.at(i
).name
);
1031 rightLegend
->addItem(
1032 new QCPPlottableLegendItem(rightLegend
, ui
->customPlot
->graph(i
)));
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
)));
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
)));
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
);
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
)
1147 // find all launches
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
);
1158 void LogsDialog::addCursor(QCPItemTracer
** cursor
, QCPGraph
* graph
, const QColor
& color
) {
1159 QCPItemTracer
* c
= new QCPItemTracer(ui
->customPlot
);
1160 ui
->customPlot
->addItem(c
);
1162 c
->setInterpolating(false);
1163 c
->setStyle(QCPItemTracer::tsCrosshair
);
1165 pen
.setStyle(Qt::DashLine
);
1169 c
->setVisible(false);
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
);
1179 pen
.setStyle(Qt::DashLine
);
1181 l
->setVisible(false);