Update CREDITS.txt
[opentx.git] / companion / src / helpers.cpp
blobb7809bee3ab79aeb908b42061a35e5299f1ee2c6
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 <QtGui>
22 #if defined _MSC_VER
23 #include <io.h>
24 #include <stdio.h>
25 #elif defined __GNUC__
26 #include <unistd.h>
27 #endif
29 #include "appdata.h"
30 #include "macros.h"
31 #include "helpers.h"
32 #include "simulatorinterface.h"
33 #include "simulatormainwindow.h"
34 #include "storage/sdcard.h"
36 #include <QLabel>
37 #include <QMessageBox>
39 using namespace Helpers;
41 Stopwatch gStopwatch("global");
43 const QColor colors[CPN_MAX_CURVES] = {
44 QColor(0,0,127),
45 QColor(0,127,0),
46 QColor(127,0,0),
47 QColor(0,127,127),
48 QColor(127,0,127),
49 QColor(127,127,0),
50 QColor(127,127,127),
51 QColor(0,0,255),
52 QColor(0,127,255),
53 QColor(127,0,255),
54 QColor(0,255,0),
55 QColor(0,255,127),
56 QColor(127,255,0),
57 QColor(255,0,0),
58 QColor(255,0,127),
59 QColor(255,127,0),
60 QColor(0,0,127),
61 QColor(0,127,0),
62 QColor(127,0,0),
63 QColor(0,127,127),
64 QColor(127,0,127),
65 QColor(127,127,0),
66 QColor(127,127,127),
67 QColor(0,0,255),
68 QColor(0,127,255),
69 QColor(127,0,255),
70 QColor(0,255,0),
71 QColor(0,255,127),
72 QColor(127,255,0),
73 QColor(255,0,0),
74 QColor(255,0,127),
75 QColor(255,127,0),
79 * GVarGroup
82 GVarGroup::GVarGroup(QCheckBox * weightGV, QAbstractSpinBox * weightSB, QComboBox * weightCB, int & weight, const ModelData & model, const int deflt, const int mini, const int maxi, const double step, bool allowGvars):
83 QObject(),
84 weightGV(weightGV),
85 weightSB(weightSB),
86 sb(dynamic_cast<QSpinBox *>(weightSB)),
87 dsb(dynamic_cast<QDoubleSpinBox *>(weightSB)),
88 weightCB(weightCB),
89 weight(weight),
90 step(step),
91 lock(true)
93 if (allowGvars && getCurrentFirmware()->getCapability(Gvars)) {
94 Helpers::populateGVCB(*weightCB, weight, model);
95 connect(weightGV, SIGNAL(stateChanged(int)), this, SLOT(gvarCBChanged(int)));
96 connect(weightCB, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesChanged()));
98 else {
99 weightGV->hide();
100 if (weight > maxi || weight < mini) {
101 weight = deflt;
105 int val;
107 if (weight>maxi || weight<mini) {
108 val = deflt;
109 weightGV->setChecked(true);
110 weightSB->hide();
111 weightCB->show();
113 else {
114 val = weight;
115 weightGV->setChecked(false);
116 weightSB->show();
117 weightCB->hide();
120 if (sb) {
121 sb->setMinimum(mini);
122 sb->setMaximum(maxi);
123 sb->setValue(val);
125 else {
126 dsb->setMinimum(mini*step);
127 dsb->setMaximum(maxi*step);
128 dsb->setValue(val*step);
131 connect(weightSB, SIGNAL(editingFinished()), this, SLOT(valuesChanged()));
133 lock = false;
136 void GVarGroup::gvarCBChanged(int state)
138 weightCB->setVisible(state);
139 if (weightSB)
140 weightSB->setVisible(!state);
141 else
142 weightSB->setVisible(!state);
143 valuesChanged();
146 void GVarGroup::valuesChanged()
148 if (!lock) {
149 if (weightGV->isChecked())
150 weight = weightCB->itemData(weightCB->currentIndex()).toInt();
151 else if (sb)
152 weight = sb->value();
153 else
154 weight = round(dsb->value()/step);
156 emit valueChanged();
161 * CurveGroup
164 CurveGroup::CurveGroup(QComboBox * curveTypeCB, QCheckBox * curveGVarCB, QComboBox * curveValueCB, QSpinBox * curveValueSB, CurveReference & curve, const ModelData & model, unsigned int flags):
165 QObject(),
166 curveTypeCB(curveTypeCB),
167 curveGVarCB(curveGVarCB),
168 curveValueCB(curveValueCB),
169 curveValueSB(curveValueSB),
170 curve(curve),
171 model(model),
172 flags(flags),
173 lock(false),
174 lastType(-1)
176 if (!(flags & HIDE_DIFF)) curveTypeCB->addItem(tr("Diff"), 0);
177 if (!(flags & HIDE_EXPO)) curveTypeCB->addItem(tr("Expo"), 1);
178 curveTypeCB->addItem(tr("Func"), 2);
179 curveTypeCB->addItem(tr("Curve"), 3);
181 curveValueCB->setMaxVisibleItems(10);
183 connect(curveTypeCB, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)));
184 connect(curveGVarCB, SIGNAL(stateChanged(int)), this, SLOT(gvarCBChanged(int)));
185 connect(curveValueCB, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesChanged()));
186 connect(curveValueSB, SIGNAL(editingFinished()), this, SLOT(valuesChanged()));
188 update();
191 void CurveGroup::update()
193 lock = true;
195 int found = curveTypeCB->findData(curve.type);
196 if (found < 0) found = 0;
197 curveTypeCB->setCurrentIndex(found);
199 if (curve.type == CurveReference::CURVE_REF_DIFF || curve.type == CurveReference::CURVE_REF_EXPO) {
200 curveGVarCB->setVisible(getCurrentFirmware()->getCapability(Gvars));
201 if (curve.value > 100 || curve.value < -100) {
202 curveGVarCB->setChecked(true);
203 if (lastType != CurveReference::CURVE_REF_DIFF && lastType != CurveReference::CURVE_REF_EXPO) {
204 lastType = curve.type;
205 Helpers::populateGVCB(*curveValueCB, curve.value, model);
207 curveValueCB->show();
208 curveValueSB->hide();
210 else {
211 curveGVarCB->setChecked(false);
212 curveValueSB->setMinimum(-100);
213 curveValueSB->setMaximum(100);
214 curveValueSB->setValue(curve.value);
215 curveValueSB->show();
216 curveValueCB->hide();
219 else {
220 curveGVarCB->hide();
221 curveValueSB->hide();
222 curveValueCB->show();
223 switch (curve.type) {
224 case CurveReference::CURVE_REF_FUNC:
225 if (lastType != curve.type) {
226 lastType = curve.type;
227 curveValueCB->clear();
228 for (int i=0; i<=6/*TODO constant*/; i++) {
229 curveValueCB->addItem(CurveReference(CurveReference::CURVE_REF_FUNC, i).toString(&model, false));
232 curveValueCB->setCurrentIndex(curve.value);
233 break;
234 case CurveReference::CURVE_REF_CUSTOM:
236 int numcurves = getCurrentFirmware()->getCapability(NumCurves);
237 if (lastType != curve.type) {
238 lastType = curve.type;
239 curveValueCB->clear();
240 for (int i= ((flags & HIDE_NEGATIVE_CURVES) ? 0 : -numcurves); i<=numcurves; i++) {
241 curveValueCB->addItem(CurveReference(CurveReference::CURVE_REF_CUSTOM, i).toString(&model, false), i);
242 if (i == curve.value) {
243 curveValueCB->setCurrentIndex(curveValueCB->count() - 1);
247 break;
249 default:
250 break;
254 lock = false;
257 void CurveGroup::gvarCBChanged(int state)
259 if (!lock) {
260 if (state) {
261 curve.value = 10000+1; // TODO constant in EEpromInterface ...
262 lastType = -1; // quickfix for issue #3518: force refresh of curveValueCB at next update() to set current index to GV1
264 else {
265 curve.value = 0; // TODO could be better
268 update();
272 void CurveGroup::typeChanged(int value)
274 if (!lock) {
275 int type = curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt();
276 switch (type) {
277 case 0:
278 curve = CurveReference(CurveReference::CURVE_REF_DIFF, 0);
279 break;
280 case 1:
281 curve = CurveReference(CurveReference::CURVE_REF_EXPO, 0);
282 break;
283 case 2:
284 curve = CurveReference(CurveReference::CURVE_REF_FUNC, 0);
285 break;
286 case 3:
287 curve = CurveReference(CurveReference::CURVE_REF_CUSTOM, 0);
288 break;
291 update();
295 void CurveGroup::valuesChanged()
297 if (!lock) {
298 switch (curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt()) {
299 case 0:
300 case 1:
302 int value;
303 if (curveGVarCB->isChecked())
304 value = curveValueCB->itemData(curveValueCB->currentIndex()).toInt();
305 else
306 value = curveValueSB->value();
307 curve = CurveReference(curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt() == 0 ? CurveReference::CURVE_REF_DIFF : CurveReference::CURVE_REF_EXPO, value);
308 break;
310 case 2:
311 curve = CurveReference(CurveReference::CURVE_REF_FUNC, curveValueCB->currentIndex());
312 break;
313 case 3:
314 curve = CurveReference(CurveReference::CURVE_REF_CUSTOM, curveValueCB->itemData(curveValueCB->currentIndex()).toInt());
315 break;
318 update();
323 * Helpers namespace functions
326 void Helpers::populateGVCB(QComboBox & b, int value, const ModelData & model)
328 int count = getCurrentFirmware()->getCapability(Gvars);
330 b.clear();
332 for (int i=-count; i<=-1; i++) {
333 int16_t gval = (int16_t)(-10000+i);
334 b.addItem("-" + RawSource(SOURCE_TYPE_GVAR, abs(i)-1).toString(&model), gval);
337 for (int i=1; i<=count; i++) {
338 int16_t gval = (int16_t)(10000+i);
339 b.addItem(RawSource(SOURCE_TYPE_GVAR, i-1).toString(&model), gval);
342 b.setCurrentIndex(b.findData(value));
343 if (b.currentIndex() == -1)
344 b.setCurrentIndex(count);
347 // Returns Diff/Expo/Weight/Offset adjustment value as either a percentage or a global variable name.
348 QString Helpers::getAdjustmentString(int16_t val, const ModelData * model, bool sign)
350 QString ret;
351 if (val >= -10000 && val <= 10000) {
352 ret = "%1%";
353 if (sign && val > 0)
354 ret.prepend("+");
355 ret = ret.arg(val);
357 else {
358 ret = RawSource(SOURCE_TYPE_GVAR, abs(val) - 10001).toString(model);
359 if (val < 0)
360 ret.prepend("-");
361 else if (sign)
362 ret.prepend("+");
364 return ret;
367 // TODO: Move lookup to GVarData class (w/out combobox)
368 void Helpers::populateGvarUseCB(QComboBox * b, unsigned int phase)
370 b->addItem(QCoreApplication::translate("GVarData", "Own value"));
371 for (int i=0; i<getCurrentFirmware()->getCapability(FlightModes); i++) {
372 if (i != (int)phase) {
373 b->addItem(QCoreApplication::translate("GVarData", "Flight mode %1 value").arg(i));
378 void Helpers::populateFileComboBox(QComboBox * b, const QSet<QString> & set, const QString & current)
380 b->clear();
381 b->addItem("----");
383 bool added = false;
384 // Convert set into list and sort it alphabetically case insensitive
385 QStringList list = QStringList::fromSet(set);
386 qSort(list.begin(), list.end(), caseInsensitiveLessThan);
387 foreach (QString entry, list) {
388 b->addItem(entry);
389 if (entry == current) {
390 b->setCurrentIndex(b->count()-1);
391 added = true;
395 if (!added && !current.isEmpty()) {
396 b->addItem(current);
397 b->setCurrentIndex(b->count()-1);
401 void Helpers::getFileComboBoxValue(QComboBox * b, char * dest, int length)
403 memset(dest, 0, length+1);
404 if (b->currentText() != "----") {
405 strncpy(dest, b->currentText().toLatin1(), length);
409 void Helpers::addRawSourceItems(QStandardItemModel * itemModel, const RawSourceType & type, int count, const GeneralSettings * const generalSettings,
410 const ModelData * const model, const int start)
412 for (int i = start; i < start + count; i++) {
413 RawSource src = RawSource(type, i);
414 if (!src.isAvailable(model, generalSettings, getCurrentBoard()))
415 continue;
417 QStandardItem * modelItem = new QStandardItem(src.toString(model, generalSettings));
418 modelItem->setData(src.toValue(), Qt::UserRole);
419 itemModel->appendRow(modelItem);
423 QStandardItemModel * Helpers::getRawSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const model, unsigned int flags)
425 QStandardItemModel * itemModel = new QStandardItemModel();
426 Boards board = Boards(getCurrentBoard());
427 Firmware * fw = getCurrentFirmware();
429 if (flags & POPULATE_NONE) {
430 addRawSourceItems(itemModel, SOURCE_TYPE_NONE, 1, generalSettings, model);
433 if (flags & POPULATE_SCRIPT_OUTPUTS) {
434 for (int i=0; i < getCurrentFirmware()->getCapability(LuaScripts); i++) {
435 addRawSourceItems(itemModel, SOURCE_TYPE_LUA_OUTPUT, fw->getCapability(LuaOutputsPerScript), generalSettings, model, i * 16);
439 if (model && (flags & POPULATE_VIRTUAL_INPUTS)) {
440 addRawSourceItems(itemModel, SOURCE_TYPE_VIRTUAL_INPUT, fw->getCapability(VirtualInputs), generalSettings, model);
443 if (flags & POPULATE_SOURCES) {
444 int totalSources = CPN_MAX_STICKS + board.getCapability(Board::Pots) + board.getCapability(Board::Sliders) + board.getCapability(Board::MouseAnalogs);
445 addRawSourceItems(itemModel, SOURCE_TYPE_STICK, totalSources, generalSettings, model);
446 addRawSourceItems(itemModel, SOURCE_TYPE_ROTARY_ENCODER, fw->getCapability(RotaryEncoders), generalSettings, model);
449 if (flags & POPULATE_TRIMS) {
450 addRawSourceItems(itemModel, SOURCE_TYPE_TRIM, board.getCapability(Board::NumTrims), generalSettings, model);
453 if (flags & POPULATE_SOURCES) {
454 addRawSourceItems(itemModel, SOURCE_TYPE_MAX, 1, generalSettings, model);
457 if (flags & POPULATE_SWITCHES) {
458 addRawSourceItems(itemModel, SOURCE_TYPE_SWITCH, board.getCapability(Board::Switches), generalSettings, model);
459 addRawSourceItems(itemModel, SOURCE_TYPE_CUSTOM_SWITCH, fw->getCapability(LogicalSwitches), generalSettings, model);
462 if (flags & POPULATE_SOURCES) {
463 addRawSourceItems(itemModel, SOURCE_TYPE_CYC, CPN_MAX_CYC, generalSettings, model);
464 addRawSourceItems(itemModel, SOURCE_TYPE_PPM, fw->getCapability(TrainerInputs), generalSettings, model);
465 addRawSourceItems(itemModel, SOURCE_TYPE_CH, fw->getCapability(Outputs), generalSettings, model);
468 if (flags & POPULATE_TELEMETRY) {
469 int count = 0;
470 if (IS_ARM(board.getBoardType())) {
471 addRawSourceItems(itemModel, SOURCE_TYPE_SPECIAL, 5, generalSettings, model);
472 count = CPN_MAX_SENSORS * 3;
474 else {
475 count = ((flags & POPULATE_TELEMETRYEXT) ? TELEMETRY_SOURCES_STATUS_COUNT : TELEMETRY_SOURCES_COUNT);
477 if (model && count)
478 addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, count, generalSettings, model);
481 if (flags & POPULATE_GVARS) {
482 addRawSourceItems(itemModel, SOURCE_TYPE_GVAR, fw->getCapability(Gvars), generalSettings, model);
485 return itemModel;
488 QString image2qstring(QImage image)
490 if (image.isNull())
491 return "";
492 QBuffer buffer;
493 image.save(&buffer, "PNG");
494 QString ImageStr;
495 int b=0;
496 int size=buffer.data().size();
497 for (int j = 0; j < size; j++) {
498 b=buffer.data().at(j);
499 ImageStr += QString("%1").arg(b&0xff, 2, 16, QChar('0'));
501 return ImageStr;
504 int findmult(float value, float base)
506 int vvalue = value*10;
507 int vbase = base*10;
508 vvalue--;
510 int mult = 0;
511 for (int i=8; i>=0; i--) {
512 if (vvalue/vbase >= (1<<i)) {
513 mult = i+1;
514 break;
518 return mult;
521 // TODO: Move to FrSkyAlarmData
522 QString getFrSkyAlarmType(int alarm)
524 switch (alarm) {
525 case 1:
526 return QCoreApplication::translate("FrSkyAlarmData", "Yellow");
527 case 2:
528 return QCoreApplication::translate("FrSkyAlarmData", "Orange");
529 case 3:
530 return QCoreApplication::translate("FrSkyAlarmData", "Red");
531 default:
532 return "----";
536 // TODO: move to FrSkyChannelData
537 QString getFrSkyUnits(int units)
539 switch(units) {
540 case 1:
541 return QCoreApplication::translate("FrSkyChannelData", "---");
542 default:
543 return QCoreApplication::translate("FrSkyChannelData", "V");
547 QString getTheme()
549 int theme_set = g.theme();
550 QString Theme;
551 switch(theme_set) {
552 case 0:
553 Theme="classic";
554 break;
555 case 2:
556 Theme="monowhite";
557 break;
558 case 3:
559 Theme="monochrome";
560 break;
561 case 4:
562 Theme="monoblue";
563 break;
564 default:
565 Theme="yerico";
566 break;
568 return Theme;
571 CompanionIcon::CompanionIcon(const QString &baseimage)
573 static QString theme = getTheme();
574 addFile(":/themes/"+theme+"/16/"+baseimage, QSize(16,16));
575 addFile(":/themes/"+theme+"/24/"+baseimage, QSize(24,24));
576 addFile(":/themes/"+theme+"/32/"+baseimage, QSize(32,32));
577 addFile(":/themes/"+theme+"/48/"+baseimage, QSize(48,48));
580 void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx)
582 QString fwId = SimulatorLoader::findSimulatorByFirmwareName(getCurrentFirmware()->getId());
583 if (fwId.isEmpty()) {
584 QMessageBox::warning(NULL,
585 CPN_STR_TTL_WARNING,
586 QCoreApplication::translate("Companion", "Simulator for this firmware is not yet available"));
587 return;
590 RadioData * simuData = new RadioData(radioData);
591 unsigned int flags = 0;
593 if (modelIdx >= 0) {
594 flags |= SIMULATOR_FLAGS_NOTX;
595 simuData->setCurrentModel(modelIdx);
598 SimulatorMainWindow * dialog = new SimulatorMainWindow(parent, fwId, flags);
599 dialog->setWindowModality(Qt::ApplicationModal);
600 dialog->setAttribute(Qt::WA_DeleteOnClose);
602 QObject::connect(dialog, &SimulatorMainWindow::destroyed, [simuData] (void) {
603 // TODO simuData and Horus tmp directory is deleted on simulator close OR we could use it to get back data from the simulation
604 delete simuData;
607 QString resultMsg;
608 if (dialog->getExitStatus(&resultMsg)) {
609 if (resultMsg.isEmpty())
610 resultMsg = QCoreApplication::translate("Companion", "Uknown error during Simulator startup.");
611 QMessageBox::critical(NULL, QCoreApplication::translate("Companion", "Simulator Error"), resultMsg);
612 dialog->deleteLater();
614 else if (dialog->setRadioData(simuData)) {
615 dialog->show();
617 else {
618 QMessageBox::critical(NULL, QCoreApplication::translate("Companion", "Data Load Error"), QCoreApplication::translate("Companion", "Error occurred while starting simulator."));
619 dialog->deleteLater();
623 QPixmap makePixMap(const QImage & image)
625 Firmware * firmware = getCurrentFirmware();
626 QImage result = image.scaled(firmware->getCapability(LcdWidth), firmware->getCapability(LcdHeight));
627 if (firmware->getCapability(LcdDepth) == 4) {
628 result = result.convertToFormat(QImage::Format_RGB32);
629 for (int i = 0; i < result.width(); ++i) {
630 for (int j = 0; j < result.height(); ++j) {
631 QRgb col = result.pixel(i, j);
632 int gray = qGray(col);
633 result.setPixel(i, j, qRgb(gray, gray, gray));
637 else {
638 result = result.convertToFormat(QImage::Format_Mono);
641 return QPixmap::fromImage(result);
645 int version2index(const QString & version)
647 int result = 999;
648 QStringList parts;
649 QString mainVersion = version;
650 if (version.contains("RC")) {
651 parts = version.split("RC");
652 result = parts[1].toInt() + 900; // RC0 = 900; RC1=901,..
653 mainVersion = parts[0];
655 else if (version.contains("N")) {
656 parts = version.split("N");
657 result = parts[1].toInt(); // nightly build up to 899
658 mainVersion = parts[0];
660 parts = mainVersion.split('.');
661 if (parts.size() > 2)
662 result += 1000 * parts[2].toInt();
663 if (parts.size() > 1)
664 result += 100000 * parts[1].toInt();
665 if (parts.size() > 0)
666 result += 10000000 * parts[0].toInt();
667 return result;
670 const QString index2version(int index)
672 QString result;
673 QString templt("%1.%2.%3");
674 if (index >= 19900000) {
675 int nightly = index % 1000;
676 index /= 1000;
677 int revision = index % 100;
678 index /= 100;
679 int minor = index % 100;
680 int major = index / 100;
681 result = templt.arg(major).arg(minor).arg(revision);
682 if (nightly > 0 && nightly < 900) {
683 result += "N" + QString::number(nightly);
685 else if (nightly >= 900 && nightly < 1000) {
686 result += "RC" + QString::number(nightly-900);
689 else if (index >= 19900) {
690 int revision = index % 100;
691 index /= 100;
692 int minor = index % 100;
693 int major = index / 100;
694 result = templt.arg(major).arg(minor).arg(revision);
696 return result;
699 bool qunlink(const QString & fileName)
701 return QFile::remove(fileName);
704 QString generateProcessUniqueTempFileName(const QString & fileName)
706 QString sanitizedFileName = fileName;
707 sanitizedFileName.remove('/');
708 return QDir::tempPath() + QString("/%1-").arg(QCoreApplication::applicationPid()) + sanitizedFileName;
711 bool isTempFileName(const QString & fileName)
713 return fileName.startsWith(QDir::tempPath());
716 QString getSoundsPath(const GeneralSettings &generalSettings)
718 QString path = g.profile[g.id()].sdPath() + "/SOUNDS/";
719 QString lang = generalSettings.ttsLanguage;
720 if (lang.isEmpty())
721 lang = "en";
722 path.append(lang);
723 return path;
726 QSet<QString> getFilesSet(const QString &path, const QStringList &filter, int maxLen)
728 QSet<QString> result;
729 QDir dir(path);
730 if (dir.exists()) {
731 foreach (QString filename, dir.entryList(filter, QDir::Files)) {
732 QFileInfo file(filename);
733 QString name = file.completeBaseName();
734 if (name.length() <= maxLen) {
735 result.insert(name);
739 return result;
742 bool caseInsensitiveLessThan(const QString &s1, const QString &s2)
744 return s1.toLower() < s2.toLower();
747 bool GpsGlitchFilter::isGlitch(GpsCoord coord)
749 if ((fabs(coord.latitude) < 0.1) && (fabs(coord.longitude) < 0.1)) {
750 return true;
753 if (lastValid) {
754 if (fabs(coord.latitude - lastLat) > 0.01) {
755 // qDebug() << "GpsGlitchFilter(): latitude glitch " << coord.latitude << lastLat;
756 if ( ++glitchCount < 10) {
757 return true;
760 if (fabs(coord.longitude - lastLon) > 0.01) {
761 // qDebug() << "GpsGlitchFilter(): longitude glitch " << coord.longitude << lastLon;
762 if ( ++glitchCount < 10) {
763 return true;
767 lastLat = coord.latitude;
768 lastLon = coord.longitude;
769 lastValid = true;
770 glitchCount = 0;
771 return false;
774 bool GpsLatLonFilter::isValid(GpsCoord coord)
776 if (lastLat == coord.latitude) {
777 return false;
779 if (lastLon == coord.longitude) {
780 return false;
782 lastLat = coord.latitude;
783 lastLon = coord.longitude;
784 return true;
787 double toDecimalCoordinate(const QString & value)
789 if (value.isEmpty()) return 0.0;
790 double temp = int(value.left(value.length()-1).toDouble() / 100);
791 double result = temp + (value.left(value.length() - 1).toDouble() - temp * 100) / 60.0;
792 QChar direction = value.at(value.size()-1);
793 if ((direction == 'S') || (direction == 'W')) {
794 result = -result;
796 return result;
799 GpsCoord extractGpsCoordinates(const QString & position)
801 GpsCoord result;
802 QStringList parts = position.split(' ');
803 if (parts.size() == 2) {
804 QString value = parts.at(0).trimmed();
805 QChar direction = value.at(value.size()-1);
806 if (direction == 'E' || direction == 'W') {
807 // OpenTX 2.1 format: "NNN.MMM[E|W] NNN.MMM[N|S]" <longitude> <latitude>
808 result.latitude = toDecimalCoordinate(parts.at(1).trimmed());
809 result.longitude = toDecimalCoordinate(parts.at(0).trimmed());
811 else {
812 // OpenTX 2.2 format: "DD.DDDDDD DD.DDDDDD" <latitude> <longitude> both in Signed degrees format (DDD.dddd)
813 // Precede South latitudes and West longitudes with a minus sign.
814 // Latitudes range from -90 to 90.
815 // Longitudes range from -180 to 180.
816 result.latitude = parts.at(0).trimmed().toDouble();
817 result.longitude = parts.at(1).trimmed().toDouble();
820 return result;
823 TableLayout::TableLayout(QWidget * parent, int rowCount, const QStringList & headerLabels)
825 #if defined(TABLE_LAYOUT)
826 tableWidget = new QTableWidget(parent);
827 QVBoxLayout * layout = new QVBoxLayout();
828 layout->addWidget(tableWidget);
829 layout->setContentsMargins(0, 0, 0, 0);
830 parent->setLayout(layout);
832 tableWidget->setRowCount(rowCount);
833 tableWidget->setColumnCount(headerLabels.size());
834 tableWidget->setShowGrid(false);
835 tableWidget->verticalHeader()->setVisible(false);
836 tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
837 tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
838 tableWidget->setFrameStyle(QFrame::NoFrame | QFrame::Plain);
839 tableWidget->setStyleSheet("QTableWidget {background-color: transparent;}");
840 tableWidget->setHorizontalHeaderLabels(headerLabels);
841 #else
842 gridWidget = new QGridLayout(parent);
844 int col = 0;
845 foreach(QString text, headerLabels) {
846 QLabel *label = new QLabel();
847 label->setFrameShape(QFrame::Panel);
848 label->setFrameShadow(QFrame::Raised);
849 label->setMidLineWidth(0);
850 label->setAlignment(Qt::AlignCenter);
851 label->setMargin(5);
852 label->setText(text);
853 // if (!minimize)
854 // label->setMinimumWidth(100);
855 gridWidget->addWidget(label, 0, col++);
857 #endif
860 void TableLayout::addWidget(int row, int column, QWidget * widget)
862 #if defined(TABLE_LAYOUT)
863 QHBoxLayout * layout = new QHBoxLayout(tableWidget);
864 layout->addWidget(widget);
865 addLayout(row, column, layout);
866 #else
867 gridWidget->addWidget(widget, row + 1, column);
868 #endif
871 void TableLayout::addLayout(int row, int column, QLayout * layout)
873 #if defined(TABLE_LAYOUT)
874 layout->setContentsMargins(1, 3, 1, 3);
875 QWidget * containerWidget = new QWidget(tableWidget);
876 containerWidget->setLayout(layout);
877 tableWidget->setCellWidget(row, column, containerWidget);
878 #else
879 gridWidget->addLayout(layout, row + 1, column);
880 #endif
883 void TableLayout::resizeColumnsToContents()
885 #if defined(TABLE_LAYOUT)
886 tableWidget->resizeColumnsToContents();
887 #else
888 #endif
891 void TableLayout::setColumnWidth(int col, int width)
893 #if defined(TABLE_LAYOUT)
894 tableWidget->setColumnWidth(col, width);
895 #else
896 #endif
899 void TableLayout::pushRowsUp(int row)
901 #if defined(TABLE_LAYOUT)
902 #else
903 // Push the rows up
904 QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
905 gridWidget->addItem(spacer, row, 0);
906 #endif
907 // Push rows upward
908 // addDoubleSpring(gridLayout, 5, num_fsw+1);