[Simulator] Dyanamic Simulator virtual hardware generation and many other improvements (#4260)

* Move simulator widgets to subfolder (no code changes).

* [Simulator] Major Simulator re-structuring to reduce code redundancies and allow for fully dynamic simulated hardware setup:
    SimulatorDialog is no longer sub-classed by individual radio UIs, instead a new class is added which handles just the "skin," LCD, and navigation controls (also makes setting up new models much quicker);
    All radio switches/knobs/sliders are populated dynamically based on loaded hardware configuration data (eeprom/bin), including control types and names (in case of no hardware config data, reasonable "factory defaults" are loaded);
    Starting Horus sim with alternate path for RADIO and MODEL data folders is now supported (.otx file is WIP);
    6-way switch now works properly and various other inconsistencies addressed (eg. some Horus analogs were backwards).

* Add missing OpenTX headers to new files.

* [Simulator] Fix loading of Horus hardware settings from Companion simulation.

* Update Travis CI build environment to Qt 5.7.

* [Simulator] Fix 9X missing colored side LCD images, an #include fix, and some cleanup.

* [TravisCI] Set up for parallel Travis builds and use Qt5.7 (#4263)

* Fix starting Simulator with non-existing file/folder structure (now properly creates new ones as needed, including Horus directories with default radio/model);  Temporarily remove option for using .otx file with Horus (until that feature is added).

* Load correct simulator skin for X7 when launching from Companion.

* X7 hardware defaults changed to match production hardware

* Fix minor compiler warning in mdichild.cpp, and some cleanup in eeprominterface.cpp.

* [Simulator] Centralize radio UI flavor determination.

* [Simulator] Updates:
    Add X7 UI flavor;
    Centralize LCD settings;
    Simplify drawing curved UI widgets;
    LCD color can be changed for any 1/4-bit display radio;
    Slight change on default LCD "off" color;
    Refactor LcdWidget to not use globals.

* [Companion] X7 EEPROM import fix

+ cosmetics
+ compilation error fixed on Manjaro

* [Companion] 9XR-PRO EEPROM import fix

+ cosmetics
+ compilation error fixed on Manjaro

* Navigation fix
French translations

* [X7] Main view cosmetics

* Fix switch/ana custom name import

* [Simulator] Add white border above/below X7 bezel, and use white-colored padding on sides; Adjust X7 default LCD color.

* Does this work? (#4269)

Read an Horus SD card produces a temporary .otx file now

* Bsongis/companion warning before automatic conversions (#4271)

* Does this work?

* Warning before automatic conversions

* Force different file name after conversion (#4272)

* Force different file name after conversion

* More detailed instructions

* [Companion] Files automatically named during XXX => Horus conversions
This commit is contained in:
Max Paperno
2017-01-24 07:54:12 +01:00
committed by Bertrand Songis
parent b3f4870da7
commit 17a03e6c18
43 changed files with 3561 additions and 5934 deletions
@@ -0,0 +1,496 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "virtualjoystickwidget.h"
#include "constants.h"
#include "sliderwidget.h"
#include "modeledit/node.h"
#include "helpers.h"
VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool showTrims, bool showBtns, bool showValues, QSize size) :
QWidget(parent),
stickSide(side),
prefSize(size),
hTrimSlider(NULL),
vTrimSlider(NULL),
btnHoldX(NULL),
btnHoldY(NULL),
btnFixX(NULL),
btnFixY(NULL),
nodeLabelX(NULL),
nodeLabelY(NULL)
{
ar = (float)size.width() / size.height();
extraSize = QSize(0, 0);
// 5 col x 4 rows
layout = new QGridLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setSizePolicy(sizePolicy);
gv = new QGraphicsView(this);
gv->setSizePolicy(sizePolicy);
gv->setMinimumSize(QSize(150, 150));
gv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scene = new QGraphicsScene(gv);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
gv->setScene(scene);
node = new Node();
node->setPos(-GBALL_SIZE / 2, -GBALL_SIZE / 2);
node->setBallSize(GBALL_SIZE);
scene->addItem(node);
int colvt, colbb, colvx, colvy;
if (stickSide == 'L') {
colvt = 3;
colbb = 1;
colvx = 1;
colvy = 3;
}
else {
colvt = 1;
colbb = 3;
colvx = 3;
colvy = 1;
}
if (showTrims) {
QWidget * hTrimWidget = createTrimWidget('H');
QWidget * vTrimWidget = createTrimWidget('V');
layout->addWidget(vTrimWidget, 1, colvt, 1, 1);
layout->addWidget(hTrimWidget, 2, 2, 1, 1);
hTrimSlider = hTrimWidget->findChild<SliderWidget *>();
vTrimSlider = vTrimWidget->findChild<SliderWidget *>();
extraSize += QSize(vTrimWidget->sizeHint().width(), hTrimWidget->sizeHint().height());
}
if (showBtns) {
QVBoxLayout * btnbox = new QVBoxLayout();
btnbox->setContentsMargins(9, 9, 9, 9);
btnbox->setSpacing(2);
btnbox->addWidget(btnHoldY = createButtonWidget(HOLD_Y));
btnbox->addWidget(btnFixY = createButtonWidget(FIX_Y));
btnbox->addWidget(btnFixX = createButtonWidget(FIX_X));
btnbox->addWidget(btnHoldX = createButtonWidget(HOLD_X));
layout->addLayout(btnbox, 1, colbb, 1, 1);
extraSize.setWidth(extraSize.width() + btnbox->sizeHint().width());
}
if (showValues) {
QLayout * valX = createNodeValueLayout('X', nodeLabelX);
QLayout * valY = createNodeValueLayout('Y', nodeLabelY);
layout->addLayout(valX, 2, colvx, 1, 1);
layout->addLayout(valY, 2, colvy, 1, 1);
}
layout->addItem(new QSpacerItem(0, 0), 0, 0, 1, 5); // r0 c0-4: top v spacer
layout->addItem(new QSpacerItem(0, 0), 1, 0, 2, 1); // r1-2 c0: left h spacer
layout->addWidget(gv, 1, 2, 1, 1); // r1 c2: stick widget
layout->addItem(new QSpacerItem(0, 0), 1, 4, 2, 1); // r1-2 c4: right h spacer
layout->addItem(new QSpacerItem(0, 0), 3, 0, 1, 5); // r3 c0-4: bot v spacer
connect(node, SIGNAL(xChanged()), this, SLOT(updateNodeValueLabels()));
connect(node, SIGNAL(yChanged()), this, SLOT(updateNodeValueLabels()));
setSize(prefSize);
}
void VirtualJoystickWidget::setStickX(qreal x)
{
node->setX(x);
}
void VirtualJoystickWidget::setStickY(qreal y)
{
node->setY(y);
}
void VirtualJoystickWidget::setStickPos(QPointF xy)
{
node->setPos(xy);
}
void VirtualJoystickWidget::centerStick()
{
node->stepToCenter();
}
qreal VirtualJoystickWidget::getStickX()
{
return node->getX();
}
qreal VirtualJoystickWidget::getStickY()
{
return node->getY();
}
QPointF VirtualJoystickWidget::getStickPos()
{
return QPointF(node->getX(), node->getY());
}
void VirtualJoystickWidget::setTrimValue(int which, int value)
{
SliderWidget * slider = getTrimSlider(which);
if (slider) {
slider->setValue(value);
}
}
void VirtualJoystickWidget::setTrimRange(int which, int min, int max)
{
SliderWidget * slider = getTrimSlider(which);
if (slider) {
slider->setRange(min, max);
}
}
int VirtualJoystickWidget::getTrimValue(int which)
{
SliderWidget * slider = getTrimSlider(which);
if (slider) {
return slider->value();
}
return 0;
}
void VirtualJoystickWidget::setStickConstraint(int which, bool active)
{
if (btnHoldX == NULL)
return; // no buttons
switch (which) {
case HOLD_X:
btnHoldX->setChecked(active);
break;
case HOLD_Y:
btnHoldY->setChecked(active);
break;
case FIX_X:
btnFixX->setChecked(active);
break;
case FIX_Y:
btnFixY->setChecked(active);
break;
default:
break;
}
}
void VirtualJoystickWidget::setSize(QSize size)
{
float thisAspectRatio = (float)size.width() / size.height();
float newGvSz, spacerSz;
bool sized = false;
if (thisAspectRatio > ar) {
// constrain width
newGvSz = (height() - extraSize.height()) * ar; // new width
spacerSz = (width() - newGvSz - extraSize.width()) / 2; // + 0.5;
if (spacerSz >= 0.0f) {
layout->setRowStretch(0, 0);
layout->setColumnStretch(0, spacerSz);
layout->setColumnStretch(4, spacerSz);
layout->setRowStretch(3, 0);
sized = true;
}
}
if (!sized) {
// constrain height
newGvSz = (width() - extraSize.width()) * ar; // new height
spacerSz = (height() - newGvSz - extraSize.height()) / 2; // + 0.5;
spacerSz = qMax(spacerSz, 0.0f);
layout->setRowStretch(0, spacerSz);
layout->setColumnStretch(0, 0);
layout->setColumnStretch(4, 0);
layout->setRowStretch(3, spacerSz);
}
layout->setColumnStretch(2, newGvSz);
layout->setRowStretch(1, newGvSz);
prefSize = QSize(newGvSz + extraSize.width(), newGvSz + extraSize.height());
gv->resize(newGvSz, newGvSz);
gv->updateGeometry();
repositionNode();
//qDebug() << thisAspectRatio << size << newGvSz << spacerSz << extraSize << gv->geometry() << gv->contentsRect() << gv->frameRect() << getStickPos();
}
void VirtualJoystickWidget::repositionNode()
{
QRect qr = gv->contentsRect();
qreal w = (qreal)qr.width() - GBALL_SIZE;
qreal h = (qreal)qr.height() - GBALL_SIZE;
qreal cx = (qreal)qr.width()/2;
qreal cy = (qreal)qr.height()/2;
scene->setSceneRect(-cx,-cy,w,h);
QPointF p = node->pos();
p.setX(qMin(cx, qMax(p.x(), -cx)));
p.setY(qMin(cy, qMax(p.y(), -cy)));
node->setPos(p);
updateNodeValueLabels();
}
QWidget *VirtualJoystickWidget::createTrimWidget(QChar type)
{
QSizePolicy sp;
QString btnAlabel, btnBlabel;
QString btnAname = QString("%1TrimBtnA_%2").arg(type.toLower()).arg(stickSide);
QString btnBname = QString("%1TrimBtnB_%2").arg(type.toLower()).arg(stickSide);
QString sliderName = QString("%1TrimAdj_%2").arg(type.toLower()).arg(stickSide);
QWidget * trimWidget = new QWidget(this);
QBoxLayout * trimLayout = new QVBoxLayout(trimWidget);
SliderWidget * trimSlider = new SliderWidget(trimWidget);
QPushButton * trimBtnA = new QPushButton(trimWidget);
QPushButton * trimBtnB = new QPushButton(trimWidget);
if (type == 'H') {
sp = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
trimLayout->setDirection(QBoxLayout::LeftToRight);
trimSlider->setFixedHeight(23);
trimSlider->setOrientation(Qt::Horizontal);
trimBtnA->setText(ARROW_LEFT);
trimBtnB->setText(ARROW_RIGHT);
}
else {
sp = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
trimLayout->setDirection(QBoxLayout::TopToBottom);
trimSlider->setFixedWidth(23);
trimSlider->setOrientation(Qt::Vertical);
trimBtnA->setText(ARROW_UP);
trimBtnB->setText(ARROW_DOWN);
}
trimWidget->setSizePolicy(sp);
trimSlider->setObjectName(sliderName);
trimSlider->setProperty("trimType", getTrimSliderType(type));
trimSlider->setSizePolicy(sp);
trimSlider->setMinimum(-125);
trimSlider->setMaximum(125);
trimBtnA->setObjectName(btnAname);
trimBtnA->setProperty("btnType", getTrimButtonType(type, 0));
trimBtnA->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
trimBtnA->setMaximumSize(QSize(23, 23));
trimBtnA->setAutoDefault(false);
trimBtnB->setObjectName(btnBname);
trimBtnB->setProperty("btnType", getTrimButtonType(type, 1));
trimBtnB->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
trimBtnB->setMaximumSize(QSize(23, 23));
trimBtnB->setAutoDefault(false);
trimLayout->setSpacing(6);
trimLayout->setContentsMargins(8, 9, 8, 9);
trimLayout->addWidget(trimBtnA);
trimLayout->addWidget(trimSlider);
trimLayout->addWidget(trimBtnB);
connect(trimBtnA, SIGNAL(pressed()), SLOT(onTrimPressed()));
connect(trimBtnB, SIGNAL(pressed()), SLOT(onTrimPressed()));
connect(trimBtnA, SIGNAL(released()), SIGNAL(trimButtonReleased()));
connect(trimBtnB, SIGNAL(released()), SIGNAL(trimButtonReleased()));
connect(trimSlider, SIGNAL(valueChanged(int)), SLOT(onSliderChange(int)));
return trimWidget;
}
QPushButton * VirtualJoystickWidget::createButtonWidget(int type)
{
QString btnRole, btnLabel;
switch (type) {
case HOLD_Y:
btnLabel = tr("Hold Y");
break;
case FIX_Y:
btnLabel = tr("Fix Y");
break;
case FIX_X:
btnLabel = tr("Fix X");
break;
case HOLD_X:
default:
btnLabel = tr("Hold X");
break;
}
QPushButton * btn = new QPushButton(this);
btn->setObjectName(QString("%1_%2").arg(btnLabel.replace(" ", "_")).arg(stickSide));
btn->setProperty("btnType", type);
btn->setText(btnLabel);
QFont font;
font.setPointSize(8);
btn->setFont(font);
btn->setStyleSheet(QLatin1String( \
"QPushButton {"
" background-color: #EEEEEE;"
" border-style: outset;"
" border-width: 1px;"
" border-radius: 4px;"
" border-color: black;"
" padding: 2px;"
"}"
"QPushButton:checked {\n"
" background-color: #4CC417;\n"
" border-style: inset;\n"
"}" ));
btn->setCheckable(true);
connect(btn, SIGNAL(toggled(bool)), SLOT(onButtonChange(bool)));
return btn;
}
QLayout *VirtualJoystickWidget::createNodeValueLayout(QChar type, QLabel *& valLabel)
{
QLabel * lbl = new QLabel(QString("%1 %").arg(type), this);
lbl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
lbl->setAlignment(Qt::AlignCenter);
QLabel * val = new QLabel("0");
val->setObjectName(QString("val_%1").arg(type));
val->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
val->setAlignment(Qt::AlignCenter);
QVBoxLayout * layout = new QVBoxLayout();
layout->setContentsMargins(2, 2, 2, 2);
layout->setSpacing(2);
layout->addWidget(lbl);
layout->addWidget(val);
valLabel = val;
return layout;
}
int VirtualJoystickWidget::getTrimSliderType(QChar type)
{
if (stickSide == 'L') {
if (type == 'H')
return TRIM_AXIS_L_X;
else
return TRIM_AXIS_L_Y;
}
else {
if (type == 'H')
return TRIM_AXIS_R_X;
else
return TRIM_AXIS_R_Y;
}
}
int VirtualJoystickWidget::getTrimButtonType(QChar type, int pos)
{
if (stickSide == 'L') {
if (type == 'H') {
if (pos == 0)
return TRIM_LH_L;
else
return TRIM_LH_R;
}
else {
if (pos == 0)
return TRIM_LV_UP;
else
return TRIM_LV_DN;
}
}
// right side
else {
if (type == 'H') {
if (pos == 0)
return TRIM_RH_L;
else
return TRIM_RH_R;
}
else {
if (pos == 0)
return TRIM_RV_UP;
else
return TRIM_RV_DN;
}
}
}
SliderWidget *VirtualJoystickWidget::getTrimSlider(int which)
{
if (which == TRIM_AXIS_L_X || which == TRIM_AXIS_R_X)
return hTrimSlider;
else
return vTrimSlider;
}
void VirtualJoystickWidget::onTrimPressed()
{
if (!sender() || !sender()->property("btnType").isValid())
return;
emit trimButtonPressed(sender()->property("btnType").toInt());
}
void VirtualJoystickWidget::onSliderChange(int value)
{
if (!sender() || !sender()->property("trimType").isValid())
return;
emit trimSliderMoved(sender()->property("trimType").toInt(), value);
updateNodeValueLabels();
}
void VirtualJoystickWidget::onButtonChange(bool checked)
{
if (!sender() || !sender()->property("btnType").isValid())
return;
switch (sender()->property("btnType").toInt()) {
case HOLD_Y:
node->setCenteringY(!checked);
break;
case FIX_Y:
node->setFixedY(checked);
break;
case FIX_X:
node->setFixedX(checked);
break;
case HOLD_X:
node->setCenteringX(!checked);
break;
}
}
void VirtualJoystickWidget::updateNodeValueLabels()
{
if (nodeLabelX)
nodeLabelX->setText(QString("%1").arg((qreal)node->getX() * 100 + getTrimValue(0) / 5, 2, 'f', 0));
if (nodeLabelY)
nodeLabelY->setText(QString("%1").arg((qreal)node->getY() * -100 + getTrimValue(1) / 5, 2, 'f', 0));
}