From de68db0905a0b5ed04511793b0ee0660a7b04bdf Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Sat, 28 Jan 2017 07:43:56 -0500 Subject: [PATCH] [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. --- .travis.yml | 4 +- companion/src/companion.qrc | 4 +- .../images/simulator/icons/8/lock-locked.png | Bin 0 -> 152 bytes .../simulator/icons/8/lock-unlocked.png | Bin 0 -> 154 bytes .../src/images/simulator/icons/credits.txt | 1 + .../src/simulation/simulateduiwidget.cpp | 5 +- companion/src/simulation/simulateduiwidget.h | 2 +- companion/src/simulation/simulatordialog.cpp | 258 ++++++++++----- companion/src/simulation/simulatordialog.h | 8 +- companion/src/simulation/simulatordialog.ui | 302 +++++++++++------- .../src/simulation/simulatorstartupdialog.ui | 2 - companion/src/simulation/widgets/lcdwidget.h | 108 ++++--- .../src/simulation/widgets/radioknobwidget.h | 39 ++- .../simulation/widgets/radioswitchwidget.h | 94 +++++- .../src/simulation/widgets/radiowidget.cpp | 2 +- companion/src/simulator.cpp | 8 +- 16 files changed, 539 insertions(+), 298 deletions(-) create mode 100644 companion/src/images/simulator/icons/8/lock-locked.png create mode 100644 companion/src/images/simulator/icons/8/lock-unlocked.png create mode 100644 companion/src/images/simulator/icons/credits.txt diff --git a/.travis.yml b/.travis.yml index e947b1240..1d9fd6014 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index e9eb212ce..79e040620 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -11,7 +11,9 @@ images/maps.png images/track.png images/track0.png - images/taranison.png + images/taranison.png + images/simulator/icons/8/lock-locked.png + images/simulator/icons/8/lock-unlocked.png images/simulator/Horus/middle.png images/simulator/Horus/left.png images/simulator/Horus/right.png diff --git a/companion/src/images/simulator/icons/8/lock-locked.png b/companion/src/images/simulator/icons/8/lock-locked.png new file mode 100644 index 0000000000000000000000000000000000000000..fef8198a36b42c41d05ce65c4106a57a0194549e GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ za0`PlBg3pY54nJ za0`PlBg3pY5isVisible()) { 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); } diff --git a/companion/src/simulation/simulateduiwidget.h b/companion/src/simulation/simulateduiwidget.h index 243784709..0415acbd1 100644 --- a/companion/src/simulation/simulateduiwidget.h +++ b/companion/src/simulation/simulateduiwidget.h @@ -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); diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index be19a248b..14379dae8 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -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 : 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()) { + 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; fmsetText(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; fmsetText(QString("FM%1").arg(fm)); + label->setAlignment(Qt::AlignCenter); + gvarsLayout->addWidget(label, 0, fm+1); + } + for (int i=0; isetText(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; isetText(QString("GV%1").arg(i+1)); - label->setAutoFillBackground(true); + gvarsLayout->addWidget(label, i+1, 0); + for (int fm=0; fmsetAutoFillBackground(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; fmsetAutoFillBackground(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; jstop(); + 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) ? "%1" : "%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) ? "%1" : "%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; diff --git a/companion/src/simulation/simulatordialog.h b/companion/src/simulation/simulatordialog.h index 0d4bda80a..8dcd456a3 100644 --- a/companion/src/simulation/simulatordialog.h +++ b/companion/src/simulation/simulatordialog.h @@ -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; diff --git a/companion/src/simulation/simulatordialog.ui b/companion/src/simulation/simulatordialog.ui index 9b1d48e9c..62bd4c713 100644 --- a/companion/src/simulation/simulatordialog.ui +++ b/companion/src/simulation/simulatordialog.ui @@ -6,8 +6,8 @@ 0 0 - 686 - 557 + 651 + 528 @@ -232,125 +232,6 @@ - - - - 3 - - - - - Show Help/Keymap (F1) - - - Show Help/Keymap - - - Keymap Help - - - F1 - - - - - - - Open the Joystick Settings window (F3) - - - Joystick Configuration - - - Joystick Config. - - - F3 - - - - - - - Open Telemetry Simulator window (F4) - - - Telemetry Simulator - - - Telemetry Sim. - - - F4 - - - - - - - Open Trainer Simulator window (F5) - - - Trainer Simulator - - - Trainer Sim. - - - F5 - - - - - - - Open Debug Console window (F6) - - - Debug Console - - - Debug Console - - - F6 - - - - - - - Reload Lua Scripts (F7) - - - Reload Lua Scripts - - - Lua Reload - - - F7 - - - - - - - Save a screenshot of the radio LCD screen (F8) - - - LCD Screenshot - - - LCD Screenshot - - - F8 - - - - - @@ -414,6 +295,185 @@ padding: 0; + + + + + 0 + 0 + + + + + 3 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Show Help/Keymap (F1) + + + Show Help/Keymap + + + Keymap + + + F1 + + + help + + + + + + + Open the Joystick Settings window (F3) + + + Joystick Configuration + + + Joystick Config. + + + F3 + + + joystick + + + + + + + Open Telemetry Simulator window (F4) + + + Telemetry Simulator + + + Telemetry Sim. + + + F4 + + + telemetry + + + + + + + Open Trainer Simulator window (F5) + + + Trainer Simulator + + + Trainer Sim. + + + F5 + + + trainer + + + + + + + Open Debug Console window (F6) + + + Debug Console + + + Debug Console + + + F6 + + + debug + + + + + + + Reload Lua Scripts (F7) + + + Reload Lua Scripts + + + Reload Lua + + + F7 + + + reloadLua + + + + + + + Save a screenshot of the radio LCD screen (F8) + + + LCD Screenshot + + + Screenshot + + + F8 + + + lcdScreenshot + + + + + + + Reload all radio data (F9) + + + Reload Radio Data + + + Reload Radio + + + F9 + + + reloadradio + + + + + + diff --git a/companion/src/simulation/simulatorstartupdialog.ui b/companion/src/simulation/simulatorstartupdialog.ui index 0f342ebae..de6ac01c0 100644 --- a/companion/src/simulation/simulatorstartupdialog.ui +++ b/companion/src/simulation/simulatorstartupdialog.ui @@ -311,8 +311,6 @@ The default is configured in the chosen Radio Profile. - groupBox - buttonBox diff --git a/companion/src/simulation/widgets/lcdwidget.h b/companion/src/simulation/widgets/lcdwidget.h index b52b0de30..863edf7d2 100644 --- a/companion/src/simulation/widgets/lcdwidget.h +++ b/companion/src/simulation/widgets/lcdwidget.h @@ -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>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>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> 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*) diff --git a/companion/src/simulation/widgets/radioknobwidget.h b/companion/src/simulation/widgets/radioknobwidget.h index 9c27d6b38..9f0c4e19c 100644 --- a/companion/src/simulation/widgets/radioknobwidget.h +++ b/companion/src/simulation/widgets/radioknobwidget.h @@ -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; }; }; diff --git a/companion/src/simulation/widgets/radioswitchwidget.h b/companion/src/simulation/widgets/radioswitchwidget.h index 2302cb8f3..c1f3310fc 100644 --- a/companion/src/simulation/widgets/radioswitchwidget.h +++ b/companion/src/simulation/widgets/radioswitchwidget.h @@ -25,7 +25,8 @@ #include "boards.h" #include -#include +#include +#include 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; }; diff --git a/companion/src/simulation/widgets/radiowidget.cpp b/companion/src/simulation/widgets/radiowidget.cpp index 7885383df..f87522234 100644 --- a/companion/src/simulation/widgets/radiowidget.cpp +++ b/companion/src/simulation/widgets/radiowidget.cpp @@ -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() diff --git a/companion/src/simulator.cpp b/companion/src/simulator.cpp index bc9d2bc9d..44b73a05c 100644 --- a/companion/src/simulator.cpp +++ b/companion/src/simulator.cpp @@ -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); }