Update CREDITS.txt
[opentx.git] / companion / src / modelprinter.cpp
blob966cdebed2b26ad9f963e87a38f664ce85f01c90
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 "helpers.h"
22 #include "modelprinter.h"
23 #include "multiprotocols.h"
24 #include "boards.h"
26 #include <QApplication>
27 #include <QPainter>
28 #include <QFile>
29 #include <QUrl>
30 #include "multiprotocols.h"
32 QString changeColor(const QString & input, const QString & to, const QString & from)
34 QString result = input;
35 return result.replace("color="+from, "color="+to);
38 ModelPrinter::ModelPrinter(Firmware * firmware, const GeneralSettings & generalSettings, const ModelData & model):
39 firmware(firmware),
40 generalSettings(generalSettings),
41 model(model)
45 ModelPrinter::~ModelPrinter()
49 QString formatTitle(const QString & name)
51 return QString("<b>" + name + "</b>&nbsp;");
54 void debugHtml(const QString & html)
56 QFile file("foo.html");
57 file.open(QIODevice::Truncate | QIODevice::WriteOnly);
58 file.write(html.toUtf8());
59 file.close();
62 QString addFont(const QString & input, const QString & color, const QString & size, const QString & face)
64 QString colorStr;
65 if (!color.isEmpty()) {
66 colorStr = "color=" + color;
68 QString sizeStr;
69 if (!size.isEmpty()) {
70 sizeStr = "size=" + size;
72 QString faceStr;
73 if (!face.isEmpty()) {
74 faceStr = "face='" + face + "'";
76 return "<font " + sizeStr + " " + faceStr + " " + colorStr + ">" + input + "</font>";
79 #define MASK_TIMEVALUE_HRSMINS 1
80 #define MASK_TIMEVALUE_ZEROHRS 2
81 #define MASK_TIMEVALUE_PADSIGN 3
83 QString ModelPrinter::printTimeValue(const int value, const unsigned int mask)
85 QString result;
86 int sign = 1;
87 int val = value;
88 if (val < 0) {
89 val = -val;
90 sign = -1;
92 result = (sign < 0 ? QString("-") : ((mask && MASK_TIMEVALUE_PADSIGN) ? QString(" ") : QString("")));
93 if (mask && MASK_TIMEVALUE_HRSMINS) {
94 int hours = val / 3600;
95 if (hours > 0 || (mask && MASK_TIMEVALUE_ZEROHRS)) {
96 val -= hours * 3600;
97 result.append(QString("%1:").arg(hours, 2, 10, QLatin1Char('0')));
100 int minutes = val / 60;
101 int seconds = val % 60;
102 result.append(QString("%1:%2").arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')));
103 return result;
106 #define BOOLEAN_ENABLEDISABLE 1
107 #define BOOLEAN_TRUEFALSE 2
108 #define BOOLEAN_YESNO 3
109 #define BOOLEAN_YN 4
110 #define BOOLEAN_ONOFF 5
112 QString ModelPrinter::printBoolean(const bool val, const int typ)
114 switch (typ) {
115 case BOOLEAN_ENABLEDISABLE:
116 return (val ? tr("Enable") : tr("Disable"));
117 case BOOLEAN_TRUEFALSE:
118 return (val ? tr("True") : tr("False"));
119 case BOOLEAN_YESNO:
120 return (val ? tr("Yes") : tr("No"));
121 case BOOLEAN_YN:
122 return (val ? tr("Y") : tr("N"));
123 case BOOLEAN_ONOFF:
124 return (val ? tr("ON") : tr("OFF"));
125 default:
126 return tr("???");
130 QString ModelPrinter::printEEpromSize()
132 return QString("%1 ").arg(getCurrentEEpromInterface()->getSize(model)) + tr("bytes");
135 QString ModelPrinter::printChannelName(int idx)
137 QString str = RawSource(SOURCE_TYPE_CH, idx).toString(&model, &generalSettings);
138 if (firmware->getCapability(ChannelsName)) {
139 str = str.leftJustified(firmware->getCapability(ChannelsName) + 5, ' ', false);
141 str.append(' ');
142 return str.toHtmlEscaped();
145 QString ModelPrinter::printTrimIncrementMode()
147 switch (model.trimInc) {
148 case -2:
149 return tr("Exponential");
150 case -1:
151 return tr("Extra Fine");
152 case 0:
153 return tr("Fine");
154 case 1:
155 return tr("Medium");
156 case 2:
157 return tr("Coarse");
158 default:
159 return tr("Unknown");
163 QString ModelPrinter::printModuleProtocol(unsigned int protocol)
165 static const char * strings[] = {
166 "OFF",
167 "PPM",
168 "Silverlit A", "Silverlit B", "Silverlit C",
169 "CTP1009",
170 "LP45", "DSM2", "DSMX",
171 "PPM16", "PPMsim",
172 "FrSky XJT (D16)", "FrSky XJT (D8)", "FrSky XJT (LR12)", "FrSky DJT",
173 "Crossfire",
174 "DIY Multiprotocol Module",
175 "FrSky R9M Module",
176 "SBUS output at VBat"
179 return CHECK_IN_ARRAY(strings, protocol);
182 QString ModelPrinter::printMultiRfProtocol(int rfProtocol, bool custom)
184 static const char *strings[] = {
185 "FlySky", "Hubsan", "FrSky", "Hisky", "V2x2", "DSM", "Devo", "YD717", "KN", "SymaX", "SLT", "CX10", "CG023",
186 "Bayang", "ESky", "MT99XX", "MJXQ", "Shenqi", "FY326", "SFHSS", "J6 PRO","FQ777","Assan","Hontai","OLRS",
187 "FlySky AFHDS2A", "Q2x2", "Walkera", "Q303", "GW008", "DM002", "CABELL", "Esky 150", "H8 3D"
189 if (custom)
190 return "Custom - proto " + QString::number(rfProtocol);
191 else
192 return CHECK_IN_ARRAY(strings, rfProtocol);
195 QString ModelPrinter::printMultiSubType(unsigned rfProtocol, bool custom, unsigned int subType) {
196 /* custom protocols */
198 if (custom)
199 rfProtocol = MM_RF_CUSTOM_SELECTED;
201 Multiprotocols::MultiProtocolDefinition pdef = multiProtocols.getProtocol(rfProtocol);
203 if (subType < (unsigned int) pdef.subTypeStrings.size())
204 return qApp->translate("Multiprotocols", qPrintable(pdef.subTypeStrings[subType]));
205 else
206 return "???";
209 QString ModelPrinter::printR9MPowerValue(unsigned subType, unsigned val, bool telem)
211 static const QStringList strFTC = QStringList() << tr("10mW") << tr("100mW") << tr("500mW") << tr("1W");
212 static const QStringList strLBT = QStringList() << tr("25mW") << tr("500mW");
215 if (subType == 0 && (int)val < strFTC.size())
216 return strFTC.at(val);
217 else if (subType == 1)
218 return (telem ? strLBT.at(0) : strLBT.at(1));
219 else
220 return "???";
223 QString ModelPrinter::printModuleSubType(unsigned protocol, unsigned subType, unsigned rfProtocol, bool custom)
225 static const char * strings[] = {
226 "FCC",
227 "LBT(EU)"
230 switch (protocol) {
231 case PULSES_MULTIMODULE:
232 return printMultiSubType(rfProtocol, custom, subType);
234 case PULSES_PXX_R9M:
235 return CHECK_IN_ARRAY(strings, subType);
237 default:
238 return "???";
242 QString ModelPrinter::printModule(int idx)
244 QStringList str;
245 QString result;
246 ModuleData module = model.moduleData[(idx<0 ? CPN_MAX_MODULES : idx)];
247 if (idx < 0) {
248 str += tr("Mode") + QString("(%1)").arg(printTrainerMode());
249 if (IS_HORUS_OR_TARANIS(firmware->getBoard())) {
250 if (model.trainerMode == TRAINER_SLAVE_JACK) {
251 str += tr("Channels") + QString("(%1-%2)").arg(module.channelsStart + 1).arg(module.channelsStart + module.channelsCount);
252 str += tr("Frame length") + QString("(%1ms)").arg(printPPMFrameLength(module.ppm.frameLength));
253 str += tr("PPM delay") + QString("(%1us)").arg(module.ppm.delay);
254 str += tr("Polarity") + QString("(%1)").arg(module.polarityToString());
257 result = str.join(", ");
259 else {
260 str += printModuleType(idx);
261 str += tr("Protocol") + QString("(%1)").arg(printModuleProtocol(module.protocol));
262 if (module.protocol) {
263 str += tr("Channels") + QString("(%1-%2)").arg(module.channelsStart + 1).arg(module.channelsStart + module.channelsCount);
264 if (module.protocol == PULSES_PPM || module.protocol == PULSES_SBUS) {
265 str += tr("Frame length") + QString("(%1ms)").arg(printPPMFrameLength(module.ppm.frameLength));
266 str += tr("Polarity") + QString("(%1)").arg(module.polarityToString());
267 if (module.protocol == PULSES_PPM)
268 str += tr("Delay") + QString("(%1us)").arg(module.ppm.delay);
270 else {
271 if (!(module.protocol == PULSES_PXX_XJT_D8 || module.protocol == PULSES_CROSSFIRE || module.protocol == PULSES_SBUS)) {
272 str += tr("Receiver") + QString("(%1)").arg(module.modelId);
274 if (module.protocol == PULSES_MULTIMODULE) {
275 str += tr("Radio protocol") + QString("(%1)").arg(printMultiRfProtocol(module.multi.rfProtocol, module.multi.customProto));
276 str += tr("Subtype") + QString("(%1)").arg(printMultiSubType(module.multi.rfProtocol, module.multi.customProto, module.subType));
277 str += tr("Option value") + QString("(%1)").arg(module.multi.optionValue);
279 if (module.protocol == PULSES_PXX_R9M) {
280 str += tr("Sub Type") + QString("(%1)").arg(printModuleSubType(module.protocol, module.subType));
281 str += tr("RF Output Power") + QString("(%1)").arg(printR9MPowerValue(module.subType, module.pxx.power, module.pxx.sport_out));
282 str += tr("Telemetry") + QString("(%1)").arg(printBoolean(module.pxx.sport_out, BOOLEAN_ENABLEDISABLE));
286 result = str.join(", ");
287 if (((PulsesProtocol)module.protocol == PulsesProtocol::PULSES_PXX_XJT_X16 || (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_PXX_R9M)
288 && firmware->getCapability(HasFailsafe))
289 result.append(printFailsafe(idx));
291 return result;
294 QString ModelPrinter::printTrainerMode()
296 QString result;
297 switch (model.trainerMode) {
298 case TRAINER_MASTER_JACK:
299 result = tr("Master/Jack");
300 break;
301 case TRAINER_SLAVE_JACK:
302 result = tr("Slave/Jack");
303 break;
304 case TRAINER_MASTER_SBUS_MODULE:
305 result = tr("Master/SBUS Module");
306 break;
307 case TRAINER_MASTER_CPPM_MODULE:
308 result = tr("Master/CPPM Module");
309 break;
310 case TRAINER_MASTER_SBUS_BATT_COMPARTMENT:
311 result = tr("Master/SBUS in battery compartment");
312 break;
313 default:
314 result = tr("????");
316 return result;
319 QString ModelPrinter::printHeliSwashType ()
321 switch (model.swashRingData.type) {
322 case HELI_SWASH_TYPE_90:
323 return tr("90");
324 case HELI_SWASH_TYPE_120:
325 return tr("120");
326 case HELI_SWASH_TYPE_120X:
327 return tr("120X");
328 case HELI_SWASH_TYPE_140:
329 return tr("140");
330 case HELI_SWASH_TYPE_NONE:
331 return tr("Off");
332 default:
333 return "???";
337 QString ModelPrinter::printCenterBeep()
339 QStringList strl;
340 if (model.beepANACenter & 0x01)
341 strl << tr("Rudder");
342 if (model.beepANACenter & 0x02)
343 strl << tr("Elevator");
344 if (model.beepANACenter & 0x04)
345 strl << tr("Throttle");
346 if (model.beepANACenter & 0x08)
347 strl << tr("Aileron");
348 if (IS_HORUS(firmware->getBoard())) {
349 // TODO
350 qDebug() << "ModelPrinter::printCenterBeep() TODO";
352 else if (IS_TARANIS(firmware->getBoard())) {
353 if (model.beepANACenter & 0x10)
354 strl << "S1";
355 if (model.beepANACenter & 0x20)
356 strl << "S2";
357 if (model.beepANACenter & 0x40)
358 strl << "S3";
359 if (model.beepANACenter & 0x80)
360 strl << "LS";
361 if (model.beepANACenter & 0x100)
362 strl << "RS";
364 else {
365 if (model.beepANACenter & 0x10)
366 strl << "P1";
367 if (model.beepANACenter & 0x20)
368 strl << "P2";
369 if (model.beepANACenter & 0x40)
370 strl << "P3";
372 return (strl.isEmpty() ? tr("None") : strl.join(", "));
375 QString ModelPrinter::printTimer(int idx)
377 return printTimer(model.timers[idx]);
380 QString ModelPrinter::printTimer(const TimerData & timer)
382 QStringList result;
383 if (firmware->getCapability(TimersName) && timer.name[0])
384 result += tr("Name") + QString("(%1)").arg(timer.name);
385 result += printTimeValue(timer.val, MASK_TIMEVALUE_HRSMINS | MASK_TIMEVALUE_ZEROHRS);
386 result += timer.mode.toString();
387 if (timer.countdownBeep)
388 result += tr("Countdown") + QString("(%1)").arg(printTimerCountdownBeep(timer.countdownBeep));
389 if (timer.minuteBeep)
390 result += tr("Minute call");
391 if (timer.persistent)
392 result += tr("Persistent") + QString("(%1)").arg(printTimerPersistent(timer.persistent));
393 return result.join(", ");
396 QString ModelPrinter::printTrim(int flightModeIndex, int stickIndex)
398 const FlightModeData & fm = model.flightModeData[flightModeIndex];
400 if (fm.trimMode[stickIndex] == -1) {
401 return tr("OFF");
403 else {
404 if (fm.trimRef[stickIndex] == flightModeIndex) {
405 return QString("%1").arg(fm.trim[stickIndex]);
407 else {
408 if (fm.trimMode[stickIndex] == 0) {
409 return tr("FM%1").arg(fm.trimRef[stickIndex]);
411 else {
412 if (fm.trim[stickIndex] < 0)
413 return tr("FM%1%2").arg(fm.trimRef[stickIndex]).arg(fm.trim[stickIndex]);
414 else
415 return tr("FM%1+%2").arg(fm.trimRef[stickIndex]).arg(fm.trim[stickIndex]);
421 QString ModelPrinter::printGlobalVar(int flightModeIndex, int gvarIndex)
423 const FlightModeData & fm = model.flightModeData[flightModeIndex];
425 if (fm.gvars[gvarIndex] <= 1024) {
426 return QString("%1").arg(fm.gvars[gvarIndex] * model.gvarData[gvarIndex].multiplierGet());
428 else {
429 int num = fm.gvars[gvarIndex] - 1025;
430 if (num >= flightModeIndex) num++;
431 return tr("FM%1").arg(num);
435 QString ModelPrinter::printRotaryEncoder(int flightModeIndex, int reIndex)
437 const FlightModeData & fm = model.flightModeData[flightModeIndex];
439 if (fm.rotaryEncoders[reIndex] <= 1024) {
440 return QString("%1").arg(fm.rotaryEncoders[reIndex]);
442 else {
443 int num = fm.rotaryEncoders[reIndex] - 1025;
444 if (num >= flightModeIndex) num++;
445 return tr("FM%1").arg(num);
449 QString ModelPrinter::printInputName(int idx)
451 RawSourceType srcType = (firmware->getCapability(VirtualInputs) ? SOURCE_TYPE_VIRTUAL_INPUT : SOURCE_TYPE_STICK);
452 return RawSource(srcType, idx).toString(&model, &generalSettings).toHtmlEscaped();
455 QString ModelPrinter::printInputLine(int idx)
457 return printInputLine(model.expoData[idx]);
460 QString ModelPrinter::printInputLine(const ExpoData & input)
462 QString str = "&nbsp;";
464 switch (input.mode) {
465 case (1): str += "&lt;-&nbsp;"; break;
466 case (2): str += "-&gt;&nbsp;"; break;
467 default: str += "&nbsp;&nbsp;&nbsp;"; break;
470 if (firmware->getCapability(VirtualInputs)) {
471 str += input.srcRaw.toString(&model, &generalSettings).toHtmlEscaped();
474 str += " " + tr("Weight").toHtmlEscaped() + QString("(%1)").arg(Helpers::getAdjustmentString(input.weight, &model, true).toHtmlEscaped());
475 if (input.curve.value)
476 str += " " + input.curve.toString(&model).toHtmlEscaped();
478 QString flightModesStr = printFlightModes(input.flightModes);
479 if (!flightModesStr.isEmpty())
480 str += " " + flightModesStr.toHtmlEscaped();
482 if (input.swtch.type != SWITCH_TYPE_NONE)
483 str += " " + tr("Switch").toHtmlEscaped() + QString("(%1)").arg(input.swtch.toString(getCurrentBoard(), &generalSettings)).toHtmlEscaped();
486 if (firmware->getCapability(VirtualInputs)) {
487 if (input.carryTrim>0)
488 str += " " + tr("NoTrim").toHtmlEscaped();
489 else if (input.carryTrim<0)
490 str += " " + RawSource(SOURCE_TYPE_TRIM, (-(input.carryTrim)-1)).toString(&model, &generalSettings).toHtmlEscaped();
493 if (input.offset)
494 str += " " + tr("Offset(%1)").arg(Helpers::getAdjustmentString(input.offset, &model)).toHtmlEscaped();
496 if (firmware->getCapability(HasExpoNames) && input.name[0])
497 str += QString(" [%1]").arg(input.name).toHtmlEscaped();
499 return str;
502 QString ModelPrinter::printMixerLine(const MixData & mix, bool showMultiplex, int highlightedSource)
504 QString str = "&nbsp;";
506 if (showMultiplex) {
507 switch(mix.mltpx) {
508 case (1): str += "*="; break;
509 case (2): str += ":="; break;
510 default: str += "+="; break;
513 else {
514 str += "&nbsp;&nbsp;";
516 // highlight source if needed
517 QString source = mix.srcRaw.toString(&model, &generalSettings).toHtmlEscaped();
518 if ( (mix.srcRaw.type == SOURCE_TYPE_CH) && (mix.srcRaw.index+1 == (int)highlightedSource) ) {
519 source = "<b>" + source + "</b>";
521 str += "&nbsp;" + source;
523 if (mix.mltpx == MLTPX_MUL && !showMultiplex)
524 str += " " + tr("MULT!").toHtmlEscaped();
525 else
526 str += " " + tr("Weight") + QString("(%1)").arg(Helpers::getAdjustmentString(mix.weight, &model, true)).toHtmlEscaped();
528 QString flightModesStr = printFlightModes(mix.flightModes);
529 if (!flightModesStr.isEmpty())
530 str += " " + flightModesStr.toHtmlEscaped();
532 if (mix.swtch.type != SWITCH_TYPE_NONE)
533 str += " " + tr("Switch") + QString("(%1)").arg(mix.swtch.toString(getCurrentBoard(), &generalSettings)).toHtmlEscaped();
535 if (mix.carryTrim > 0)
536 str += " " + tr("NoTrim").toHtmlEscaped();
537 else if (mix.carryTrim < 0)
538 str += " " + RawSource(SOURCE_TYPE_TRIM, (-(mix.carryTrim)-1)).toString(&model, &generalSettings);
540 if (firmware->getCapability(HasNoExpo) && mix.noExpo)
541 str += " " + tr("No DR/Expo").toHtmlEscaped();
542 if (mix.sOffset)
543 str += " " + tr("Offset") + QString("(%1)").arg(Helpers::getAdjustmentString(mix.sOffset, &model)).toHtmlEscaped();
544 if (mix.curve.value)
545 str += " " + mix.curve.toString(&model).toHtmlEscaped();
546 int scale = firmware->getCapability(SlowScale);
547 if (scale == 0)
548 scale = 1;
549 if (mix.delayDown || mix.delayUp)
550 str += " " + tr("Delay") + QString("(u%1:d%2)").arg((double)mix.delayUp/scale).arg((double)mix.delayDown/scale).toHtmlEscaped();
551 if (mix.speedDown || mix.speedUp)
552 str += " " + tr("Slow") + QString("(u%1:d%2)").arg((double)mix.speedUp/scale).arg((double)mix.speedDown/scale).toHtmlEscaped();
553 if (mix.mixWarn)
554 str += " " + tr("Warn") + QString("(%1)").arg(mix.mixWarn).toHtmlEscaped();
555 if (firmware->getCapability(HasMixerNames) && mix.name[0])
556 str += QString(" [%1]").arg(mix.name).toHtmlEscaped();
557 return str;
560 QString ModelPrinter::printFlightModeSwitch(const RawSwitch & swtch)
562 return swtch.toString(getCurrentBoard(), &generalSettings);
565 QString ModelPrinter::printFlightModeName(int index)
567 return model.flightModeData[index].nameToString(index);
570 QString ModelPrinter::printFlightModes(unsigned int flightModes)
572 int numFlightModes = firmware->getCapability(FlightModes);
573 if (numFlightModes && flightModes) {
574 if (flightModes == (unsigned int)(1<<numFlightModes) - 1) {
575 return tr("Disabled in all flight modes");
577 else {
578 QStringList list;
579 for (int i=0; i<numFlightModes; i++) {
580 if (!(flightModes & (1<<i))) {
581 list << printFlightModeName(i);
584 return (list.size() > 1 ? tr("Flight modes") : tr("Flight mode")) + QString("(%1)").arg(list.join(", "));
587 else
588 return "";
591 QString ModelPrinter::printLogicalSwitchLine(int idx)
593 QString result = "";
594 const LogicalSwitchData & ls = model.logicalSw[idx];
595 const QString sw1Name = RawSwitch(ls.val1).toString(getCurrentBoard(), &generalSettings);
596 const QString sw2Name = RawSwitch(ls.val2).toString(getCurrentBoard(), &generalSettings);
598 if (ls.isEmpty())
599 return result;
601 if (ls.andsw!=0) {
602 result +="( ";
604 switch (ls.getFunctionFamily()) {
605 case LS_FAMILY_EDGE:
606 result += tr("Edge") + QString("(%1, [%2:%3])").arg(sw1Name).arg(ValToTim(ls.val2)).arg(ls.val3<0 ? tr("instant") : QString("%1").arg(ValToTim(ls.val2+ls.val3)));
607 break;
608 case LS_FAMILY_STICKY:
609 result += tr("Sticky") + QString("(%1, %2)").arg(sw1Name).arg(sw2Name);
610 break;
611 case LS_FAMILY_TIMER:
612 result += tr("Timer") + QString("(%1, %2)").arg(ValToTim(ls.val1)).arg(ValToTim(ls.val2));
613 break;
614 case LS_FAMILY_VOFS: {
615 RawSource source = RawSource(ls.val1);
616 RawSourceRange range = source.getRange(&model, generalSettings);
617 QString res;
618 if (ls.val1)
619 res += source.toString(&model, &generalSettings);
620 else
621 res += "0";
622 res.remove(" ");
623 if (ls.func == LS_FN_APOS || ls.func == LS_FN_ANEG)
624 res = "|" + res + "|";
625 else if (ls.func == LS_FN_DAPOS)
626 res = "|d(" + res + ")|";
627 else if (ls.func == LS_FN_DPOS)
628 result = "d(" + res + ")";
629 result += res;
630 if (ls.func == LS_FN_VEQUAL)
631 result += " = ";
632 else if (ls.func == LS_FN_APOS || ls.func == LS_FN_VPOS || ls.func == LS_FN_DPOS || ls.func == LS_FN_DAPOS)
633 result += " &gt; ";
634 else if (ls.func == LS_FN_ANEG || ls.func == LS_FN_VNEG)
635 result += " &lt; ";
636 else if (ls.func == LS_FN_VALMOSTEQUAL)
637 result += " ~ ";
638 else
639 result += tr(" missing");
640 result += QString::number(range.step * (ls.val2 /*TODO+ source.getRawOffset(model)*/) + range.offset);
641 break;
643 case LS_FAMILY_VBOOL:
644 result += sw1Name;
645 switch (ls.func) {
646 case LS_FN_AND:
647 result += " AND ";
648 break;
649 case LS_FN_OR:
650 result += " OR ";
651 break;
652 case LS_FN_XOR:
653 result += " XOR ";
654 break;
655 default:
656 result += " bar ";
657 break;
659 result += sw2Name;
660 break;
662 case LS_FAMILY_VCOMP:
663 if (ls.val1)
664 result += RawSource(ls.val1).toString(&model, &generalSettings);
665 else
666 result += "0";
667 switch (ls.func) {
668 case LS_FN_EQUAL:
669 case LS_FN_VEQUAL:
670 result += " = ";
671 break;
672 case LS_FN_NEQUAL:
673 result += " != ";
674 break;
675 case LS_FN_GREATER:
676 result += " &gt; ";
677 break;
678 case LS_FN_LESS:
679 result += " &lt; ";
680 break;
681 case LS_FN_EGREATER:
682 result += " &gt;= ";
683 break;
684 case LS_FN_ELESS:
685 result += " &lt;= ";
686 break;
687 default:
688 result += " foo ";
689 break;
691 if (ls.val2)
692 result += RawSource(ls.val2).toString(&model, &generalSettings);
693 else
694 result += "0";
695 break;
698 if (ls.andsw != 0) {
699 result +=" ) AND ";
700 result += RawSwitch(ls.andsw).toString(getCurrentBoard(), &generalSettings);
703 if (firmware->getCapability(LogicalSwitchesExt)) {
704 if (ls.duration)
705 result += " " + tr("Duration") + QString("(%1s)").arg(ls.duration/10.0);
706 if (ls.delay)
707 result += " " + tr("Delay") + QString("(%1s)").arg(ls.delay/10.0);
710 return result;
713 QString ModelPrinter::printCustomFunctionLine(int idx)
715 QString result;
716 const CustomFunctionData & cf = model.customFn[idx];
717 if (cf.swtch.type == SWITCH_TYPE_NONE)
718 return result;
720 result += cf.swtch.toString(getCurrentBoard(), &generalSettings) + " - ";
721 result += cf.funcToString(&model) + " (";
722 result += cf.paramToString(&model) + ")";
723 if (!cf.repeatToString().isEmpty())
724 result += " " + cf.repeatToString();
725 if (!cf.enabledToString().isEmpty())
726 result += " " + cf.enabledToString();
727 return result;
730 QString ModelPrinter::printCurveName(int idx)
732 return model.curves[idx].nameToString(idx).toHtmlEscaped();
735 QString ModelPrinter::printCurve(int idx)
737 QString result;
738 const CurveData & curve = model.curves[idx];
739 result += (curve.type == CurveData::CURVE_TYPE_CUSTOM) ? tr("Custom") : tr("Standard");
740 result += ", [";
741 if (curve.type == CurveData::CURVE_TYPE_CUSTOM) {
742 for (int j=0; j<curve.count; j++) {
743 if (j != 0)
744 result += ", ";
745 result += QString("(%1, %2)").arg(curve.points[j].x).arg(curve.points[j].y);
748 else {
749 for (int j=0; j<curve.count; j++) {
750 if (j != 0)
751 result += ", ";
752 result += QString("%1").arg(curve.points[j].y);
755 result += "]";
756 return result;
759 CurveImage::CurveImage():
760 size(200),
761 image(size+1, size+1, QImage::Format_RGB32),
762 painter(&image)
764 painter.setBrush(QBrush("#FFFFFF"));
765 painter.setPen(QColor(0, 0, 0));
766 painter.drawRect(0, 0, size, size);
768 painter.setPen(QColor(0, 0, 0));
769 painter.drawLine(0, size/2, size, size/2);
770 painter.drawLine(size/2, 0, size/2, size);
771 for (int i=0; i<21; i++) {
772 painter.drawLine(size/2-5, (size*i)/(20), size/2+5, (size*i)/(20));
773 painter.drawLine((size*i)/(20), size/2-5, (size*i)/(20), size/2+5);
777 void CurveImage::drawCurve(const CurveData & curve, QColor color)
779 painter.setPen(QPen(color, 2, Qt::SolidLine));
780 for (int j=1; j<curve.count; j++) {
781 if (curve.type == CurveData::CURVE_TYPE_CUSTOM)
782 painter.drawLine(size/2+(size*curve.points[j-1].x)/200, size/2-(size*curve.points[j-1].y)/200, size/2+(size*curve.points[j].x)/200, size/2-(size*curve.points[j].y)/200);
783 else
784 painter.drawLine(size*(j-1)/(curve.count-1), size/2-(size*curve.points[j-1].y)/200, size*(j)/(curve.count-1), size/2-(size*curve.points[j].y)/200);
788 QString ModelPrinter::createCurveImage(int idx, QTextDocument * document)
790 CurveImage image;
791 image.drawCurve(model.curves[idx], colors[idx]);
792 QString filename = QString("mydata://curve-%1-%2.png").arg((uint64_t)this).arg(idx);
793 if (document)
794 document->addResource(QTextDocument::ImageResource, QUrl(filename), image.get());
795 // qDebug() << "ModelPrinter::createCurveImage()" << idx << filename;
796 return filename;
799 QString ModelPrinter::printGlobalVarUnit(int idx)
801 return model.gvarData[idx].unitToString().toHtmlEscaped();
804 QString ModelPrinter::printGlobalVarPrec(int idx)
806 return model.gvarData[idx].precToString().toHtmlEscaped();
809 QString ModelPrinter::printGlobalVarMin(int idx)
811 return QString::number(model.gvarData[idx].getMinPrec());
814 QString ModelPrinter::printGlobalVarMax(int idx)
816 return QString::number(model.gvarData[idx].getMaxPrec());
819 QString ModelPrinter::printGlobalVarPopup(int idx)
821 return printBoolean(model.gvarData[idx].popup, BOOLEAN_YN);
824 QString ModelPrinter::printOutputValueGVar(int val)
826 QString result = "";
827 if (abs(val) > 10000) {
828 if (val < 0)
829 result = "-";
830 result.append(RawSource(SOURCE_TYPE_GVAR, abs(val)-10001).toString(&model));
832 else {
833 if (val >= 0)
834 result = "+";
835 result.append(QString::number((qreal)val/10, 'f', 1) + "%");
837 return result;
840 QString ModelPrinter::printOutputOffset(int idx)
842 return printOutputValueGVar(model.limitData[idx].offset);
845 QString ModelPrinter::printOutputMin(int idx)
847 return printOutputValueGVar(model.limitData[idx].min);
850 QString ModelPrinter::printOutputMax(int idx)
852 return printOutputValueGVar(model.limitData[idx].max);
855 QString ModelPrinter::printOutputRevert(int idx)
857 return model.limitData[idx].revertToString();
860 QString ModelPrinter::printOutputPpmCenter(int idx)
862 return QString::number(model.limitData[idx].ppmCenter + 1500);
865 QString ModelPrinter::printOutputCurve(int idx)
867 return CurveReference(CurveReference::CURVE_REF_CUSTOM, model.limitData[idx].curve.value).toString(&model, false);
870 QString ModelPrinter::printOutputSymetrical(int idx)
872 return printBoolean(model.limitData[idx].symetrical, BOOLEAN_YN);
875 QString ModelPrinter::printSettingsOther()
877 QStringList str;
878 if (model.extendedLimits)
879 str += tr("Extended Limits");
880 if (firmware->getCapability(HasDisplayText) && model.displayChecklist)
881 str += tr("Display Checklist");
882 if (firmware->getCapability(GlobalFunctions) && !model.noGlobalFunctions)
883 str += tr("Global Functions");
884 return str.join(", ");
887 QString ModelPrinter::printSwitchWarnings()
889 QStringList str;
890 Boards board = firmware->getBoard();
891 uint64_t switchStates = model.switchWarningStates;
892 uint64_t value;
894 for (int idx=0; idx<board.getCapability(Board::Switches); idx++) {
895 Board::SwitchInfo switchInfo = Boards::getSwitchInfo(board.getBoardType(), idx);
896 switchInfo.config = Board::SwitchType(generalSettings.switchConfig[idx]);
897 if (switchInfo.config == Board::SWITCH_NOT_AVAILABLE || switchInfo.config == Board::SWITCH_TOGGLE) {
898 continue;
900 if (!(model.switchWarningEnable & (1 << idx))) {
901 if (IS_HORUS_OR_TARANIS(board.getBoardType())) {
902 value = (switchStates >> (2*idx)) & 0x03;
904 else {
905 value = (idx==0 ? switchStates & 0x3 : switchStates & 0x1);
906 switchStates >>= (idx==0 ? 2 : 1);
908 str += RawSwitch(SWITCH_TYPE_SWITCH, 1+idx*3+value).toString(board.getBoardType(), &generalSettings, &model);
911 return (str.isEmpty() ? tr("None") : str.join(", ")) ;
914 QString ModelPrinter::printPotWarnings()
916 QStringList str;
917 int genAryIdx = 0;
918 Boards board = firmware->getBoard();
919 str += (model.potsWarningMode ? tr("Mode") + QString("(%1)").arg(printPotsWarningMode()) : tr("None"));
920 if (model.potsWarningMode) {
921 for (int i=0; i<board.getCapability(Board::Pots)+board.getCapability(Board::Sliders); i++) {
922 RawSource src(SOURCE_TYPE_STICK, CPN_MAX_STICKS + i);
923 if ((src.isPot(&genAryIdx) && generalSettings.isPotAvailable(genAryIdx)) || (src.isSlider(&genAryIdx) && generalSettings.isSliderAvailable(genAryIdx))) {
924 if (!model.potsWarningEnabled[i])
925 str += src.toString(&model, &generalSettings);
929 return str.join(", ");
932 QString ModelPrinter::printPotsWarningMode()
934 switch (model.potsWarningMode) {
935 case 0:
936 return tr("OFF");
937 case 1:
938 return tr("Manual");
939 case 2:
940 return tr("Auto");
941 default:
942 return tr("????");
946 QString ModelPrinter::printFailsafe(int idx)
948 QStringList strl;
949 ModuleData module = model.moduleData[idx];
950 strl += "<br>" + tr("Failsafe Mode") + QString("(%1)").arg(printFailsafeMode(module.failsafeMode));
951 if (module.failsafeMode == FAILSAFE_CUSTOM) {
952 for (int i=0; i<module.channelsCount; i++) {
953 strl += QString("%1(%2)").arg(printChannelName(module.channelsStart + i).trimmed()).arg(printFailsafeValue(module.failsafeChannels[i]));
956 return strl.join(", ");
959 QString ModelPrinter::printFailsafeValue(int val)
961 switch (val) {
962 case 2000:
963 return tr("Hold");
964 case 2001:
965 return tr("No Pulse");
966 default:
967 return QString("%1%").arg(QString::number(divRoundClosest(val * 1000, 1024) / 10.0));
971 QString ModelPrinter::printFailsafeMode(unsigned int fsmode)
973 switch (fsmode) {
974 case FAILSAFE_NOT_SET:
975 return tr("Not set");
976 case FAILSAFE_HOLD:
977 return tr("Hold");
978 case FAILSAFE_CUSTOM:
979 return tr("Custom");
980 case FAILSAFE_NOPULSES:
981 return tr("No pulses");
982 case FAILSAFE_RECEIVER:
983 return tr("Receiver");
984 default:
985 return tr("???");
989 QString ModelPrinter::printTimerCountdownBeep(unsigned int countdownBeep)
991 switch (countdownBeep) {
992 case TimerData::COUNTDOWN_SILENT:
993 return tr("Silent");
994 case TimerData::COUNTDOWN_BEEPS:
995 return tr("Beeps");
996 case TimerData::COUNTDOWN_VOICE:
997 return tr("Voice");
998 case TimerData::COUNTDOWN_HAPTIC:
999 return tr("Haptic");
1000 default:
1001 return tr("???");
1005 QString ModelPrinter::printTimerPersistent(unsigned int persistent)
1007 switch (persistent) {
1008 case 0:
1009 return tr("OFF");
1010 case 1:
1011 return tr("Flight");
1012 case 2:
1013 return tr("Manual reset");
1014 default:
1015 return tr("???");
1019 QString ModelPrinter::printSettingsTrim()
1021 QStringList str;
1022 str += tr("Step") + QString("(%1)").arg(printTrimIncrementMode());
1023 if (IS_ARM(firmware->getBoard()) && model.trimsDisplay)
1024 str += tr("Display") + QString("(%1)").arg(printTrimsDisplayMode());
1025 if (model.extendedTrims)
1026 str += tr("Extended");
1027 return str.join(", ");
1030 QString ModelPrinter::printThrottleSource(int idx)
1032 Boards board = firmware->getBoard();
1033 int chnstart = board.getCapability(Board::Pots)+board.getCapability(Board::Sliders);
1034 if (idx == 0)
1035 return "THR";
1036 else if (idx < (chnstart+1))
1037 return firmware->getAnalogInputName(idx+board.getCapability(Board::Sticks)-1);
1038 else
1039 return RawSource(SOURCE_TYPE_CH, idx-chnstart-1).toString(&model, &generalSettings);
1042 QString ModelPrinter::printTrimsDisplayMode()
1044 switch (model.trimsDisplay) {
1045 case 0:
1046 return tr("Never");
1047 case 1:
1048 return tr("On Change");
1049 case 2:
1050 return tr("Always");
1051 default:
1052 return tr("Unknown");
1056 QString ModelPrinter::printModuleType(int idx)
1058 if (idx < 0)
1059 return tr("Trainer Port");
1060 else if (firmware->getCapability(NumModules) > 1)
1061 if (IS_HORUS_OR_TARANIS(firmware->getBoard()))
1062 if (idx == 0)
1063 return tr("Internal Radio System");
1064 else
1065 return tr("External Radio Module");
1066 else if (idx == 0)
1067 return tr("Radio System");
1068 else
1069 return tr("Extra Radio System");
1070 else
1071 return tr("Radio System");
1074 QString ModelPrinter::printPxxPower(int power)
1076 static const char *strings[] = {
1077 "10mW", "100mW", "500mW", "3W"
1079 return CHECK_IN_ARRAY(strings, power);
1082 QString ModelPrinter::printThrottle()
1084 QStringList result;
1085 result += tr("Source") + QString("(%1)").arg(printThrottleSource(model.thrTraceSrc));
1086 if (model.thrTrim)
1087 result += tr("Trim idle only");
1088 if (!model.disableThrottleWarning)
1089 result += tr("Warning");
1090 if (model.throttleReversed)
1091 result += tr("Reversed");
1092 return result.join(", ");
1095 QString ModelPrinter::printPPMFrameLength(int ppmFL)
1097 double result = (((double)ppmFL * 5) + 225) / 10;
1098 return QString::number(result);
1101 QString ModelPrinter::printTimerName(int idx)
1103 QString result;
1104 result = tr("Tmr") + QString("%1").arg(idx+1);
1105 if (firmware->getCapability(TimersName) && model.timers[idx].name[0])
1106 result.append(":" + QString(model.timers[idx].name));
1108 return result;
1111 QString ModelPrinter::printTimerTimeValue(unsigned int val)
1113 return printTimeValue(val, MASK_TIMEVALUE_HRSMINS | MASK_TIMEVALUE_ZEROHRS);
1116 QString ModelPrinter::printTimerMinuteBeep(bool mb)
1118 return printBoolean(mb, BOOLEAN_YESNO);