Updated French Translation (#5484)
[opentx.git] / companion / src / helpers.cpp
blobb775a3eff61734c0085377db5e25e32abe6e1dfe
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 "simulatormainwindow.h"
33 #include "storage/sdcard.h"
35 #include <QLabel>
36 #include <QMessageBox>
38 using namespace Helpers;
40 Stopwatch gStopwatch("global");
42 const QColor colors[CPN_MAX_CURVES] = {
43 QColor(0,0,127),
44 QColor(0,127,0),
45 QColor(127,0,0),
46 QColor(0,127,127),
47 QColor(127,0,127),
48 QColor(127,127,0),
49 QColor(127,127,127),
50 QColor(0,0,255),
51 QColor(0,127,255),
52 QColor(127,0,255),
53 QColor(0,255,0),
54 QColor(0,255,127),
55 QColor(127,255,0),
56 QColor(255,0,0),
57 QColor(255,0,127),
58 QColor(255,127,0),
59 QColor(0,0,127),
60 QColor(0,127,0),
61 QColor(127,0,0),
62 QColor(0,127,127),
63 QColor(127,0,127),
64 QColor(127,127,0),
65 QColor(127,127,127),
66 QColor(0,0,255),
67 QColor(0,127,255),
68 QColor(127,0,255),
69 QColor(0,255,0),
70 QColor(0,255,127),
71 QColor(127,255,0),
72 QColor(255,0,0),
73 QColor(255,0,127),
74 QColor(255,127,0),
78 * GVarGroup
81 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):
82 QObject(),
83 weightGV(weightGV),
84 weightSB(weightSB),
85 sb(dynamic_cast<QSpinBox *>(weightSB)),
86 dsb(dynamic_cast<QDoubleSpinBox *>(weightSB)),
87 weightCB(weightCB),
88 weight(weight),
89 step(step),
90 lock(true)
92 if (allowGvars && getCurrentFirmware()->getCapability(Gvars)) {
93 Helpers::populateGVCB(*weightCB, weight, model);
94 connect(weightGV, SIGNAL(stateChanged(int)), this, SLOT(gvarCBChanged(int)));
95 connect(weightCB, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesChanged()));
97 else {
98 weightGV->hide();
99 if (weight > maxi || weight < mini) {
100 weight = deflt;
104 int val;
106 if (weight>maxi || weight<mini) {
107 val = deflt;
108 weightGV->setChecked(true);
109 weightSB->hide();
110 weightCB->show();
112 else {
113 val = weight;
114 weightGV->setChecked(false);
115 weightSB->show();
116 weightCB->hide();
119 if (sb) {
120 sb->setMinimum(mini);
121 sb->setMaximum(maxi);
122 sb->setValue(val);
124 else {
125 dsb->setMinimum(mini*step);
126 dsb->setMaximum(maxi*step);
127 dsb->setValue(val*step);
130 connect(weightSB, SIGNAL(editingFinished()), this, SLOT(valuesChanged()));
132 lock = false;
135 void GVarGroup::gvarCBChanged(int state)
137 weightCB->setVisible(state);
138 if (weightSB)
139 weightSB->setVisible(!state);
140 else
141 weightSB->setVisible(!state);
142 valuesChanged();
145 void GVarGroup::valuesChanged()
147 if (!lock) {
148 if (weightGV->isChecked())
149 weight = weightCB->itemData(weightCB->currentIndex()).toInt();
150 else if (sb)
151 weight = sb->value();
152 else
153 weight = round(dsb->value()/step);
155 emit valueChanged();
160 * CurveGroup
163 CurveGroup::CurveGroup(QComboBox * curveTypeCB, QCheckBox * curveGVarCB, QComboBox * curveValueCB, QSpinBox * curveValueSB, CurveReference & curve, const ModelData & model, unsigned int flags):
164 QObject(),
165 curveTypeCB(curveTypeCB),
166 curveGVarCB(curveGVarCB),
167 curveValueCB(curveValueCB),
168 curveValueSB(curveValueSB),
169 curve(curve),
170 model(model),
171 flags(flags),
172 lock(false),
173 lastType(-1)
175 if (!(flags & HIDE_DIFF)) curveTypeCB->addItem(tr("Diff"), 0);
176 if (!(flags & HIDE_EXPO)) curveTypeCB->addItem(tr("Expo"), 1);
177 curveTypeCB->addItem(tr("Func"), 2);
178 curveTypeCB->addItem(tr("Curve"), 3);
180 curveValueCB->setMaxVisibleItems(10);
182 connect(curveTypeCB, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)));
183 connect(curveGVarCB, SIGNAL(stateChanged(int)), this, SLOT(gvarCBChanged(int)));
184 connect(curveValueCB, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesChanged()));
185 connect(curveValueSB, SIGNAL(editingFinished()), this, SLOT(valuesChanged()));
187 update();
190 void CurveGroup::update()
192 lock = true;
194 int found = curveTypeCB->findData(curve.type);
195 if (found < 0) found = 0;
196 curveTypeCB->setCurrentIndex(found);
198 if (curve.type == CurveReference::CURVE_REF_DIFF || curve.type == CurveReference::CURVE_REF_EXPO) {
199 curveGVarCB->setVisible(getCurrentFirmware()->getCapability(Gvars));
200 if (curve.value > 100 || curve.value < -100) {
201 curveGVarCB->setChecked(true);
202 if (lastType != CurveReference::CURVE_REF_DIFF && lastType != CurveReference::CURVE_REF_EXPO) {
203 lastType = curve.type;
204 Helpers::populateGVCB(*curveValueCB, curve.value, model);
206 curveValueCB->show();
207 curveValueSB->hide();
209 else {
210 curveGVarCB->setChecked(false);
211 curveValueSB->setMinimum(-100);
212 curveValueSB->setMaximum(100);
213 curveValueSB->setValue(curve.value);
214 curveValueSB->show();
215 curveValueCB->hide();
218 else {
219 curveGVarCB->hide();
220 curveValueSB->hide();
221 curveValueCB->show();
222 switch (curve.type) {
223 case CurveReference::CURVE_REF_FUNC:
224 if (lastType != curve.type) {
225 lastType = curve.type;
226 curveValueCB->clear();
227 for (int i=0; i<=6/*TODO constant*/; i++) {
228 curveValueCB->addItem(CurveReference(CurveReference::CURVE_REF_FUNC, i).toString(&model, false));
231 curveValueCB->setCurrentIndex(curve.value);
232 break;
233 case CurveReference::CURVE_REF_CUSTOM:
235 int numcurves = getCurrentFirmware()->getCapability(NumCurves);
236 if (lastType != curve.type) {
237 lastType = curve.type;
238 curveValueCB->clear();
239 for (int i= ((flags & HIDE_NEGATIVE_CURVES) ? 0 : -numcurves); i<=numcurves; i++) {
240 curveValueCB->addItem(CurveReference(CurveReference::CURVE_REF_CUSTOM, i).toString(&model, false), i);
241 if (i == curve.value) {
242 curveValueCB->setCurrentIndex(curveValueCB->count() - 1);
246 break;
248 default:
249 break;
253 lock = false;
256 void CurveGroup::gvarCBChanged(int state)
258 if (!lock) {
259 if (state) {
260 curve.value = 10000+1; // TODO constant in EEpromInterface ...
261 lastType = -1; // quickfix for issue #3518: force refresh of curveValueCB at next update() to set current index to GV1
263 else {
264 curve.value = 0; // TODO could be better
267 update();
271 void CurveGroup::typeChanged(int value)
273 if (!lock) {
274 int type = curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt();
275 switch (type) {
276 case 0:
277 curve = CurveReference(CurveReference::CURVE_REF_DIFF, 0);
278 break;
279 case 1:
280 curve = CurveReference(CurveReference::CURVE_REF_EXPO, 0);
281 break;
282 case 2:
283 curve = CurveReference(CurveReference::CURVE_REF_FUNC, 0);
284 break;
285 case 3:
286 curve = CurveReference(CurveReference::CURVE_REF_CUSTOM, 0);
287 break;
290 update();
294 void CurveGroup::valuesChanged()
296 if (!lock) {
297 switch (curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt()) {
298 case 0:
299 case 1:
301 int value;
302 if (curveGVarCB->isChecked())
303 value = curveValueCB->itemData(curveValueCB->currentIndex()).toInt();
304 else
305 value = curveValueSB->value();
306 curve = CurveReference(curveTypeCB->itemData(curveTypeCB->currentIndex()).toInt() == 0 ? CurveReference::CURVE_REF_DIFF : CurveReference::CURVE_REF_EXPO, value);
307 break;
309 case 2:
310 curve = CurveReference(CurveReference::CURVE_REF_FUNC, curveValueCB->currentIndex());
311 break;
312 case 3:
313 curve = CurveReference(CurveReference::CURVE_REF_CUSTOM, curveValueCB->itemData(curveValueCB->currentIndex()).toInt());
314 break;
317 update();
322 * Helpers namespace functions
325 void Helpers::populateGVCB(QComboBox & b, int value, const ModelData & model)
327 int count = getCurrentFirmware()->getCapability(Gvars);
329 b.clear();
331 for (int i=-count; i<=-1; i++) {
332 int16_t gval = (int16_t)(-10000+i);
333 b.addItem("-" + RawSource(SOURCE_TYPE_GVAR, abs(i)-1).toString(&model), gval);
336 for (int i=1; i<=count; i++) {
337 int16_t gval = (int16_t)(10000+i);
338 b.addItem(RawSource(SOURCE_TYPE_GVAR, i-1).toString(&model), gval);
341 b.setCurrentIndex(b.findData(value));
342 if (b.currentIndex() == -1)
343 b.setCurrentIndex(count);
346 // Returns Diff/Expo/Weight/Offset adjustment value as either a percentage or a global variable name.
347 QString Helpers::getAdjustmentString(int16_t val, const ModelData * model, bool sign)
349 QString ret;
350 if (val >= -10000 && val <= 10000) {
351 ret = "%1%";
352 if (sign && val > 0)
353 ret.prepend("+");
354 ret = ret.arg(val);
356 else {
357 ret = RawSource(SOURCE_TYPE_GVAR, abs(val) - 10001).toString(model);
358 if (val < 0)
359 ret.prepend("-");
360 else if (sign)
361 ret.prepend("+");
363 return ret;
366 void Helpers::populateGvarUseCB(QComboBox * b, unsigned int phase)
368 b->addItem(QObject::tr("Own value"));
369 for (int i=0; i<getCurrentFirmware()->getCapability(FlightModes); i++) {
370 if (i != (int)phase) {
371 b->addItem(QObject::tr("Flight mode %1 value").arg(i));
376 void Helpers::populateFileComboBox(QComboBox * b, const QSet<QString> & set, const QString & current)
378 b->clear();
379 b->addItem("----");
381 bool added = false;
382 // Convert set into list and sort it alphabetically case insensitive
383 QStringList list = QStringList::fromSet(set);
384 qSort(list.begin(), list.end(), caseInsensitiveLessThan);
385 foreach (QString entry, list) {
386 b->addItem(entry);
387 if (entry == current) {
388 b->setCurrentIndex(b->count()-1);
389 added = true;
393 if (!added && !current.isEmpty()) {
394 b->addItem(current);
395 b->setCurrentIndex(b->count()-1);
399 void Helpers::getFileComboBoxValue(QComboBox * b, char * dest, int length)
401 memset(dest, 0, length+1);
402 if (b->currentText() != "----") {
403 strncpy(dest, b->currentText().toLatin1(), length);
407 void Helpers::addRawSourceItems(QStandardItemModel * itemModel, const RawSourceType & type, int count, const GeneralSettings * const generalSettings,
408 const ModelData * const model, const int start, const QList<int> exclude)
410 for (int i = start; i < start + count; i++) {
411 if (exclude.contains(i))
412 continue;
414 RawSource src = RawSource(type, i);
415 if (model) {
416 if (type == SOURCE_TYPE_VIRTUAL_INPUT && !model->isInputValid(i))
417 continue;
419 if (generalSettings) {
420 int genAryIdx = 0;
421 if (type == SOURCE_TYPE_STICK && ((src.isPot(&genAryIdx) && !generalSettings->isPotAvailable(genAryIdx)) || (src.isSlider(&genAryIdx) && !generalSettings->isSliderAvailable(genAryIdx))))
422 continue;
425 QStandardItem * modelItem = new QStandardItem(src.toString(model, generalSettings));
426 modelItem->setData(src.toValue(), Qt::UserRole);
427 itemModel->appendRow(modelItem);
429 if (type == SOURCE_TYPE_SWITCH && generalSettings && IS_HORUS_OR_TARANIS(getCurrentBoard()) && !generalSettings->switchSourceAllowedTaranis(i)) {
430 modelItem->setData(0, Qt::UserRole - 1); // trick to disable an item
435 QStandardItemModel * Helpers::getRawSourceItemModel(const GeneralSettings * const generalSettings, const ModelData * const model, unsigned int flags)
437 QStandardItemModel * itemModel = new QStandardItemModel();
438 Boards board = Boards(getCurrentBoard());
439 Firmware * fw = getCurrentFirmware();
441 if (flags & POPULATE_NONE) {
442 addRawSourceItems(itemModel, SOURCE_TYPE_NONE, 1, generalSettings, model);
445 if (flags & POPULATE_SCRIPT_OUTPUTS) {
446 for (int i=0; i < getCurrentFirmware()->getCapability(LuaScripts); i++) {
447 addRawSourceItems(itemModel, SOURCE_TYPE_LUA_OUTPUT, fw->getCapability(LuaOutputsPerScript), generalSettings, model, i * 16);
451 if (model && (flags & POPULATE_VIRTUAL_INPUTS)) {
452 addRawSourceItems(itemModel, SOURCE_TYPE_VIRTUAL_INPUT, fw->getCapability(VirtualInputs), generalSettings, model);
455 if (flags & POPULATE_SOURCES) {
456 int totalSources = CPN_MAX_STICKS + board.getCapability(Board::Pots) + board.getCapability(Board::Sliders) + board.getCapability(Board::MouseAnalogs);
457 addRawSourceItems(itemModel, SOURCE_TYPE_STICK, totalSources, generalSettings, model);
458 addRawSourceItems(itemModel, SOURCE_TYPE_ROTARY_ENCODER, fw->getCapability(RotaryEncoders), generalSettings, model);
461 if (flags & POPULATE_TRIMS) {
462 addRawSourceItems(itemModel, SOURCE_TYPE_TRIM, board.getCapability(Board::NumTrims), generalSettings, model);
465 if (flags & POPULATE_SOURCES) {
466 addRawSourceItems(itemModel, SOURCE_TYPE_MAX, 1, generalSettings, model);
469 if (flags & POPULATE_SWITCHES) {
470 addRawSourceItems(itemModel, SOURCE_TYPE_SWITCH, board.getCapability(Board::Switches), generalSettings, model);
471 addRawSourceItems(itemModel, SOURCE_TYPE_CUSTOM_SWITCH, fw->getCapability(LogicalSwitches), generalSettings, model);
474 if (flags & POPULATE_SOURCES) {
475 addRawSourceItems(itemModel, SOURCE_TYPE_CYC, CPN_MAX_CYC, generalSettings, model);
476 addRawSourceItems(itemModel, SOURCE_TYPE_PPM, fw->getCapability(TrainerInputs), generalSettings, model);
477 addRawSourceItems(itemModel, SOURCE_TYPE_CH, fw->getCapability(Outputs), generalSettings, model);
480 if (flags & POPULATE_TELEMETRY) {
481 if (IS_ARM(board.getBoardType())) {
482 addRawSourceItems(itemModel, SOURCE_TYPE_SPECIAL, 5, generalSettings, model);
484 if (model) {
485 QList<int> exclude;
486 for (int i=0; i < CPN_MAX_SENSORS * 3; ++i) {
487 //this conditon must be false if we populate Global Functions where model = 0
488 if (!model->sensorData[div(i, 3).quot].isAvailable())
489 exclude << i;
491 if (exclude.size() < CPN_MAX_SENSORS * 3)
492 addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, CPN_MAX_SENSORS * 3, generalSettings, model, 0, exclude);
495 else {
496 QList<int> exclude;
497 if (!fw->getCapability(RtcTime))
498 exclude << TELEMETRY_SOURCE_TX_TIME;
499 if (!fw->getCapability(SportTelemetry))
500 exclude << TELEMETRY_SOURCE_SWR;
501 if (!IS_ARM(board.getBoardType()))
502 exclude << TELEMETRY_SOURCE_TIMER3;
503 int count = ((flags & POPULATE_TELEMETRYEXT) ? TELEMETRY_SOURCES_STATUS_COUNT : TELEMETRY_SOURCES_COUNT);
504 addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, count, generalSettings, model, 0, exclude);
508 if (flags & POPULATE_GVARS) {
509 addRawSourceItems(itemModel, SOURCE_TYPE_GVAR, fw->getCapability(Gvars), generalSettings, model);
512 return itemModel;
515 QString image2qstring(QImage image)
517 if (image.isNull())
518 return "";
519 QBuffer buffer;
520 image.save(&buffer, "PNG");
521 QString ImageStr;
522 int b=0;
523 int size=buffer.data().size();
524 for (int j = 0; j < size; j++) {
525 b=buffer.data().at(j);
526 ImageStr += QString("%1").arg(b&0xff, 2, 16, QChar('0'));
528 return ImageStr;
531 int findmult(float value, float base)
533 int vvalue = value*10;
534 int vbase = base*10;
535 vvalue--;
537 int mult = 0;
538 for (int i=8; i>=0; i--) {
539 if (vvalue/vbase >= (1<<i)) {
540 mult = i+1;
541 break;
545 return mult;
548 QString getFrSkyAlarmType(int alarm)
550 switch (alarm) {
551 case 1:
552 return QObject::tr("Yellow");
553 case 2:
554 return QObject::tr("Orange");
555 case 3:
556 return QObject::tr("Red");
557 default:
558 return "----";
562 QString getFrSkyUnits(int units)
564 switch(units) {
565 case 1:
566 return QObject::tr("---");
567 default:
568 return "V";
572 QString getFrSkyProtocol(int protocol)
574 switch(protocol) {
575 case 2:
576 if ((getCurrentFirmware()->getCapability(Telemetry) & TM_HASWSHH))
577 return QObject::tr("Winged Shadow How High");
578 else
579 return QObject::tr("Winged Shadow How High (not supported)");
580 case 1:
581 return QObject::tr("FrSky Sensor Hub");
582 default:
583 return QObject::tr("None");
587 QString getFrSkyMeasure(int units)
589 switch(units) {
590 case 1:
591 return QObject::tr("Imperial");
592 default:
593 return QObject::tr("Metric");
597 QString getFrSkySrc(int index)
599 return RawSource(SOURCE_TYPE_TELEMETRY, index-1).toString();
602 QString getTheme()
604 int theme_set = g.theme();
605 QString Theme;
606 switch(theme_set) {
607 case 0:
608 Theme="classic";
609 break;
610 case 2:
611 Theme="monowhite";
612 break;
613 case 3:
614 Theme="monochrome";
615 break;
616 case 4:
617 Theme="monoblue";
618 break;
619 default:
620 Theme="yerico";
621 break;
623 return Theme;
626 CompanionIcon::CompanionIcon(const QString &baseimage)
628 static QString theme = getTheme();
629 addFile(":/themes/"+theme+"/16/"+baseimage, QSize(16,16));
630 addFile(":/themes/"+theme+"/24/"+baseimage, QSize(24,24));
631 addFile(":/themes/"+theme+"/32/"+baseimage, QSize(32,32));
632 addFile(":/themes/"+theme+"/48/"+baseimage, QSize(48,48));
635 void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx)
637 QString fwId = SimulatorLoader::findSimulatorByFirmwareName(getCurrentFirmware()->getId());
638 if (fwId.isEmpty()) {
639 QMessageBox::warning(NULL,
640 QObject::tr("Warning"),
641 QObject::tr("Simulator for this firmware is not yet available"));
642 return;
645 RadioData * simuData = new RadioData(radioData);
646 unsigned int flags = 0;
648 if (modelIdx >= 0) {
649 flags |= SIMULATOR_FLAGS_NOTX;
650 simuData->setCurrentModel(modelIdx);
653 SimulatorMainWindow * dialog = new SimulatorMainWindow(parent, fwId, flags);
654 dialog->setWindowModality(Qt::ApplicationModal);
655 dialog->setAttribute(Qt::WA_DeleteOnClose);
657 QObject::connect(dialog, &SimulatorMainWindow::destroyed, [simuData] (void) {
658 // TODO simuData and Horus tmp directory is deleted on simulator close OR we could use it to get back data from the simulation
659 delete simuData;
662 QString resultMsg;
663 if (dialog->getExitStatus(&resultMsg)) {
664 if (resultMsg.isEmpty())
665 resultMsg = QObject::tr("Uknown error during Simulator startup.");
666 QMessageBox::critical(NULL, QObject::tr("Simulator Error"), resultMsg);
667 dialog->deleteLater();
669 else if (dialog->setRadioData(simuData)) {
670 dialog->show();
672 else {
673 QMessageBox::critical(NULL, QObject::tr("Data Load Error"), QObject::tr("Error occurred while starting simulator."));
674 dialog->deleteLater();
678 QPixmap makePixMap(const QImage & image)
680 Firmware * firmware = getCurrentFirmware();
681 QImage result = image.scaled(firmware->getCapability(LcdWidth), firmware->getCapability(LcdHeight));
682 if (firmware->getCapability(LcdDepth) == 4) {
683 result = result.convertToFormat(QImage::Format_RGB32);
684 for (int i = 0; i < result.width(); ++i) {
685 for (int j = 0; j < result.height(); ++j) {
686 QRgb col = result.pixel(i, j);
687 int gray = qGray(col);
688 result.setPixel(i, j, qRgb(gray, gray, gray));
692 else {
693 result = result.convertToFormat(QImage::Format_Mono);
696 return QPixmap::fromImage(result);
700 int version2index(const QString & version)
702 int result = 999;
703 QStringList parts;
704 QString mainVersion = version;
705 if (version.contains("RC")) {
706 parts = version.split("RC");
707 result = parts[1].toInt() + 900; // RC0 = 900; RC1=901,..
708 mainVersion = parts[0];
710 else if (version.contains("N")) {
711 parts = version.split("N");
712 result = parts[1].toInt(); // nightly build up to 899
713 mainVersion = parts[0];
715 parts = mainVersion.split('.');
716 if (parts.size() > 2)
717 result += 1000 * parts[2].toInt();
718 if (parts.size() > 1)
719 result += 100000 * parts[1].toInt();
720 if (parts.size() > 0)
721 result += 10000000 * parts[0].toInt();
722 return result;
725 const QString index2version(int index)
727 QString result;
728 QString templt("%1.%2.%3");
729 if (index >= 19900000) {
730 int nightly = index % 1000;
731 index /= 1000;
732 int revision = index % 100;
733 index /= 100;
734 int minor = index % 100;
735 int major = index / 100;
736 result = templt.arg(major).arg(minor).arg(revision);
737 if (nightly > 0 && nightly < 900) {
738 result += "N" + QString::number(nightly);
740 else if (nightly >= 900 && nightly < 1000) {
741 result += "RC" + QString::number(nightly-900);
744 else if (index >= 19900) {
745 int revision = index % 100;
746 index /= 100;
747 int minor = index % 100;
748 int major = index / 100;
749 result = templt.arg(major).arg(minor).arg(revision);
751 return result;
754 bool qunlink(const QString & fileName)
756 return QFile::remove(fileName);
759 QString generateProcessUniqueTempFileName(const QString & fileName)
761 QString sanitizedFileName = fileName;
762 sanitizedFileName.remove('/');
763 return QDir::tempPath() + QString("/%1-").arg(QCoreApplication::applicationPid()) + sanitizedFileName;
766 bool isTempFileName(const QString & fileName)
768 return fileName.startsWith(QDir::tempPath());
771 QString getSoundsPath(const GeneralSettings &generalSettings)
773 QString path = g.profile[g.id()].sdPath() + "/SOUNDS/";
774 QString lang = generalSettings.ttsLanguage;
775 if (lang.isEmpty())
776 lang = "en";
777 path.append(lang);
778 return path;
781 QSet<QString> getFilesSet(const QString &path, const QStringList &filter, int maxLen)
783 QSet<QString> result;
784 QDir dir(path);
785 if (dir.exists()) {
786 foreach (QString filename, dir.entryList(filter, QDir::Files)) {
787 QFileInfo file(filename);
788 QString name = file.completeBaseName();
789 if (name.length() <= maxLen) {
790 result.insert(name);
794 return result;
797 bool caseInsensitiveLessThan(const QString &s1, const QString &s2)
799 return s1.toLower() < s2.toLower();
802 bool GpsGlitchFilter::isGlitch(GpsCoord coord)
804 if ((fabs(coord.latitude) < 0.1) && (fabs(coord.longitude) < 0.1)) {
805 return true;
808 if (lastValid) {
809 if (fabs(coord.latitude - lastLat) > 0.01) {
810 // qDebug() << "GpsGlitchFilter(): latitude glitch " << coord.latitude << lastLat;
811 if ( ++glitchCount < 10) {
812 return true;
815 if (fabs(coord.longitude - lastLon) > 0.01) {
816 // qDebug() << "GpsGlitchFilter(): longitude glitch " << coord.longitude << lastLon;
817 if ( ++glitchCount < 10) {
818 return true;
822 lastLat = coord.latitude;
823 lastLon = coord.longitude;
824 lastValid = true;
825 glitchCount = 0;
826 return false;
829 bool GpsLatLonFilter::isValid(GpsCoord coord)
831 if (lastLat == coord.latitude) {
832 return false;
834 if (lastLon == coord.longitude) {
835 return false;
837 lastLat = coord.latitude;
838 lastLon = coord.longitude;
839 return true;
842 double toDecimalCoordinate(const QString & value)
844 if (value.isEmpty()) return 0.0;
845 double temp = int(value.left(value.length()-1).toDouble() / 100);
846 double result = temp + (value.left(value.length() - 1).toDouble() - temp * 100) / 60.0;
847 QChar direction = value.at(value.size()-1);
848 if ((direction == 'S') || (direction == 'W')) {
849 result = -result;
851 return result;
854 GpsCoord extractGpsCoordinates(const QString & position)
856 GpsCoord result;
857 QStringList parts = position.split(' ');
858 if (parts.size() == 2) {
859 QString value = parts.at(0).trimmed();
860 QChar direction = value.at(value.size()-1);
861 if (direction == 'E' || direction == 'W') {
862 // OpenTX 2.1 format: "NNN.MMM[E|W] NNN.MMM[N|S]" <longitude> <latitude>
863 result.latitude = toDecimalCoordinate(parts.at(1).trimmed());
864 result.longitude = toDecimalCoordinate(parts.at(0).trimmed());
866 else {
867 // OpenTX 2.2 format: "DD.DDDDDD DD.DDDDDD" <latitude> <longitude> both in Signed degrees format (DDD.dddd)
868 // Precede South latitudes and West longitudes with a minus sign.
869 // Latitudes range from -90 to 90.
870 // Longitudes range from -180 to 180.
871 result.latitude = parts.at(0).trimmed().toDouble();
872 result.longitude = parts.at(1).trimmed().toDouble();
875 return result;
878 TableLayout::TableLayout(QWidget * parent, int rowCount, const QStringList & headerLabels)
880 #if defined(TABLE_LAYOUT)
881 tableWidget = new QTableWidget(parent);
882 QVBoxLayout * layout = new QVBoxLayout();
883 layout->addWidget(tableWidget);
884 layout->setContentsMargins(0, 0, 0, 0);
885 parent->setLayout(layout);
887 tableWidget->setRowCount(rowCount);
888 tableWidget->setColumnCount(headerLabels.size());
889 tableWidget->setShowGrid(false);
890 tableWidget->verticalHeader()->setVisible(false);
891 tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
892 tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
893 tableWidget->setFrameStyle(QFrame::NoFrame | QFrame::Plain);
894 tableWidget->setStyleSheet("QTableWidget {background-color: transparent;}");
895 tableWidget->setHorizontalHeaderLabels(headerLabels);
896 #else
897 gridWidget = new QGridLayout(parent);
899 int col = 0;
900 foreach(QString text, headerLabels) {
901 QLabel *label = new QLabel();
902 label->setFrameShape(QFrame::Panel);
903 label->setFrameShadow(QFrame::Raised);
904 label->setMidLineWidth(0);
905 label->setAlignment(Qt::AlignCenter);
906 label->setMargin(5);
907 label->setText(text);
908 // if (!minimize)
909 // label->setMinimumWidth(100);
910 gridWidget->addWidget(label, 0, col++);
912 #endif
915 void TableLayout::addWidget(int row, int column, QWidget * widget)
917 #if defined(TABLE_LAYOUT)
918 QHBoxLayout * layout = new QHBoxLayout(tableWidget);
919 layout->addWidget(widget);
920 addLayout(row, column, layout);
921 #else
922 gridWidget->addWidget(widget, row + 1, column);
923 #endif
926 void TableLayout::addLayout(int row, int column, QLayout * layout)
928 #if defined(TABLE_LAYOUT)
929 layout->setContentsMargins(1, 3, 1, 3);
930 QWidget * containerWidget = new QWidget(tableWidget);
931 containerWidget->setLayout(layout);
932 tableWidget->setCellWidget(row, column, containerWidget);
933 #else
934 gridWidget->addLayout(layout, row + 1, column);
935 #endif
938 void TableLayout::resizeColumnsToContents()
940 #if defined(TABLE_LAYOUT)
941 tableWidget->resizeColumnsToContents();
942 #else
943 #endif
946 void TableLayout::setColumnWidth(int col, int width)
948 #if defined(TABLE_LAYOUT)
949 tableWidget->setColumnWidth(col, width);
950 #else
951 #endif
954 void TableLayout::pushRowsUp(int row)
956 #if defined(TABLE_LAYOUT)
957 #else
958 // Push the rows up
959 QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
960 gridWidget->addItem(spacer, row, 0);
961 #endif
962 // Push rows upward
963 // addDoubleSpring(gridLayout, 5, num_fsw+1);