[Companion] Fixes
[opentx.git] / companion / src / multimodelprinter.cpp
blobe50fe9636a0d1b166af86957a5ef9e9631e15ae7
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<models.size(); cc++) { \
105 ModelPrinter * modelPrinter = modelPrinters[cc]; \
106 ModelData * model = models[cc]; \
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(modelPrinters.count()) + label + "</h2></td></tr>";
117 MultiModelPrinter::MultiModelPrinter(Firmware * firmware):
118 firmware(firmware)
122 MultiModelPrinter::~MultiModelPrinter()
124 for(int i=0; i<modelPrinters.size(); i++) {
125 delete modelPrinters[i];
129 void MultiModelPrinter::setModel(int idx, const ModelData & model)
131 int count = std::max(models.size(), idx+1);
132 models.resize(count);
133 modelPrinters.resize(count);
135 if (modelPrinters[idx]) {
136 // free existing model printer
137 delete modelPrinters[idx];
138 modelPrinters[idx] = 0;
141 models[idx] = (ModelData *)&model; // TODO remove cast
142 modelPrinters[idx] = new ModelPrinter(firmware, defaultSettings, model);
145 QString MultiModelPrinter::print(QTextDocument * document)
147 if (document) document->clear();
149 QString str = "<table border='1' cellspacing='0' cellpadding='3' width='100%' style='font-family: monospace;'>";
150 str += printSetup();
151 if (firmware->getCapability(Heli))
152 str += printHeliSetup();
153 if (firmware->getCapability(FlightModes))
154 str += printFlightModes();
155 str += printInputs();
156 str += printMixers();
157 str += printLimits();
158 str += printCurves(document);
159 if (firmware->getCapability(Gvars) && !firmware->getCapability(GvarsFlightModes))
160 str += printGvars();
161 str += printLogicalSwitches();
162 str += printCustomFunctions();
163 str += printTelemetry();
164 str += "</table>";
165 return str;
168 QString MultiModelPrinter::printSetup()
170 QString str = printTitle(tr("General Model Settings"));
172 MultiColumns columns(models.size());
173 columns.appendTitle(tr("Name:"));
174 COMPARE(model->name);
175 columns.append("<br/>");
176 columns.appendTitle(tr("EEprom Size:"));
177 COMPARE(modelPrinter->printEEpromSize());
178 columns.append("<br/>");
179 for (int i=0; i<firmware->getCapability(Timers); i++) {
180 columns.appendTitle(tr("Timer%1:").arg(i+1));
181 COMPARE(modelPrinter->printTimer(i));
182 columns.append("<br/>");
184 for (int i=0; i<firmware->getCapability(NumModules); i++) {
185 columns.appendTitle(firmware->getCapability(NumModules) > 1 ? tr("Module%1:").arg(i+1) : tr("Module:"));
186 COMPARE(modelPrinter->printModule(i));
187 columns.append("<br/>");
189 if (IS_TARANIS(firmware->getBoard())) {
190 columns.appendTitle(tr("Trainer port:"));
191 COMPARE(modelPrinter->printTrainerMode());
192 columns.append("<br/>");
194 columns.appendTitle(tr("Throttle Trim:"));
195 COMPARE(modelPrinter->printThrottleTrimMode());
196 columns.append("<br/>");
197 columns.appendTitle(tr("Trim Increment:"));
198 COMPARE(modelPrinter->printTrimIncrementMode());
199 columns.append("<br/>");
200 columns.appendTitle(tr("Center Beep:"));
201 COMPARE(modelPrinter->printCenterBeep());
202 str.append(columns.print());
203 return str;
207 QString MultiModelPrinter::printHeliSetup()
209 bool heliEnabled = false;
210 for (int k=0; k<models.size(); k++) {
211 heliEnabled = heliEnabled || models[k]->swashRingData.type != HELI_SWASH_TYPE_NONE;
214 if (!heliEnabled)
215 return "";
217 QString str = printTitle(tr("Helicopter Setup"));
218 MultiColumns columns(models.size());
219 columns.appendTitle (tr("Swash Type:"));
220 COMPARE(modelPrinter->printHeliSwashType());
221 columns.append ("<br/>");
223 columns.appendTitle (tr("Swash Ring:"));
224 COMPARE(model->swashRingData.value);
226 columns.append ("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
227 columns.append("<tr>");
228 columns.append("<td></td><td><b>" + tr("Input") + "</b></td><td><b>" + tr("Weight") + "</b></td>");
229 columns.append("</tr>");
231 columns.append("<tr><td><b>" + tr("Long. cyc") + "</b></td><td>");
232 COMPARE(model->swashRingData.elevatorSource.toString(model));
233 columns.append("</td><td>");
234 COMPARE(model->swashRingData.elevatorWeight)
235 columns.append("</td></tr>");
237 columns.append("<tr><td><b>" + tr("Lateral cyc") + "</b></td><td>");
238 COMPARE(model->swashRingData.aileronSource.toString(model));
239 columns.append("</td><td>");
240 COMPARE(model->swashRingData.aileronWeight)
241 columns.append("</td></tr>");
244 columns.append("<tr><td><b>" + tr("Collective") + "</b></td><td>");
245 COMPARE(model->swashRingData.collectiveSource.toString(model));
246 columns.append("</td><td>");
247 COMPARE(model->swashRingData.collectiveWeight)
248 columns.append("</td></tr>");
249 columns.append("</table>");
253 str.append(columns.print());
254 return str;
257 QString MultiModelPrinter::printFlightModes()
259 QString str = printTitle(tr("Flight modes"));
261 // Trims
263 MultiColumns columns(models.size());
264 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
265 columns.append("<tr>");
266 columns.append("<td><b>" + tr("Flight mode") + "</b></td>");
267 columns.append("<td><b>" + tr("Switch") + "</b></td>");
268 columns.append("<td><b>" + tr("Fade IN") + "</b></td>");
269 columns.append("<td><b>" + tr("Fade OUT") + "</b></td>");
270 for (int i=0; i<4; i++) {
271 columns.append("<td><b>" + getCurrentFirmware()->getAnalogInputName(i) + " trim</b></td>");
273 columns.append("</tr>");
275 for (int i=0; i<firmware->getCapability(FlightModes); i++) {
276 columns.append("<tr><td><b>" + tr("FM%1").arg(i) + "</b>&nbsp;");
277 COMPARE(model->flightModeData[i].name);
278 columns.append("</td><td>");
279 COMPARE(model->flightModeData[i].swtch.toString());
280 columns.append("</td><td>");
281 COMPARE(model->flightModeData[i].fadeIn);
282 columns.append("</td><td>");
283 COMPARE(model->flightModeData[i].fadeOut);
284 columns.append("</td>");
285 for (int k=0; k<CPN_MAX_STICKS; k++) {
286 columns.append("<td>");
287 COMPARE(modelPrinter->printTrim(i, k));
288 columns.append("</td>");
290 columns.append("</tr>");
293 columns.append("</table>");
294 str.append(columns.print());
297 // GVars and Rotary Encoders
298 int gvars = firmware->getCapability(Gvars);
299 if ((gvars && firmware->getCapability(GvarsFlightModes)) || firmware->getCapability(RotaryEncoders)) {
300 MultiColumns columns(models.size());
301 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
302 columns.append("<tr><td><b>" + tr("Flight mode") + "</b></td>");
303 if (firmware->getCapability(GvarsFlightModes)) {
304 for (int i=0; i<gvars; i++) {
305 columns.append("<td><b>" + tr("GV%1").arg(i+1) + "</b><br/>");
306 COMPARE(model->gvars_names[i]);
307 columns.append("</td>");
310 for (int i=0; i<firmware->getCapability(RotaryEncoders); i++) {
311 columns.append("<td><b>" + tr("RE%1").arg(i+1) + "</b></td>");
313 columns.append("</tr>");
314 for (int i=0; i<firmware->getCapability(FlightModes); i++) {
315 columns.append("<tr><td><b>" + tr("FM%1").arg(i) + "</b>&nbsp;");
316 COMPARE(model->flightModeData[i].name);
317 columns.append("</td>");
318 if (firmware->getCapability(GvarsFlightModes)) {
319 for (int k=0; k<gvars; k++) {
320 columns.append("<td>");
321 COMPARE(modelPrinter->printGlobalVar(i, k));
322 columns.append("</td>");
325 for (int k=0; k<firmware->getCapability(RotaryEncoders); k++) {
326 columns.append("<td>");
327 COMPARE(modelPrinter->printRotaryEncoder(i, k));
328 columns.append("</td>");
330 columns.append("</tr>");
332 columns.append("</table>");
333 str.append(columns.print());
336 return str;
339 QString MultiModelPrinter::printLimits()
341 QString str = printTitle(tr("Limits"));
342 MultiColumns columns(models.size());
343 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>" \
344 "<tr>" \
345 " <td><b>" + tr("Channel") + "</b></td>" \
346 " <td><b>" + (firmware->getCapability(ChannelsName) > 0 ? tr("Name") : "") + "</b></td>" \
347 " <td><b>" + tr("Offset") + "</b></td>" \
348 " <td><b>" + tr("Min") + "</b></td>" \
349 " <td><b>" + tr("Max") + "</b></td>" \
350 " <td><b>" + tr("Invert") + "</b></td>" \
351 "</tr>");
352 for (int i=0; i<firmware->getCapability(Outputs); i++) {
353 columns.append("<tr><td><b>");
354 COMPARE(modelPrinter->printChannelName(i));
355 columns.append("</b></td><td>");
356 COMPARE(modelPrinter->printOutputName(i));
357 columns.append("</td><td>");
358 COMPARE(model->limitData[i].offsetToString());
359 columns.append("</td><td>");
360 COMPARE(model->limitData[i].minToString());
361 columns.append("</td><td>");
362 COMPARE(model->limitData[i].maxToString());
363 columns.append("</td><td>");
364 COMPARE(model->limitData[i].revertToString());
365 columns.append("</td></tr>");
367 columns.append("</table>");
368 str.append(columns.print());
369 return str;
372 QString MultiModelPrinter::printGvars()
374 QString str = printTitle(tr("Global Variables"));
375 int gvars = firmware->getCapability(Gvars);
376 MultiColumns columns(models.size());
377 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'><tr>");
378 for (int i=0; i<gvars; i++) {
379 columns.append(QString("<td><b>") + tr("GV%1").arg(i+1) + "</b></td>");
381 columns.append("</tr><tr>");
382 for (int i=0; i<gvars; i++) {
383 columns.append("<td>");
384 COMPARE(model->flightModeData[0].gvars[i]);
385 columns.append("</td>");
387 columns.append("</tr>");
388 str.append(columns.print());
389 return str;
392 QString MultiModelPrinter::printInputs()
394 QString str = printTitle(tr("Inputs"));
395 MultiColumns columns(models.size());
396 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
397 for (int i=0; i<std::max(4, firmware->getCapability(VirtualInputs)); i++) {
398 int count = 0;
399 for (int k=0; k<models.size(); k++) {
400 count = std::max(count, models[k]->expos(i).size());
402 if (count > 0) {
403 columns.append("<tr><td width='20%'><b>");
404 COMPARE(modelPrinter->printInputName(i));
405 columns.append("</b></td><td>");
406 for (int j=0; j<count; j++) {
407 if (j > 0)
408 columns.append("<br/>");
409 COMPARE(j<model->expos(i).size() ? modelPrinter->printInputLine(*model->expos(i)[j]) : "");
411 columns.append("</td></tr>");
414 str.append(columns.print());
415 return str;
418 QString MultiModelPrinter::printMixers()
420 QString str = printTitle(tr("Mixers"));
421 MultiColumns columns(models.size());
422 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
423 for (int i=0; i<firmware->getCapability(Outputs); i++) {
424 int count = 0;
425 for (int k=0; k<models.size(); k++) {
426 count = std::max(count, models[k]->mixes(i).size());
428 if (count > 0) {
429 columns.append("<tr><td width='20%'><b>");
430 COMPARE(modelPrinter->printMixerName(i+1));
431 columns.append("</b></td><td>");
432 for (int j=0; j<count; j++) {
433 if (j > 0)
434 columns.append("<br/>");
435 COMPARE((j < model->mixes(i).size()) ? modelPrinter->printMixerLine(*model->mixes(i)[j], (j>0)) : "&nbsp;");
437 columns.append("</td></tr>");
440 str.append(columns.print());
441 return str;
444 QString MultiModelPrinter::printCurves(QTextDocument * document)
446 QString str;
447 MultiColumns columns(models.size());
448 int count = 0;
449 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
450 for (int i=0; i<firmware->getCapability(NumCurves); i++) {
451 bool curveEmpty = true;
452 for (int k=0; k<models.size(); k++) {
453 if (!models[k]->curves[i].isEmpty()) {
454 curveEmpty = false;
455 break;
458 if (!curveEmpty) {
459 count++;
460 columns.append("<tr><td width='20%'><b>" + tr("CV%1").arg(i+1) + "</b></td><td>");
461 COMPARE(modelPrinter->printCurve(i));
462 for (int k=0; k<models.size(); k++)
463 columns.append(k, QString("<br/><img src='%1' border='0' />").arg(modelPrinters[k]->createCurveImage(i, document)));
464 columns.append("</td></tr>");
467 columns.append("</table><br/>");
468 if (count > 0) {
469 str.append(printTitle(tr("Curves")));
470 str.append(columns.print());
472 return str;
475 QString MultiModelPrinter::printLogicalSwitches()
477 QString str;
478 MultiColumns columns(models.size());
479 int count = 0;
480 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
481 for (int i=0; i<firmware->getCapability(LogicalSwitches); i++) {
482 bool lsEmpty = true;
483 for (int k=0; k<models.size(); k++) {
484 if (!modelPrinters[k]->printLogicalSwitchLine(i).isEmpty()) {
485 lsEmpty = false;
486 break;
489 if (!lsEmpty) {
490 count++;
491 columns.append("<tr><td width='20%'><b>" + tr("L%1").arg(i+1) + "</b></td><td>");
492 COMPARE(modelPrinter->printLogicalSwitchLine(i));
493 columns.append("</td></tr>");
496 columns.append("</table>");
497 if (count > 0) {
498 str.append(printTitle(tr("Logical Switches")));
499 str.append(columns.print());
501 return str;
504 QString MultiModelPrinter::printCustomFunctions()
506 QString str;
507 MultiColumns columns(models.size());
508 int count = 0;
509 columns.append("<table cellspacing='0' cellpadding='1' width='100%' border='0' style='border-collapse:collapse'>");
510 for (int i=0; i<firmware->getCapability(CustomFunctions); i++) {
511 bool sfEmpty = true;
512 for (int k=0; k<models.size(); k++) {
513 if (!modelPrinters[k]->printCustomFunctionLine(i).isEmpty()) {
514 sfEmpty = false;
515 break;
518 if (!sfEmpty) {
519 count++;
520 columns.append("<tr><td width='20%'><b>" + tr("SF%1").arg(i+1) + "</b></td><td>");
521 COMPARE(modelPrinter->printCustomFunctionLine(i));
522 columns.append("</td></tr>");
525 columns.append("</table>");
526 if (count > 0) {
527 str.append(printTitle(tr("Special Functions")));
528 str.append(columns.print());
530 return str;
533 QString MultiModelPrinter::printTelemetry()
535 QString str = printTitle(tr("Telemetry Settings"));
537 // Analogs on non ARM boards
538 if (!IS_ARM(firmware->getBoard())) {
539 MultiColumns columns(models.size());
540 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>" \
541 "<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>");
542 for (int i=0; i<2; i++) {
543 columns.append("<tr><td><b>"+tr("A%1").arg(i+1)+"</b></td><td>");
544 COMPARE(getFrSkyUnits(model->frsky.channels[i].type));
545 columns.append("</td><td>");
546 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)));
547 columns.append("</td><td>");
548 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)));
549 columns.append("</td></tr>");
551 columns.append("</table><br/>");
552 str.append(columns.print());
553 // TODO I remove the analogs alarms for now
556 // RSSI alarms
558 MultiColumns columns(models.size());
559 columns.append("<table border='0' cellspacing='0' cellpadding='1' width='100%'>");
560 for (int i=0; i<2; i++) {
561 columns.append("<tr><td><b>" + QString(i==0 ? tr("RSSI Alarms") : "") + "</b></td><td>");
562 if (IS_HORUS_OR_TARANIS(GetEepromInterface()->getBoard())) {
563 COMPARE(i==0 ? tr("Low Alarm") : tr("Critical Alarm"));
565 else {
566 COMPARE(getFrSkyAlarmType(model->frsky.rssiAlarms[i].level));
568 columns.append("</td><td>&lt;</td><td>");
569 COMPARE(QString::number(model->frsky.rssiAlarms[i].value, 10));
570 columns.append("</td></tr>");
572 columns.append("</table><br/>");
573 str.append(columns.print());
576 return str;