[companion] Adjust GVAR not possible in global functions (fix #5425)
[opentx.git] / companion / src / multimodelprinter.cpp
blobdd60a33b774fb9b2ccd7a0a2f86fa02ce7a64b1f
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 "helpers_html.h"
23 #include "multimodelprinter.h"
24 #include <algorithm>
26 MultiModelPrinter::MultiColumns::MultiColumns(int count):
27 count(count),
28 compareColumns(NULL)
30 columns = new QString[count];
33 MultiModelPrinter::MultiColumns::~MultiColumns()
35 delete[] columns;
38 void MultiModelPrinter::MultiColumns::append(const QString & str)
40 for (int i=0; i<count; i++) {
41 append(i, str);
45 void MultiModelPrinter::MultiColumns::appendTitle(const QString & name)
47 append("<b>" + name + "</b>&nbsp;");
50 void MultiModelPrinter::MultiColumns::append(int idx, const QString & str)
52 if (compareColumns)
53 compareColumns[idx].append(str);
54 else
55 columns[idx].append(str);
58 void MultiModelPrinter::MultiColumns::beginCompare()
60 compareColumns = new QString[count];
63 void MultiModelPrinter::MultiColumns::endCompare(const QString & color)
65 for (int i=0; i<count; i++) {
66 QString cellColor = color;
67 if (i==0 && count>1 && compareColumns[0]!=compareColumns[1])
68 cellColor = "green";
69 else if (i>0 && compareColumns[i]!=compareColumns[0])
70 cellColor = "red";
71 columns[i].append(QString("<font color='%1'>%2</font>").arg(cellColor).arg(compareColumns[i]));
73 delete[] compareColumns;
74 compareColumns = NULL;
77 template <class T>
78 void MultiModelPrinter::MultiColumns::append(int idx, T val)
80 append(idx, QString("%1").arg(val));
83 QString MultiModelPrinter::MultiColumns::print()
85 QString result = "<tr>";
86 for (int i=0; i<count; i++) {
87 result.append(QString("<td width='%1%'>%2</td>").arg(100.0/count).arg(columns[i]));
89 result.append("</tr>");
90 return result;
93 bool MultiModelPrinter::MultiColumns::isEmpty()
95 for (int i=0; i<count; i++) {
96 if (!columns[i].isEmpty())
97 return false;
99 return true;
102 #define COMPARE(what) \
103 columns.beginCompare(); \
104 for (int cc=0; cc < modelPrinterMap.size(); cc++) { \
105 ModelPrinter * modelPrinter = modelPrinterMap.value(cc).second; \
106 const ModelData * model = modelPrinterMap.value(cc).first; \
107 (void)(model); (void)(modelPrinter); \
108 columns.append(cc, (what)); \
110 columns.endCompare();
112 QString MultiModelPrinter::printTitle(const QString & label)
114 return QString("<tr><td colspan='%1'><h2>").arg(modelPrinterMap.count()) + label + "</h2></td></tr>";
117 MultiModelPrinter::MultiModelPrinter(Firmware * firmware):
118 firmware(firmware)
122 MultiModelPrinter::~MultiModelPrinter()
124 clearModels();
127 void MultiModelPrinter::setModel(int idx, const ModelData * model, const GeneralSettings * generalSettings)
129 if (modelPrinterMap.contains(idx) && modelPrinterMap.value(idx).second) {
130 // free existing model printer
131 delete modelPrinterMap.value(idx).second;
132 modelPrinterMap[idx].second = NULL;
135 QPair<const ModelData *, ModelPrinter *> pair(model, new ModelPrinter(firmware, *generalSettings, *model));
136 modelPrinterMap.insert(idx, pair); // QMap.insert will replace any existing key
139 void MultiModelPrinter::setModel(int idx, const ModelData * model)
141 setModel(idx, model, &defaultSettings);
144 void MultiModelPrinter::clearModels()
146 for(int i=0; i < modelPrinterMap.size(); i++) {
147 if (modelPrinterMap.value(i).second)
148 delete modelPrinterMap.value(i).second;
150 modelPrinterMap.clear();
153 QString MultiModelPrinter::print(QTextDocument * document)
155 if (document) document->clear();
157 QString str = "<table border='1' cellspacing='0' cellpadding='3' width='100%' style='font-family: monospace;'>";
158 str += printSetup();
159 if (firmware->getCapability(Heli))
160 str += printHeliSetup();
161 if (firmware->getCapability(FlightModes))
162 str += printFlightModes();
163 str += printInputs();
164 str += printMixers();
165 str += printLimits();
166 str += printCurves(document);
167 if (firmware->getCapability(Gvars) && !firmware->getCapability(GvarsFlightModes))
168 str += printGvars();
169 str += printLogicalSwitches();
170 str += printCustomFunctions();
171 str += printTelemetry();
172 str += "</table>";
173 return str;
176 QString MultiModelPrinter::printSetup()
178 QString str = printTitle(tr("General Model Settings"));
180 MultiColumns columns(modelPrinterMap.size());
181 columns.appendTitle(tr("Name:"));
182 COMPARE(model->name);
183 columns.append("<br/>");
184 columns.appendTitle(tr("EEprom Size:"));
185 COMPARE(modelPrinter->printEEpromSize());
186 columns.append("<br/>");
187 for (int i=0; i<firmware->getCapability(Timers); i++) {
188 columns.appendTitle(tr("Timer%1:").arg(i+1));
189 COMPARE(modelPrinter->printTimer(i));
190 columns.append("<br/>");
192 for (int i=0; i<firmware->getCapability(NumModules); i++) {
193 columns.appendTitle(firmware->getCapability(NumModules) > 1 ? tr("Module%1:").arg(i+1) : tr("Module:"));
194 COMPARE(modelPrinter->printModule(i));
195 columns.append("<br/>");
197 if (IS_HORUS_OR_TARANIS(firmware->getBoard())) {
198 columns.appendTitle(tr("Trainer port:"));
199 COMPARE(modelPrinter->printTrainerMode());
200 columns.append("<br/>");
202 columns.appendTitle(tr("Throttle Trim:"));
203 COMPARE(modelPrinter->printThrottleTrimMode());
204 columns.append("<br/>");
205 columns.appendTitle(tr("Trim Increment:"));
206 COMPARE(modelPrinter->printTrimIncrementMode());
207 columns.append("<br/>");
208 columns.appendTitle(tr("Center Beep:"));
209 COMPARE(modelPrinter->printCenterBeep());
210 str.append(columns.print());
211 return str;
215 QString MultiModelPrinter::printHeliSetup()
217 bool heliEnabled = false;
218 for (int k=0; k < modelPrinterMap.size(); k++) {
219 heliEnabled = heliEnabled || modelPrinterMap.value(k).first->swashRingData.type != HELI_SWASH_TYPE_NONE;
222 if (!heliEnabled)
223 return "";
225 QString str = printTitle(tr("Helicopter Setup"));
226 MultiColumns columns(modelPrinterMap.size());
227 columns.appendTitle (tr("Swash Type:"));
228 COMPARE(modelPrinter->printHeliSwashType());
229 columns.append ("<br/>");
231 columns.appendTitle (tr("Swash Ring:"));
232 COMPARE(model->swashRingData.value);
234 columns.append ("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
235 columns.append("<tr>");
236 columns.append("<td></td><td><b>" + tr("Input") + "</b></td><td><b>" + tr("Weight") + "</b></td>");
237 columns.append("</tr>");
239 columns.append("<tr><td><b>" + tr("Long. cyc") + "</b></td><td>");
240 COMPARE(model->swashRingData.elevatorSource.toString(model));
241 columns.append("</td><td>");
242 COMPARE(model->swashRingData.elevatorWeight)
243 columns.append("</td></tr>");
245 columns.append("<tr><td><b>" + tr("Lateral cyc") + "</b></td><td>");
246 COMPARE(model->swashRingData.aileronSource.toString(model));
247 columns.append("</td><td>");
248 COMPARE(model->swashRingData.aileronWeight)
249 columns.append("</td></tr>");
252 columns.append("<tr><td><b>" + tr("Collective") + "</b></td><td>");
253 COMPARE(model->swashRingData.collectiveSource.toString(model));
254 columns.append("</td><td>");
255 COMPARE(model->swashRingData.collectiveWeight)
256 columns.append("</td></tr>");
257 columns.append("</table>");
261 str.append(columns.print());
262 return str;
265 QString MultiModelPrinter::printFlightModes()
267 QString str = printTitle(tr("Flight modes"));
269 // Trims
271 MultiColumns columns(modelPrinterMap.size());
272 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
273 columns.append("<tr>");
274 columns.append("<td><b>" + tr("Flight mode") + "</b></td>");
275 columns.append("<td><b>" + tr("Switch") + "</b></td>");
276 columns.append("<td><b>" + tr("Fade IN") + "</b></td>");
277 columns.append("<td><b>" + tr("Fade OUT") + "</b></td>");
278 for (int i=0; i < getBoardCapability(getCurrentBoard(), Board::NumTrims); i++) {
279 columns.append("<td><b>" + RawSource(SOURCE_TYPE_TRIM, i).toString() + "</b></td>");
281 columns.append("</tr>");
283 for (int i=0; i<firmware->getCapability(FlightModes); i++) {
284 columns.append("<tr><td><b>" + tr("FM%1").arg(i) + "</b>&nbsp;");
285 COMPARE(model->flightModeData[i].name);
286 columns.append("</td><td>");
287 COMPARE(modelPrinter->printFlightModeSwitch(model->flightModeData[i].swtch));
288 columns.append("</td><td>");
289 COMPARE(model->flightModeData[i].fadeIn);
290 columns.append("</td><td>");
291 COMPARE(model->flightModeData[i].fadeOut);
292 columns.append("</td>");
293 for (int k=0; k < getBoardCapability(getCurrentBoard(), Board::NumTrims); k++) {
294 columns.append("<td>");
295 COMPARE(modelPrinter->printTrim(i, k));
296 columns.append("</td>");
298 columns.append("</tr>");
301 columns.append("</table>");
302 str.append(columns.print());
305 // GVars and Rotary Encoders
306 int gvars = firmware->getCapability(Gvars);
307 if ((gvars && firmware->getCapability(GvarsFlightModes)) || firmware->getCapability(RotaryEncoders)) {
308 MultiColumns columns(modelPrinterMap.size());
309 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
310 columns.append("<tr><td><b>" + tr("Global variables") + "</b></td>");
311 if (firmware->getCapability(GvarsFlightModes)) {
312 for (int i=0; i<gvars; i++) {
313 columns.append("<td><b>" + tr("GV%1").arg(i+1) + "</b></td>");
316 for (int i=0; i<firmware->getCapability(RotaryEncoders); i++) {
317 columns.append("<td><b>" + tr("RE%1").arg(i+1) + "</b></td>");
319 columns.append("</tr>");
321 if (firmware->getCapability(GvarsFlightModes)) {
322 columns.append("<tr><td><b>Name</b></td>");
323 for (int i=0; i<gvars; i++) {
324 columns.append("<td>");
325 COMPARE(model->gvarData[i].name);
326 columns.append("</td>");
328 columns.append("</tr>");
329 columns.append("<tr><td><b>Unit</b></td>");
330 for (int i=0; i<gvars; i++) {
331 columns.append("<td>");
332 COMPARE(modelPrinter->printGlobalVarUnit(i));
333 columns.append("</td>");
335 columns.append("</tr>");
336 columns.append("<tr><td><b>Prec</b></td>");
337 for (int i=0; i<gvars; i++) {
338 columns.append("<td>");
339 COMPARE(modelPrinter->printGlobalVarPrec(i));
340 columns.append("</td>");
342 columns.append("</tr>");
343 columns.append("<tr><td><b>Min</b></td>");
344 for (int i=0; i<gvars; i++) {
345 columns.append("<td>");
346 COMPARE(modelPrinter->printGlobalVarMin(i));
347 columns.append("</td>");
349 columns.append("</tr>");
350 columns.append("<tr><td><b>Max</b></td>");
351 for (int i=0; i<gvars; i++) {
352 columns.append("<td>");
353 COMPARE(modelPrinter->printGlobalVarMax(i));
354 columns.append("</td>");
356 columns.append("</tr>");
357 columns.append("<tr><td><b>Popup</b></td>");
358 for (int i=0; i<gvars; i++) {
359 columns.append("<td>");
360 COMPARE(modelPrinter->printGlobalVarPopup(i));
361 columns.append("</td>");
363 columns.append("</tr>");
366 columns.append("<tr><td><b>" + tr("Flight mode") + "</b></td></tr>");
368 for (int i=0; i<firmware->getCapability(FlightModes); i++) {
369 columns.append("<tr><td><b>" + tr("FM%1").arg(i) + "</b>&nbsp;");
370 COMPARE(model->flightModeData[i].name);
371 columns.append("</td>");
372 if (firmware->getCapability(GvarsFlightModes)) {
373 for (int k=0; k<gvars; k++) {
374 columns.append("<td>");
375 COMPARE(modelPrinter->printGlobalVar(i, k));
376 columns.append("</td>");
379 for (int k=0; k<firmware->getCapability(RotaryEncoders); k++) {
380 columns.append("<td>");
381 COMPARE(modelPrinter->printRotaryEncoder(i, k));
382 columns.append("</td>");
384 columns.append("</tr>");
386 columns.append("</table>");
387 str.append(columns.print());
390 return str;
393 QString MultiModelPrinter::printLimits()
395 QString str = printTitle(tr("Limits"));
396 MultiColumns columns(modelPrinterMap.size());
397 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>" \
398 "<tr>" \
399 " <td><b>" + tr("Channel") + "</b></td>" \
400 " <td><b>" + tr("Offset") + "</b></td>" \
401 " <td><b>" + tr("Min") + "</b></td>" \
402 " <td><b>" + tr("Max") + "</b></td>" \
403 " <td><b>" + tr("Invert") + "</b></td>" \
404 "</tr>");
405 for (int i=0; i<firmware->getCapability(Outputs); i++) {
406 int count = 0;
407 for (int k=0; k < modelPrinterMap.size(); k++)
408 count = std::max(count, modelPrinterMap.value(k).first->mixes(i).size());
409 if (!count)
410 continue;
411 columns.append("<tr><td><b>");
412 COMPARE(modelPrinter->printChannelName(i));
413 columns.append("</td><td>");
414 COMPARE(model->limitData[i].offsetToString());
415 columns.append("</td><td>");
416 COMPARE(model->limitData[i].minToString());
417 columns.append("</td><td>");
418 COMPARE(model->limitData[i].maxToString());
419 columns.append("</td><td>");
420 COMPARE(model->limitData[i].revertToString());
421 columns.append("</td></tr>");
424 columns.append("</table>");
425 str.append(columns.print());
426 return str;
429 QString MultiModelPrinter::printGvars()
431 QString str = printTitle(tr("Global Variables"));
432 int gvars = firmware->getCapability(Gvars);
433 MultiColumns columns(modelPrinterMap.size());
434 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'><tr>");
435 for (int i=0; i<gvars; i++) {
436 columns.append(QString("<td><b>") + tr("GV%1").arg(i+1) + "</b></td>");
438 columns.append("</tr><tr>");
439 for (int i=0; i<gvars; i++) {
440 columns.append("<td>");
441 COMPARE(model->flightModeData[0].gvars[i]);
442 columns.append("</td>");
444 columns.append("</tr>");
445 str.append(columns.print());
446 return str;
449 QString MultiModelPrinter::printInputs()
451 QString str = printTitle(tr("Inputs"));
452 MultiColumns columns(modelPrinterMap.size());
453 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
454 for (int i=0; i<std::max(4, firmware->getCapability(VirtualInputs)); i++) {
455 int count = 0;
456 for (int k=0; k < modelPrinterMap.size(); k++) {
457 count = std::max(count, modelPrinterMap.value(k).first->expos(i).size());
459 if (count > 0) {
460 columns.append("<tr><td width='20%'><b>");
461 COMPARE(modelPrinter->printInputName(i));
462 columns.append("</b></td><td>");
463 for (int j=0; j<count; j++) {
464 if (j > 0)
465 columns.append("<br/>");
466 COMPARE(j<model->expos(i).size() ? modelPrinter->printInputLine(*model->expos(i)[j]) : "");
468 columns.append("</td></tr>");
471 str.append(columns.print());
472 return str;
475 QString MultiModelPrinter::printMixers()
477 QString str = printTitle(tr("Mixers"));
478 MultiColumns columns(modelPrinterMap.size());
479 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
480 for (int i=0; i<firmware->getCapability(Outputs); i++) {
481 int count = 0;
482 for (int k=0; k < modelPrinterMap.size(); k++) {
483 count = std::max(count, modelPrinterMap.value(k).first->mixes(i).size());
485 if (count > 0) {
486 columns.append("<tr><td width='20%'><b>");
487 COMPARE(modelPrinter->printChannelName(i));
488 columns.append("</b></td><td>");
489 for (int j=0; j<count; j++) {
490 if (j > 0)
491 columns.append("<br/>");
492 COMPARE((j < model->mixes(i).size()) ? modelPrinter->printMixerLine(*model->mixes(i)[j], (j>0)) : "&nbsp;");
494 columns.append("</td></tr>");
497 str.append(columns.print());
498 return str;
501 QString MultiModelPrinter::printCurves(QTextDocument * document)
503 QString str;
504 MultiColumns columns(modelPrinterMap.size());
505 int count = 0;
506 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
507 for (int i=0; i<firmware->getCapability(NumCurves); i++) {
508 bool curveEmpty = true;
509 for (int k=0; k < modelPrinterMap.size(); k++) {
510 if (!modelPrinterMap.value(k).first->curves[i].isEmpty()) {
511 curveEmpty = false;
512 break;
515 if (!curveEmpty) {
516 count++;
517 columns.append("<tr><td width='20%'><b>");
518 COMPARE(modelPrinter->printCurveName(i));
519 columns.append("</b></td><td>");
520 COMPARE(modelPrinter->printCurve(i));
521 for (int k=0; k < modelPrinterMap.size(); k++)
522 columns.append(k, QString("<br/><img src='%1' border='0' />").arg(modelPrinterMap.value(k).second->createCurveImage(i, document)));
523 columns.append("</td></tr>");
526 columns.append("</table><br/>");
527 if (count > 0) {
528 str.append(printTitle(tr("Curves")));
529 str.append(columns.print());
531 return str;
534 QString MultiModelPrinter::printLogicalSwitches()
536 QString str;
537 MultiColumns columns(modelPrinterMap.size());
538 int count = 0;
539 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
540 for (int i=0; i<firmware->getCapability(LogicalSwitches); i++) {
541 bool lsEmpty = true;
542 for (int k=0; k < modelPrinterMap.size(); k++) {
543 if (!modelPrinterMap.value(k).first->logicalSw[i].isEmpty()) {
544 lsEmpty = false;
545 break;
548 if (!lsEmpty) {
549 count++;
550 columns.append("<tr><td width='20%'><b>" + tr("L%1").arg(i+1) + "</b></td><td>");
551 COMPARE(modelPrinter->printLogicalSwitchLine(i));
552 columns.append("</td></tr>");
555 columns.append("</table>");
556 if (count > 0) {
557 str.append(printTitle(tr("Logical Switches")));
558 str.append(columns.print());
560 return str;
563 QString MultiModelPrinter::printCustomFunctions()
565 QString str;
566 MultiColumns columns(modelPrinterMap.size());
567 int count = 0;
568 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
569 for (int i=0; i < firmware->getCapability(CustomFunctions); i++) {
570 bool sfEmpty = true;
571 for (int k=0; k < modelPrinterMap.size(); k++) {
572 if (!modelPrinterMap.value(k).first->customFn[i].isEmpty()) {
573 sfEmpty = false;
574 break;
577 if (!sfEmpty) {
578 count++;
579 columns.append("<tr><td width='20%'><b>" + tr("SF%1").arg(i+1) + "</b></td><td>");
580 COMPARE(modelPrinter->printCustomFunctionLine(i));
581 columns.append("</td></tr>");
584 columns.append("</table>");
585 if (count > 0) {
586 str.append(printTitle(tr("Special Functions")));
587 str.append(columns.print());
589 return str;
592 QString MultiModelPrinter::printTelemetry()
594 QString str = printTitle(tr("Telemetry Settings"));
596 // Analogs on non ARM boards
597 if (!IS_ARM(firmware->getBoard())) {
598 MultiColumns columns(modelPrinterMap.size());
599 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>" \
600 "<tr><td width='22%'><b>" + tr("Analogs") + "</b></td><td width='26%'><b>" + tr("Unit") + "</b></td><td width='26%'><b>" + tr("Scale") + "</b></td><td width='26%'><b>" + tr("Offset") + "</b></td></tr>");
601 for (int i=0; i<2; i++) {
602 columns.append("<tr><td><b>"+tr("A%1").arg(i+1)+"</b></td><td>");
603 COMPARE(getFrSkyUnits(model->frsky.channels[i].type));
604 columns.append("</td><td>");
605 COMPARE(QString::number((model->frsky.channels[i].ratio / (model->frsky.channels[i].type==0 ? 10.0 : 1)), 10, (model->frsky.channels[i].type==0 ? 1 : 0)));
606 columns.append("</td><td>");
607 COMPARE(QString::number((model->frsky.channels[i].offset*(model->frsky.channels[i].ratio / (model->frsky.channels[i].type==0 ?10.0 : 1)))/255, 10, (model->frsky.channels[i].type==0 ? 1 : 0)));
608 columns.append("</td></tr>");
610 columns.append("</table><br/>");
611 str.append(columns.print());
612 // TODO I remove the analogs alarms for now
615 // RSSI alarms
617 MultiColumns columns(modelPrinterMap.size());
618 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>");
619 for (int i=0; i<2; i++) {
620 columns.append("<tr><td><b>" + QString(i==0 ? tr("RSSI Alarms") : "") + "</b></td><td>");
621 if (IS_ARM(getCurrentBoard())) {
622 COMPARE(i==0 ? tr("Low Alarm") : tr("Critical Alarm"));
624 else {
625 COMPARE(getFrSkyAlarmType(model->rssiAlarms.level[i]));
627 columns.append("</td><td>&lt;</td><td>");
628 if (i == 0) {
629 COMPARE(QString::number(model->rssiAlarms.warning, 10));
631 else {
632 COMPARE(QString::number(model->rssiAlarms.critical, 10));
634 columns.append("</td></tr>");
636 columns.append("</table><br/>");
637 str.append(columns.print());
640 return str;