Files
edgetx-gx12/companion/src/simulation/widgets/virtualjoystickwidget.cpp
T
Max PapernoandBertrand Songis 2f9d9c1a99 [Simulator] New main UI window, debug console, separate outputs window, more. (#4385)
* [package] Add QtSvg support DLLs for Windows.

* [Simulator] Add images/css resources for new version; Create SimulatorIcon and SimulatorStyle classes; Build with SVG support, remove unused bitmaps.

* [Simulator] Improve and simplify VirtualJoystickWidget resize event (sticks maintain X/Y position) & improve values layout w/out trims.

* [Simulator] Refactor Trainer simulator to use VirtualJoystickWidget, gains lock/hold buttons and X/Y position readout.

* [Simulator] More compact layout for Telemetry simulator, reduces min. width and adds scrolling columns to allow smaller window sizes.

* [Simulator] Add new SimulatorMainWindow and RadioOutputsWidget UI classes.

* [storage] Add non-persistent sessionId() to AppData for tracking currently active radio profile ID.

* [Simulator] Major UI updates:
    * Use a MainWindow interface with dockable/floatable windows, proper toolbar & menu;
    * Persistent size/layout state of all windows/docks/etc between uses, per radio profile;
    * Completely separate radio Outputs window with configurable layout (also persistent between uses);
    * Translations now enabled in standalone Simulator, follows main Companion setting (they need a lot of updates);
    * New custom icons throughout, toolbar icon sizes adhere to user preference in Companion.

* [Simulator] Debug Output console updates:
    * New filter feature with optional full RegEx support, can work in inclusive or exclusive modes (full help text is included in UI);
    * Up to 50 user-defined filters are saved to permanent settings, also a few predefined filters are provided;
    * Saves/restores the last filter used and if it was en/disabled;
    * Added word wrap and clear screen features;
    * Configurable scroll-back buffer size;
    * Increased efficiency & performance of the buffering/printing process.

* [Simulator][OSX] Visual UI tweaks for OS X.

* [Simulator] Fix some debug console settings not being restored properly.

* [Simulator] Add ability to hide/show menu bar; Persist view state of menu bar and radio title bar; Reduce font size on OS X; Other small visual tweaks and improvements.

* [build] Add QtSvg to Linux build scripts (a guess for Dockerfile).

* [build] Fix for Qt 5.3.

* Add OpenTx headers to new files.

* [Companion][storage] Update GeneralSettings() initializer to use current sessionId for profile data retrieval, removes workaround for standalone Simulator being started with arbitrary profile; AppData: always update sessionId when global id() changes.

* [X10] Fixes for new simulator
2017-02-04 11:59:42 +01:00

526 lines
14 KiB
C++

/*
* 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.
*/
#define GBALL_SIZE 20
#define RESX 1024
#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(size);
// gv->setMaximumSize(size + size * 3);
// gv->setFixedSize(prefSize);
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());
}
else {
colvx = colvy = colvt;
}
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);
if (!showTrims) {
QVBoxLayout * vertXY = new QVBoxLayout();
vertXY->addLayout(valX);
vertXY->addLayout(valY);
layout->addLayout(vertXY, 1, colvx, 1, 1);
extraSize += QSize(nodeLabelX->sizeHint().width(), 0);
}
else {
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, frameSize());
}
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 getStickPos().x();
}
qreal VirtualJoystickWidget::getStickY()
{
return getStickPos().y();
}
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::setStickColor(const QColor & color)
{
node->setColor(color);
}
QSize VirtualJoystickWidget::sizeHint() const {
return prefSize;
}
void VirtualJoystickWidget::resizeEvent(QResizeEvent * event)
{
QWidget::resizeEvent(event);
setSize(event->size(), event->oldSize());
}
void VirtualJoystickWidget::setSize(const QSize & size, const QSize &)
{
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();
QRectF qr = (QRectF)gv->contentsRect();
qreal w = qr.width() - GBALL_SIZE;
qreal h = qr.height() - GBALL_SIZE;
qreal cx = qr.width() / 2;
qreal cy = qr.height() / 2;
qreal nodeX = node->getX();
qreal nodeY = node->getY();
scene->setSceneRect(-cx,-cy,w,h);
node->setX(nodeX);
node->setY(nodeY);
//qDebug() << thisAspectRatio << size << newGvSz << spacerSz << extraSize << gv->geometry() << gv->contentsRect() << gv->frameRect() << getStickPos();
}
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);
val->setMinimumWidth(val->fontMetrics().width("-100 "));
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));
}