[Simulator] Further improvements (reload radio, momentary switches, etc.) (#4303)

* [Simulator] Add ability to reload all radio data w/out exiting simulator (will also adjust UI for any hardware changes); Refactor how keymap help text is generated.

* [Simulator] Only update channel/logical switch/GVar output displays if the corresponding tab is actually visible (further CPU usage reductions).

* [Simulator] Implement momentary toggle switches with "latch" function.

* [Simulator] Mouse wheel event over multi-position knobs now properly rotates them to detent positions.

* [Simulator] Fix mouse wheel event propagation after multi-position knob rotation; Cosmetics.

* [Simulator] Internals: Greatly improve object cleanup on exit; Refactor SimulatedUIWidget timer event triggering.

* [Simulator] Fix throttle stick position/lock not being set properly upon repeated launches from Companion.

* [Simulator] Fix build w/out SDL. KnobWidget cleanup.

* [Companion] Do NOT delete SimulatorInterface on simulator dialog exit (fix possible crash on dialog close).

* [Simulator] Ensure joystick is NULL at start.

* [Simulator] Fix/improve color blending on 4-bit screens with different BG colors; Cosmetics.

* [Simulator] Fix LCD screenshot folder path; Fix timer slot for Qt backwards compatibility.

* [TravisCI] Set Qt version to 5.3 to match release build servers.

* Fixed segfault when saving setup with no models defined [Horus]

* Fixed radio data import [Horus] (broken in 0f90ff0b65)

* [Simulator] Always keep toggle switch active while held with mouse button.
This commit is contained in:
Max Paperno
2017-01-28 13:43:56 +01:00
committed by Bertrand Songis
parent f40e64c2aa
commit de68db0905
16 changed files with 539 additions and 298 deletions
+2 -2
View File
@@ -11,11 +11,11 @@ python:
env:
global:
# - QT_BASE=53
- QT_BASE=53
# - QT_BASE=54
# - QT_BASE=55
# - QT_BASE=56
- QT_BASE=57
# - QT_BASE=57
#- GCC_ARM=/opt/gcc-arm-none-eabi/bin
- AVR_FLAVORS="AVR9X 9X GRUVIN9X MEGA2560"
matrix:
+3 -1
View File
@@ -11,7 +11,9 @@
<file>images/maps.png</file>
<file>images/track.png</file>
<file>images/track0.png</file>
<file>images/taranison.png</file>
<file>images/taranison.png</file>
<file>images/simulator/icons/8/lock-locked.png</file>
<file>images/simulator/icons/8/lock-unlocked.png</file>
<file>images/simulator/Horus/middle.png</file>
<file>images/simulator/Horus/left.png</file>
<file>images/simulator/Horus/right.png</file>
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

@@ -0,0 +1 @@
Open Iconic collection, used under MIT License. https://github.com/iconic/open-iconic
@@ -83,8 +83,9 @@ QPolygon SimulatedUIWidget::polyArc(int ctrX, int ctrY, int radius, int startAng
return polygon;
}
void SimulatedUIWidget::timedUpdate(unsigned loop)
void SimulatedUIWidget::updateUi()
{
//static quint32 loop = 0;
if (m_lcd->isVisible()) {
bool lightEnable;
if (m_simulator->lcdChanged(lightEnable)) {
@@ -126,7 +127,7 @@ void SimulatedUIWidget::saveScreenshot(int idx)
m_simuDialog->traceCallback("SIMULATOR ERROR - Cannot open screenshot folder, check your settings.\n");
return;
}
fileName += QString("%1screenshot_%2.png").arg(dir.absolutePath(), QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss"));
fileName += QString("%1/screenshot_%2.png").arg(dir.absolutePath(), QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss"));
}
m_lcd->makeScreenshot(fileName);
}
+1 -1
View File
@@ -62,7 +62,7 @@ class SimulatedUIWidget : public QWidget
public slots:
void timedUpdate(unsigned loop);
void updateUi();
void saveScreenshot(int idx = -1);
void captureScreenshot() { saveScreenshot(); }
void simulatorWheelEvent(qint8 steps);
+173 -85
View File
@@ -75,7 +75,8 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator
startupFromFile(false),
deleteTempRadioData(false),
saveTempRadioData(false),
middleButtonPressed(false)
middleButtonPressed(false),
firstShow(true)
{
setWindowFlags(Qt::Window);
@@ -83,6 +84,10 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator
traceCallbackInstance = this;
simulator->installTraceHook(traceCb);
#ifdef JOYSTICKS
joystick = NULL;
#endif
// defaults
setRadioProfileId(radioProfileId);
setSdPath(g.profile[radioProfileId].sdPath());
@@ -93,8 +98,26 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator
SimulatorDialog::~SimulatorDialog()
{
traceCallbackInstance = 0;
delete timer;
delete simulator;
if (timer) {
timer->stop();
timer->deleteLater();
}
if (radioUiWidget)
radioUiWidget->deleteLater();
if (vJoyLeft)
vJoyLeft->deleteLater();
if (vJoyRight)
vJoyRight->deleteLater();
#ifdef JOYSTICKS
if (joystick)
joystick->deleteLater();
#endif
firmware = NULL; // Not sure we should delete this but at least release our pointer.
// NOTE : <simulator> should be deleted (or not) in the parent process which gave it to us in the first place.
delete ui;
}
@@ -280,7 +303,7 @@ bool SimulatorDialog::saveTempData()
QString error;
QString file = g.profile[radioProfileId].simulatorOptions().dataFile;
if (!saveTempRadioData || radioDataPath.isEmpty() || file.isEmpty())
if (radioDataPath.isEmpty() || file.isEmpty())
return ret;
RadioData radioData;
@@ -312,6 +335,14 @@ void SimulatorDialog::deleteTempData()
}
}
void SimulatorDialog::saveState()
{
SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions();
opts.windowGeometry = saveGeometry();
opts.controlsState = saveRadioWidgetsState();
g.profile[radioProfileId].simulatorOptions(opts);
}
void SimulatorDialog::setUiAreaStyle(const QString & style)
{
ui->radioUiTab->setStyleSheet(style);
@@ -339,7 +370,6 @@ void SimulatorDialog::start()
setupOutputsDisplay();
setupGVarsDisplay();
restoreRadioWidgetsState();
setTrims();
if (startupData.isEmpty())
simulator->start((const char *)0);
@@ -348,9 +378,23 @@ void SimulatorDialog::start()
else
simulator->start(startupData, (flags & SIMULATOR_FLAGS_NOTX) ? false : true);
setTrims();
getValues();
setupTimer();
startupData.clear(); // this is safe because simulator->start() makes copy of data/discards the file name
}
void SimulatorDialog::stop()
{
timer->stop();
simulator->stop();
}
void SimulatorDialog::restart()
{
stop();
saveState();
setStartupData(startupData, startupFromFile);
start();
}
/*
@@ -412,31 +456,28 @@ void SimulatorDialog::setupUi()
connect(ui->btn_debugConsole, SIGNAL(released()), this, SLOT(openDebugOutput()));
connect(ui->btn_luaReload, SIGNAL(released()), this, SLOT(luaReload()));
connect(ui->btn_screenshot, SIGNAL(released()), radioUiWidget, SLOT(captureScreenshot()));
// Hide some main UI buttons based on board capabilities, and add keymap help texts.
keymapHelp.append(keymapHelp_t(ui->btn_help->shortcut().toString(QKeySequence::NativeText), ui->btn_help->statusTip()));
connect(ui->btn_reloadSimu, SIGNAL(released()), this, SLOT(restart()));
#ifdef JOYSTICKS
keymapHelp.append(keymapHelp_t(ui->btn_joystickDialog->shortcut().toString(QKeySequence::NativeText), ui->btn_joystickDialog->statusTip()));
bool showJoystick = true;
#else
ui->btn_joystickDialog->hide();
bool showJoystick = false;
#endif
if (firmware->getCapability(Capability(SportTelemetry)))
keymapHelp.append(keymapHelp_t(ui->btn_telemSim->shortcut().toString(QKeySequence::NativeText), ui->btn_telemSim->statusTip()));
else
ui->btn_telemSim->hide();
keymapHelp.append(keymapHelp_t(ui->btn_trainerSim->shortcut().toString(QKeySequence::NativeText), ui->btn_trainerSim->statusTip()));
keymapHelp.append(keymapHelp_t(ui->btn_debugConsole->shortcut().toString(QKeySequence::NativeText), ui->btn_debugConsole->statusTip()));
if (firmware->getCapability(Capability(LuaInputsPerScript))) // hackish! but using "LuaScripts" checks for id "lua" in fw.
keymapHelp.append(keymapHelp_t(ui->btn_luaReload->shortcut().toString(QKeySequence::NativeText), ui->btn_luaReload->statusTip()));
else
ui->btn_luaReload->hide();
keymapHelp.append(keymapHelp_t(ui->btn_screenshot->shortcut().toString(QKeySequence::NativeText), ui->btn_screenshot->statusTip()));
// Hide some main UI buttons based on board capabilities, and add keymap help texts.
QString role;
foreach (QPushButton * btn, ui->buttonBox->findChildren<QPushButton *>()) {
role = btn->property("role").toString();
if ((role == "joystick" && !showJoystick) ||
(role == "telemetry" && !firmware->getCapability(Capability(SportTelemetry))) ||
(role == "reloadLua" && !firmware->getCapability(Capability(LuaInputsPerScript))) )
{
btn->hide();
continue;
}
if (!btn->shortcut().isEmpty())
keymapHelp.append(keymapHelp_t(btn->shortcut().toString(QKeySequence::NativeText), btn->statusTip()));
}
}
@@ -452,6 +493,7 @@ void SimulatorDialog::setupRadioWidgets()
ui->radioWidgetsHTLayout->removeWidget(w);
w->deleteLater();
}
switches.clear();
}
if (analogs.size()) {
foreach (RadioWidget * w, analogs) {
@@ -461,6 +503,7 @@ void SimulatorDialog::setupRadioWidgets()
ui->VCGridLayout->removeWidget(w);
w->deleteLater();
}
analogs.clear();
}
// Now set up new widgets.
@@ -535,7 +578,22 @@ void SimulatorDialog::setupRadioWidgets()
void SimulatorDialog::setupOutputsDisplay()
{
// setup Outputs tab
QWidget * outputsWidget = new QWidget();
QWidget * outputsWidget;
// delete old widget if already exists
if (ui->tabWidget->count() > 1 && ui->tabWidget->widget(1)->objectName() == "RadioOutputsWidget") {
outputsWidget = ui->tabWidget->widget(1);
ui->tabWidget->removeTab(1);
outputsWidget->deleteLater();
outputsWidget = NULL;
}
channelValues.clear();
channelSliders.clear();
logicalSwitchLabels.clear();
outputsWidget = new QWidget();
outputsWidget->setObjectName("RadioOutputsWidget");
QGridLayout * gridLayout = new QGridLayout(outputsWidget);
gridLayout->setHorizontalSpacing(0);
gridLayout->setVerticalSpacing(3);
@@ -609,39 +667,54 @@ void SimulatorDialog::setupOutputsDisplay()
void SimulatorDialog::setupGVarsDisplay()
{
int fmodes = firmware->getCapability(FlightModes);
int gvars = firmware->getCapability(Gvars);
if (gvars>0) {
// setup GVars tab
QWidget * gvarsWidget = new QWidget();
QGridLayout * gvarsLayout = new QGridLayout(gvarsWidget);
ui->tabWidget->addTab(gvarsWidget, QString(tr("GVars")));
// setup GVars tab
for (int fm=0; fm<fmodes; fm++) {
QLabel * label = new QLabel(gvarsWidget);
label->setText(QString("FM%1").arg(fm));
label->setAlignment(Qt::AlignCenter);
gvarsLayout->addWidget(label, 0, fm+1);
int gvars = firmware->getCapability(Gvars);
int fmodes = firmware->getCapability(FlightModes);
QWidget * gvarsWidget;
// delete old widget if already exists
if (ui->tabWidget->count() > 2 && ui->tabWidget->widget(2)->objectName() == "RadioGVOutputsWidget") {
gvarsWidget = ui->tabWidget->widget(2);
ui->tabWidget->removeTab(2);
gvarsWidget->deleteLater();
gvarsWidget = NULL;
}
gvarValues.clear();
if (!gvars)
return;
gvarsWidget = new QWidget();
gvarsWidget->setObjectName("RadioGVOutputsWidget");
QGridLayout * gvarsLayout = new QGridLayout(gvarsWidget);
ui->tabWidget->insertTab(2, gvarsWidget, QString(tr("GVars")));
for (int fm=0; fm<fmodes; fm++) {
QLabel * label = new QLabel(gvarsWidget);
label->setText(QString("FM%1").arg(fm));
label->setAlignment(Qt::AlignCenter);
gvarsLayout->addWidget(label, 0, fm+1);
}
for (int i=0; i<gvars; i++) {
QLabel * label = new QLabel(gvarsWidget);
label->setText(QString("GV%1").arg(i+1));
label->setAutoFillBackground(true);
if ((i % 2) ==0 ) {
label->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }");
}
for (int i=0; i<gvars; i++) {
QLabel * label = new QLabel(gvarsWidget);
label->setText(QString("GV%1").arg(i+1));
label->setAutoFillBackground(true);
gvarsLayout->addWidget(label, i+1, 0);
for (int fm=0; fm<fmodes; fm++) {
QLabel * value = new QLabel(gvarsWidget);
value->setAutoFillBackground(true);
value->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
if ((i % 2) ==0 ) {
label->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }");
}
gvarsLayout->addWidget(label, i+1, 0);
for (int fm=0; fm<fmodes; fm++) {
QLabel * value = new QLabel(gvarsWidget);
value->setAutoFillBackground(true);
value->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
if ((i % 2) ==0 ) {
value->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }");
}
value->setText("0");
gvarValues << value;
gvarsLayout->addWidget(value, i+1, fm+1);
value->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }");
}
value->setText("0");
gvarValues << value;
gvarsLayout->addWidget(value, i+1, fm+1);
}
}
}
@@ -689,7 +762,12 @@ void SimulatorDialog::setupJoysticks()
QMessageBox::critical(this, tr("Warning"), tr("Joystick enabled but not configured correctly"));
return;
}
joystick = new Joystick(this);
if (!joystick)
joystick = new Joystick(this);
else
joystick->close();
if (joystick && joystick->open(g.jsCtrl())) {
int numAxes = std::min(joystick->numAxes, MAX_JOYSTICKS);
for (int j=0; j<numAxes; j++) {
@@ -720,8 +798,17 @@ void SimulatorDialog::setupJoysticks()
void SimulatorDialog::setupTimer()
{
if (timer) {
timer->stop();
disconnect(timer, 0, this, 0);
disconnect(timer, 0, radioUiWidget, 0);
timer->deleteLater();
timer = NULL;
}
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent()));
connect(timer, SIGNAL(timeout()), radioUiWidget, SLOT(updateUi()));
timer->start(10);
}
@@ -774,48 +861,51 @@ void SimulatorDialog::setValues()
{
static const int numGvars = firmware->getCapability(Gvars);
static const int numFlightModes = firmware->getCapability(FlightModes);
static QString phase_name;
static TxOutputs prevOutputs;
static int prevPhase = -1;
int currentPhase;
TxOutputs outputs;
simulator->getValues(outputs);
for (int i = 0; i < channelSliders.size() && i < CPN_MAX_CHNOUT; i++) {
channelSliders[i]->setValue(qMin(1024, qMax(-1024, outputs.chans[i])));
channelValues[i]->setText(QString("%1").arg((qreal)outputs.chans[i]*100/1024, 0, 'f', 1));
}
int currentPhase = simulator->getPhase();
for (int i = 0; i < logicalSwitchLabels.size() && i < CPN_MAX_CSW; i++) {
if (prevOutputs.vsw[i] != outputs.vsw[i]) {
// setStyleSheet() is very CPU time consuming, doing it only on the actual
// switch state change brings down CPU usage time (for Taranis simulation) from 40% to 7% (Linux, one core usage)
logicalSwitchLabels[i]->setStyleSheet(outputs.vsw[i] ? CSWITCH_ON : CSWITCH_OFF);
// Outputs tab visible?
if (ui->tabWidget->currentIndex() == 1) {
for (int i = 0; i < channelSliders.size() && i < CPN_MAX_CHNOUT; i++) {
channelSliders[i]->setValue(qMin(1024, qMax(-1024, outputs.chans[i])));
channelValues[i]->setText(QString("%1").arg((qreal)outputs.chans[i]*100/1024, 0, 'f', 1));
}
for (int i = 0; i < logicalSwitchLabels.size() && i < CPN_MAX_CSW; i++) {
// setStyleSheet() is very CPU time consuming, do it only if the actual switch state changes
if (prevOutputs.vsw[i] != outputs.vsw[i]) {
logicalSwitchLabels[i]->setStyleSheet(outputs.vsw[i] ? CSWITCH_ON : CSWITCH_OFF);
prevOutputs.vsw[i] = outputs.vsw[i];
}
}
}
for (int gv = 0; gv < numGvars; gv++) {
for (int fm = 0; fm < numFlightModes; fm++) {
if (prevPhase != lastPhase || prevOutputs.gvars[fm][gv] != outputs.gvars[fm][gv]) {
// GVars tab visible?
if (ui->tabWidget->currentIndex() == 2) {
for (int gv = 0; gv < numGvars; gv++) {
for (int fm = 0; fm < numFlightModes; fm++) {
// same trick for GVARS, but this has far less effect on CPU usage as setStyleSheet()
gvarValues[gv*numFlightModes+fm]->setText(QString((fm == lastPhase) ? "<b>%1</b>" : "%1").arg(outputs.gvars[fm][gv]));
if (currentPhase != lastPhase || prevOutputs.gvars[fm][gv] != outputs.gvars[fm][gv]) {
gvarValues[gv*numFlightModes+fm]->setText(QString((fm == lastPhase) ? "<b>%1</b>" : "%1").arg(outputs.gvars[fm][gv]));
prevOutputs.gvars[fm][gv] = outputs.gvars[fm][gv];
}
}
}
}
// display current flight mode in window title
currentPhase = simulator->getPhase();
if (currentPhase != lastPhase) {
lastPhase = currentPhase;
phase_name = QString(simulator->getPhaseName(currentPhase));
QString phase_name = QString(simulator->getPhaseName(currentPhase));
if (phase_name.isEmpty())
phase_name = QString::number(currentPhase);
setWindowTitle(windowName + QString(" - Flight Mode %1").arg(phase_name));
}
prevOutputs = outputs;
prevPhase = lastPhase;
}
// "get" values from this UI and send them to the firmware simulator.
@@ -889,12 +979,8 @@ void SimulatorDialog::setTrims()
void SimulatorDialog::closeEvent(QCloseEvent *)
{
simulator->stop();
timer->stop();
SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions();
opts.windowGeometry = saveGeometry();
opts.controlsState = saveRadioWidgetsState();
g.profile[radioProfileId].simulatorOptions(opts);
stop();
saveState();
if (saveTempRadioData)
saveTempData();
if (deleteTempRadioData)
@@ -903,7 +989,6 @@ void SimulatorDialog::closeEvent(QCloseEvent *)
void SimulatorDialog::showEvent(QShowEvent *)
{
static bool firstShow = true;
if (firstShow) {
restoreGeometry(g.profile[radioProfileId].simulatorOptions().windowGeometry);
@@ -956,7 +1041,7 @@ void SimulatorDialog::onTimerEvent()
setTrims();
centerSticks();
}
radioUiWidget->timedUpdate(lcd_counter);
updateDebugOutput();
}
@@ -989,6 +1074,7 @@ void SimulatorDialog::openTelemetrySimulator()
// allow only one instance
if (TelemetrySimu == 0) {
TelemetrySimu = new TelemetrySimulator(this, simulator);
TelemetrySimu->setAttribute(Qt::WA_DeleteOnClose);
TelemetrySimu->show();
connect(TelemetrySimu, &TelemetrySimulator::destroyed, [this](QObject *) {
this->TelemetrySimu = NULL;
@@ -1004,6 +1090,7 @@ void SimulatorDialog::openTrainerSimulator()
// allow only one instance
if (TrainerSimu == 0) {
TrainerSimu = new TrainerSimulator(this, simulator);
TrainerSimu->setAttribute(Qt::WA_DeleteOnClose);
TrainerSimu->show();
connect(TrainerSimu, &TrainerSimulator::destroyed, [this](QObject *) {
this->TrainerSimu = NULL;
@@ -1030,6 +1117,7 @@ void SimulatorDialog::openDebugOutput()
if (!DebugOut) {
DebugOut = new DebugOutput(this);
DebugOut->traceCallback(traceBuffer);
DebugOut->setAttribute(Qt::WA_DeleteOnClose);
DebugOut->show();
connect(DebugOut, &DebugOutput::destroyed, [this](QObject *) {
this->DebugOut = NULL;
+7 -1
View File
@@ -87,13 +87,18 @@ class SimulatorDialog : public QDialog
bool useTempDataPath(bool deleteOnClose = true, bool saveOnClose = false);
bool saveTempData();
void deleteTempData();
void saveState();
void setUiAreaStyle(const QString & style);
void traceCallback(const char * text);
void start();
QString getSdPath() const { return sdCardPath; }
QString getDataPath() const { return radioDataPath; }
public slots:
void start();
void stop();
void restart();
private:
void setupUi();
void setupRadioWidgets();
@@ -149,6 +154,7 @@ class SimulatorDialog : public QDialog
bool deleteTempRadioData;
bool saveTempRadioData;
bool middleButtonPressed;
bool firstShow;
#ifdef JOYSTICKS
Joystick *joystick;
+181 -121
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>686</width>
<height>557</height>
<width>651</width>
<height>528</height>
</rect>
</property>
<property name="sizePolicy">
@@ -232,125 +232,6 @@
</layout>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<item>
<widget class="QPushButton" name="btn_help">
<property name="toolTip">
<string>Show Help/Keymap (F1)</string>
</property>
<property name="statusTip">
<string>Show Help/Keymap</string>
</property>
<property name="text">
<string>Keymap Help</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_joystickDialog">
<property name="toolTip">
<string>Open the Joystick Settings window (F3)</string>
</property>
<property name="statusTip">
<string>Joystick Configuration</string>
</property>
<property name="text">
<string>Joystick Config.</string>
</property>
<property name="shortcut">
<string>F3</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_telemSim">
<property name="toolTip">
<string>Open Telemetry Simulator window (F4)</string>
</property>
<property name="statusTip">
<string>Telemetry Simulator</string>
</property>
<property name="text">
<string>Telemetry Sim.</string>
</property>
<property name="shortcut">
<string>F4</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_trainerSim">
<property name="toolTip">
<string>Open Trainer Simulator window (F5)</string>
</property>
<property name="statusTip">
<string>Trainer Simulator</string>
</property>
<property name="text">
<string>Trainer Sim.</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_debugConsole">
<property name="toolTip">
<string>Open Debug Console window (F6)</string>
</property>
<property name="statusTip">
<string>Debug Console</string>
</property>
<property name="text">
<string>Debug Console</string>
</property>
<property name="shortcut">
<string>F6</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_luaReload">
<property name="toolTip">
<string>Reload Lua Scripts (F7)</string>
</property>
<property name="statusTip">
<string>Reload Lua Scripts</string>
</property>
<property name="text">
<string>Lua Reload</string>
</property>
<property name="shortcut">
<string>F7</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_screenshot">
<property name="toolTip">
<string>Save a screenshot of the radio LCD screen (F8)</string>
</property>
<property name="statusTip">
<string>LCD Screenshot</string>
</property>
<property name="text">
<string>LCD Screenshot</string>
</property>
<property name="shortcut">
<string>F8</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="enabled">
@@ -414,6 +295,185 @@ padding: 0;</string>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QWidget" name="buttonBox" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="buttonsLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QPushButton" name="btn_help">
<property name="toolTip">
<string>Show Help/Keymap (F1)</string>
</property>
<property name="statusTip">
<string>Show Help/Keymap</string>
</property>
<property name="text">
<string>Keymap</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
<property name="role" stdset="0">
<string>help</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_joystickDialog">
<property name="toolTip">
<string>Open the Joystick Settings window (F3)</string>
</property>
<property name="statusTip">
<string>Joystick Configuration</string>
</property>
<property name="text">
<string>Joystick Config.</string>
</property>
<property name="shortcut">
<string>F3</string>
</property>
<property name="role" stdset="0">
<string>joystick</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_telemSim">
<property name="toolTip">
<string>Open Telemetry Simulator window (F4)</string>
</property>
<property name="statusTip">
<string>Telemetry Simulator</string>
</property>
<property name="text">
<string>Telemetry Sim.</string>
</property>
<property name="shortcut">
<string>F4</string>
</property>
<property name="role" stdset="0">
<string>telemetry</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_trainerSim">
<property name="toolTip">
<string>Open Trainer Simulator window (F5)</string>
</property>
<property name="statusTip">
<string>Trainer Simulator</string>
</property>
<property name="text">
<string>Trainer Sim.</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
<property name="role" stdset="0">
<string>trainer</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_debugConsole">
<property name="toolTip">
<string>Open Debug Console window (F6)</string>
</property>
<property name="statusTip">
<string>Debug Console</string>
</property>
<property name="text">
<string>Debug Console</string>
</property>
<property name="shortcut">
<string>F6</string>
</property>
<property name="role" stdset="0">
<string>debug</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_luaReload">
<property name="toolTip">
<string>Reload Lua Scripts (F7)</string>
</property>
<property name="statusTip">
<string>Reload Lua Scripts</string>
</property>
<property name="text">
<string>Reload Lua</string>
</property>
<property name="shortcut">
<string>F7</string>
</property>
<property name="role" stdset="0">
<string>reloadLua</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_screenshot">
<property name="toolTip">
<string>Save a screenshot of the radio LCD screen (F8)</string>
</property>
<property name="statusTip">
<string>LCD Screenshot</string>
</property>
<property name="text">
<string>Screenshot</string>
</property>
<property name="shortcut">
<string>F8</string>
</property>
<property name="role" stdset="0">
<string>lcdScreenshot</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_reloadSimu">
<property name="toolTip">
<string>Reload all radio data (F9)</string>
</property>
<property name="statusTip">
<string>Reload Radio Data</string>
</property>
<property name="text">
<string>Reload Radio</string>
</property>
<property name="shortcut">
<string>F9</string>
</property>
<property name="role" stdset="0">
<string>reloadradio</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
@@ -311,8 +311,6 @@ The default is configured in the chosen Radio Profile.</string>
</widget>
</item>
</layout>
<zorder>groupBox</zorder>
<zorder>buttonBox</zorder>
</widget>
<resources>
<include location="../companion.qrc"/>
+57 -51
View File
@@ -72,9 +72,7 @@ class LcdWidget : public QWidget
void setBackgroundColor(QColor color)
{
_r = color.red();
_g = color.green();
_b = color.blue();
bgColor = color;
}
void makeScreenshot(const QString & fileName)
@@ -121,79 +119,87 @@ class LcdWidget : public QWidget
unsigned char *previousBuf;
bool lightEnable;
int _r, _g, _b;
QColor bgColor;
QColor bgDefaultColor;
inline void doPaint(QPainter & p)
{
QRgb rgb;
uint16_t z;
if (!lcdBuf)
return;
if (lcdDepth == 16) {
for (int x=0; x<lcdWidth; x++) {
for (int y=0; y<lcdHeight; y++) {
uint16_t z = ((uint16_t *)lcdBuf)[y * lcdWidth + x];
rgb = qRgb(255*((z&0xF800)>>11)/0x1F, 255*((z&0x07E0)>>5)/0x3F, 255*(z&0x001F)/0x1F);
for (int x = 0; x < lcdWidth; x++) {
for (int y = 0; y < lcdHeight; y++) {
z = ((uint16_t *)lcdBuf)[y * lcdWidth + x];
rgb = qRgb(255 * ((z & 0xF800) >> 11) / 0x1F,
255 * ((z & 0x07E0) >> 5) / 0x3F,
255 * (z & 0x001F) / 0x1F);
p.setPen(rgb);
p.drawPoint(x, y);
}
}
return;
}
else if (lcdDepth == 12) {
for (int x=0; x<lcdWidth; x++) {
for (int y=0; y<lcdHeight; y++) {
uint16_t z = ((uint16_t *)lcdBuf)[y * lcdWidth + x];
rgb = qRgb(255*((z&0xF00)>>8)/0x0f, 255*((z&0x0F0)>>4)/0x0f, 255*(z&0x00F)/0x0f);
if (lcdDepth == 12) {
for (int x = 0; x < lcdWidth; x++) {
for (int y = 0; y < lcdHeight; y++) {
z = ((uint16_t *)lcdBuf)[y * lcdWidth + x];
rgb = qRgb(255 * ((z & 0xF00) >> 8) / 0x0F,
255 * ((z & 0x0F0) >> 4) / 0x0F,
255 * (z & 0x00F) / 0x0F);
p.setPen(rgb);
p.drawPoint(x, y);
}
}
return;
}
else {
if (lightEnable)
rgb = qRgb(_r, _g, _b);
else
rgb = bgDefaultColor.rgba();
p.setBackground(QBrush(rgb));
p.eraseRect(0, 0, 2*lcdWidth, 2*lcdHeight);
QColor bg;
if (lightEnable)
bg = bgColor;
else
bg = bgDefaultColor;
if (lcdBuf) {
p.setBackground(QBrush(bg));
p.eraseRect(0, 0, 2*lcdWidth, 2*lcdHeight);
if (lcdDepth == 1) {
rgb = qRgb(0, 0, 0);
p.setPen(rgb);
p.setBrush(QBrush(rgb));
}
uint16_t idx, mask;
uint16_t previousDepth = 0xFF;
for (int y = 0; y < lcdHeight; y++) {
idx = (y * lcdDepth / 8) * lcdWidth;
mask = (1 << (y % 8));
for (int x = 0; x < lcdWidth; x++, idx++) {
if (lcdDepth == 1) {
rgb = qRgb(0, 0, 0);
if (lcdBuf[idx] & mask)
p.drawRect(2 * x, 2 * y, 1, 1);
continue;
}
// lcdDepth == 4
z = (y & 1) ? (lcdBuf[idx] >> 4) : (lcdBuf[idx] & 0x0F);
if (!z)
continue;
if (z != previousDepth) {
previousDepth = z;
rgb = qRgb(bg.red() - (z * bg.red()) / 15,
bg.green() - (z * bg.green()) / 15,
bg.blue() - (z * bg.blue()) / 15);
p.setPen(rgb);
p.setBrush(QBrush(rgb));
}
unsigned int previousDepth = 0xFF;
for (int y=0; y<lcdHeight; y++) {
unsigned int idx = (y*lcdDepth/8)*lcdWidth;
unsigned int mask = (1 << (y%8));
for (int x=0; x<lcdWidth; x++, idx++) {
if (lcdDepth == 1) {
if (lcdBuf[idx] & mask) {
p.drawRect(2*x, 2*y, 1, 1);
}
}
else {
unsigned int z = (y & 1) ? (lcdBuf[idx] >> 4) : (lcdBuf[idx] & 0x0F);
if (z) {
if (z != previousDepth) {
previousDepth = z;
if (lightEnable)
rgb = qRgb(_r-(z*_r)/15, _g-(z*_g)/15, _b-(z*_b)/15);
else
rgb = qRgb(161-(z*161)/15, 161-(z*161)/15, 161-(z*161)/15);
p.setPen(rgb);
p.setBrush(QBrush(rgb));
}
p.drawRect(2*x, 2*y, 1, 1);
}
}
}
}
p.drawRect(2*x, 2*y, 1, 1);
}
}
}
void paintEvent(QPaintEvent*)
@@ -68,29 +68,27 @@ class RadioKnobWidget : public RadioWidget
class KnobWidget : public QDial
{
friend class RadioKnobWidget;
public:
explicit KnobWidget(Board::PotType type, QWidget * parent = 0):
QDial(parent),
m_type(type),
m_positions(type == Board::POT_MULTIPOS_SWITCH ? 5 : 0)
QDial(parent)
{
setToolTip(tr("Right-double-click to reset to center."));
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setFixedSize(QSize(42, 42));
setMinimum(-1024);
setMaximum(1024);
setNotchesVisible(true);
if (m_positions) {
if (type == Board::POT_MULTIPOS_SWITCH) {
m_stepSize = 2048 / 5;
// this is a bit of a hack to get the notch markers to display correctly
// the actual notches/value are constrained in setValue()
setSingleStep(2048 / m_positions / 10);
setPageStep(2048 / m_positions);
setSingleStep(m_stepSize / 10);
setPageStep(m_stepSize);
setNotchTarget(5.7);
}
else {
m_stepSize = 1;
setToolTip(tr("Right-double-click to reset to center."));
setPageStep(128);
setNotchTarget(64);
}
@@ -98,15 +96,15 @@ class RadioKnobWidget : public RadioWidget
void setValue(int value)
{
if (m_positions) {
value = ((int)roundf((value + 1024) / (2048.0f / m_positions)) * (2048.0f / m_positions) - 1024);
if (m_stepSize > 1) {
value = (value + 1024) / m_stepSize * m_stepSize - 1024;
}
QDial::setValue(value);
}
void mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) {
if (m_stepSize == 1 && event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) {
setValue(0);
event->accept();
return;
@@ -114,8 +112,21 @@ class RadioKnobWidget : public RadioWidget
QDial::mousePressEvent(event);
}
Board::PotType m_type;
quint8 m_positions;
void wheelEvent(QWheelEvent * event)
{
if (event->angleDelta().isNull())
return;
if (m_stepSize > 1) {
int numSteps = event->angleDelta().y() / 8 / 15 * m_stepSize; // one step per 15deg
setValue(value() + numSteps);
event->accept();
return;
}
QDial::wheelEvent(event);
}
quint16 m_stepSize;
};
};
@@ -25,7 +25,8 @@
#include "boards.h"
#include <QSlider>
#include <QDebug>
#include <QTimer>
#include <QToolButton>
class RadioSwitchWidget : public RadioWidget
{
@@ -50,27 +51,88 @@ class RadioSwitchWidget : public RadioWidget
{
m_type = RADIO_WIDGET_SWITCH;
QSlider * sl = new QSlider();
sl->setOrientation(Qt::Vertical);
sl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
sl->setFixedHeight(50);
sl->setInvertedAppearance(true);
sl->setInvertedControls(true);
sl->setTickPosition(QSlider::TicksBothSides);
sl->setPageStep(1);
sl->setMinimum((swType == Board::SWITCH_3POS ? -1 : 0));
sl->setMaximum(1);
sl->setTickInterval(1);
sl->setValue(m_value);
m_slider = new QSlider();
m_slider->setOrientation(Qt::Vertical);
m_slider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
m_slider->setInvertedAppearance(true);
m_slider->setInvertedControls(true);
m_slider->setTickPosition(QSlider::TicksBothSides);
m_slider->setPageStep(1);
m_slider->setMinimum((swType == Board::SWITCH_3POS ? -1 : 0));
m_slider->setMaximum(1);
m_slider->setTickInterval(1);
m_slider->setValue(m_value);
connect(sl, &QSlider::valueChanged, this, &RadioWidget::setValue);
connect(this, &RadioWidget::valueChanged, sl, &QSlider::setValue);
if (swType == Board::SWITCH_TOGGLE) {
QIcon icon;
icon.addFile(QStringLiteral(":/images/simulator/icons/8/lock-locked.png"), QSize(8, 8), QIcon::Normal, QIcon::On);
icon.addFile(QStringLiteral(":/images/simulator/icons/8/lock-unlocked.png"), QSize(8, 8), QIcon::Normal, QIcon::Off);
QToolButton * lockBtn = new QToolButton(this);
lockBtn->setIcon(icon);
lockBtn->setIconSize(QSize(8, 8));
lockBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
lockBtn->setToolButtonStyle(Qt::ToolButtonIconOnly);
lockBtn->setAutoRaise(true);
lockBtn->setCheckable(true);
lockBtn->setToolTip(tr("Latch/unlatch the momentary switch."));
setWidget(sl);
QWidget * container = new QWidget(this);
container->setFixedHeight(50);
container->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QVBoxLayout * cl = new QVBoxLayout(container);
cl->setContentsMargins(0, 0, 0, 0);
cl->setSpacing(1);
cl->addWidget(m_slider, 1, Qt::AlignHCenter);
cl->addWidget(lockBtn, 0, Qt::AlignHCenter);
setWidget(container);
connect(lockBtn, &QToolButton::toggled, this, &RadioSwitchWidget::setToggleLocked);
connect(m_slider, &QSlider::sliderReleased, this, &RadioSwitchWidget::onMomentaryReleased);
connect(m_slider, &QSlider::valueChanged, this, &RadioSwitchWidget::onMomentaryReleased);
connect(this, &RadioWidget::flagsChanged, lockBtn, &QToolButton::setChecked);
}
else {
m_slider->setFixedHeight(50);
setWidget(m_slider);
}
connect(m_slider, &QSlider::valueChanged, this, &RadioWidget::setValue);
connect(this, &RadioWidget::valueChanged, m_slider, &QSlider::setValue);
}
void setToggleLocked(bool lock)
{
if (m_flags != lock) {
m_flags = lock;
setValue((int)lock);
emit flagsChanged(m_flags);
}
}
void resetToggle()
{
setValue(0);
}
private slots:
void onMomentaryReleased()
{
if (!m_flags && !m_slider->isSliderDown())
QTimer::singleShot(500, this, SLOT(onMomentaryTimeout()));
}
void onMomentaryTimeout()
{
if (!m_flags)
resetToggle();
}
private:
Board::SwitchType swType;
QSlider * m_slider;
};
@@ -134,7 +134,7 @@ QByteArray RadioWidget::getStateData() const
RadioWidget::RadioWidgetState RadioWidget::getState() const
{
return RadioWidgetState(quint8(m_type), qint8(m_index), qint16(m_value));;
return RadioWidgetState(quint8(m_type), qint8(m_index), qint16(m_value), quint16(m_flags));;
}
void RadioWidget::init()
+7 -1
View File
@@ -261,6 +261,11 @@ int main(int argc, char *argv[])
showMessage(QObject::tr("ERROR: Simulator %1 not found").arg(simOptions.firmwareId), QMessageBox::Critical);
return finish(2);
}
SimulatorInterface * simulator = factory->create();
if (!simulator) {
showMessage(QObject::tr("ERROR: Failed to create simulator interface, possibly missing or bad library."), QMessageBox::Critical);
return finish(2);
}
current_firmware_variant = getFirmware(simOptions.firmwareId);
@@ -268,7 +273,7 @@ int main(int argc, char *argv[])
g.id(profileId);
g.simuLastProfId(profileId);
SimulatorDialog * dialog = new SimulatorDialog(NULL, factory->create(), SIMULATOR_FLAGS_STANDALONE);
SimulatorDialog * dialog = new SimulatorDialog(NULL, simulator, SIMULATOR_FLAGS_STANDALONE);
dialog->setRadioProfileId(profileId);
dialog->setOptions(simOptions, true);
dialog->start();
@@ -277,6 +282,7 @@ int main(int argc, char *argv[])
g.id(oldProfId);
dialog->deleteLater();
delete simulator;
return finish(result);
}