From 57dc0159d6b74d2a8a9a04fd7e4967489ff0550e Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Sun, 2 Apr 2017 06:17:37 -0400 Subject: [PATCH] [Simulator] Asynchronous SimulatorInterface & a few new features. (#4738) * [Simulator] Create RadioKeyWidget class for UI buttons & refactor ButtonsWidget; Refactor SimulatedUIWidget (and subtypes) to use new RadioKeyWidgets/ButtonsWidget; Centralize help text for key mappings and get creative with some icons; Simplify some radio UI setups with rectangular buttons. * [Simulator] Convert all simulator data I/O to signals/slots mechanism: * SimulatorInterface/OpenTxSimulator: - Now inherits from QObject to allow signal/slot interface; - Allows data exchange on a per-item basis (eg. each I/O value is treated separately instead of sending whole arrays or structs of data); - Checks for data changes and only emits signals when change is detected (GUI can now assume only new values are being sent); - Manages its own 10ms timer (doesn't rely on GUI to do that); - Sends "heartbeat" signals @ 1Hz for status monitoring; * Simulator GUI: - All data is exchanged between GUI elements as well as SimulatorInterface via signals/slots using standardized methods; - Data is sent immediately, and only, when actually changed (eg. a control is moved) instead of in bulk at specific time intervals; - Similarly, an asynchronous method is used for reading incoming data, w/out timers or loops; - Improve VirtualJoystickWidget to be more encapsulated and configurable; - Pause telemetry simulator if window is hidden; * [Simulator] Move SimulatorInterface instance to separate thread, ensure safe asynchronous operations & proper timer interactions; Protect/remove some functions, & reorganize the order (cosmetics). * [Simulator] Traces are now delivered to OpenTxSimulator and one or more QIODevice(s) can be added as recipient(s); Add SimulatorInterface::getCapability() for compile-time settings; Remove reversed POT1/SLIDER1 mixer exception (Taranis) requirement for SIMU; Fix plus/minus key delay on wheel event w/out encoder. * [Simulator] Add current knob/slider/trim input value in tool-tips (KnobWidget and SliderWidget). * [Simulator] Fix trims widget internal value not properly updating, and remove trim influence on virtual joystick X/Y value display (closes #4671). * [SimulatorInterface] Add handling of transmitter input voltage, including a rough conversion of volts to ADC value for different boards, and default battery volts lookup function; Clear analogs array before starting. * [Simulator] Add SimulatorInterface::init() method to separate pre-startup tasks; Report actual trim range, not just extended on/off; Change how radio widget states are restored; VirtualJoystickWidget: Connect trim changes directly from simulator, connect joystick events directly, report stick mode directly instead of setting values/constraints externally. * [Simulator] Calculate default Tx V input based on configured range in radio settings (or warning V+2 for radios which don't support a range). * [Simulator] Add functional aux. trims for Horus (closes #4699). * [Companion] Remove problematic QMessageLogContext from AppDebugMessageHandler::messageOutput(). * [Simulator] Prevent trim change via slider if disabled for flight mode (closes #4600). * [OpenTxSimulator] Fixes for Qt < 5.4. * [OpenTxSimulator] Fix slot name. --- companion/src/appdebugmessagehandler.cpp | 4 +- companion/src/appdebugmessagehandler.h | 2 +- companion/src/companion.qrc | 2 + .../simulator/icons/svg/arrow_click.svg | 27 + .../src/images/simulator/icons/svg/mouse.svg | 20 + companion/src/simulation/CMakeLists.txt | 2 + companion/src/simulation/debugoutput.cpp | 33 +- companion/src/simulation/debugoutput.h | 6 +- .../src/simulation/radiooutputswidget.cpp | 159 ++--- companion/src/simulation/radiooutputswidget.h | 17 +- companion/src/simulation/radiouiaction.h | 50 +- .../src/simulation/simulateduiwidget.cpp | 115 ++-- companion/src/simulation/simulateduiwidget.h | 37 +- .../src/simulation/simulateduiwidget9X.cpp | 41 +- .../src/simulation/simulateduiwidgetX12.cpp | 43 +- .../src/simulation/simulateduiwidgetX7.cpp | 23 +- .../src/simulation/simulateduiwidgetX7.ui | 2 +- .../src/simulation/simulateduiwidgetX9.cpp | 48 +- .../src/simulation/simulateduiwidgetX9E.cpp | 23 +- companion/src/simulation/simulator_strings.h | 66 ++ companion/src/simulation/simulatorimport.h | 114 ---- companion/src/simulation/simulatorinterface.h | 181 ++++-- .../src/simulation/simulatormainwindow.cpp | 74 ++- .../src/simulation/simulatormainwindow.h | 4 +- companion/src/simulation/simulatorwidget.cpp | 564 ++++++++-------- companion/src/simulation/simulatorwidget.h | 118 ++-- companion/src/simulation/telemetrysimu.cpp | 67 +- companion/src/simulation/telemetrysimu.h | 17 +- companion/src/simulation/trainersimu.cpp | 74 ++- companion/src/simulation/trainersimu.h | 19 +- .../src/simulation/widgets/buttonswidget.h | 66 +- .../src/simulation/widgets/radiokeywidget.h | 100 +++ .../src/simulation/widgets/radioknobwidget.h | 21 +- .../simulation/widgets/radioswitchwidget.h | 3 +- .../src/simulation/widgets/radiotrimwidget.h | 63 +- .../src/simulation/widgets/radiowidget.cpp | 107 +++- .../src/simulation/widgets/radiowidget.h | 79 ++- .../src/simulation/widgets/sliderwidget.h | 37 +- .../widgets/virtualjoystickwidget.cpp | 174 ++++- .../widgets/virtualjoystickwidget.h | 53 +- radio/src/mixer.cpp | 2 +- radio/src/targets/horus/keys_driver.cpp | 4 +- radio/src/targets/simu/CMakeLists.txt | 4 +- radio/src/targets/simu/opentxsimulator.cpp | 600 ++++++++++++++---- radio/src/targets/simu/opentxsimulator.h | 106 ++-- radio/src/targets/simu/simpgmspace.cpp | 8 +- 46 files changed, 2058 insertions(+), 1321 deletions(-) create mode 100644 companion/src/images/simulator/icons/svg/arrow_click.svg create mode 100644 companion/src/images/simulator/icons/svg/mouse.svg create mode 100644 companion/src/simulation/simulator_strings.h delete mode 100644 companion/src/simulation/simulatorimport.h create mode 100644 companion/src/simulation/widgets/radiokeywidget.h diff --git a/companion/src/appdebugmessagehandler.cpp b/companion/src/appdebugmessagehandler.cpp index 816ad2057..ab2ee3858 100644 --- a/companion/src/appdebugmessagehandler.cpp +++ b/companion/src/appdebugmessagehandler.cpp @@ -98,7 +98,7 @@ void AppDebugMessageHandler::messageHandler(QtMsgType type, const QMessageLogCon qSetMessagePattern(msgPattern); - if (!m_defaultHandler || receivers(SIGNAL(messageOutput(quint8, const QString &, const QMessageLogContext &)))) { + if (!m_defaultHandler || receivers(SIGNAL(messageOutput(quint8, const QString &)))) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) msgPattern = qFormatLogMessage(type, context, msg); #else @@ -111,7 +111,7 @@ void AppDebugMessageHandler::messageHandler(QtMsgType type, const QMessageLogCon } #endif - emit messageOutput(lvl, msgPattern, context); + emit messageOutput(lvl, msgPattern); } // if (QThread::currentThread() == qApp->thread()) // gui thread diff --git a/companion/src/appdebugmessagehandler.h b/companion/src/appdebugmessagehandler.h index 1e79ef63e..648090b16 100644 --- a/companion/src/appdebugmessagehandler.h +++ b/companion/src/appdebugmessagehandler.h @@ -127,7 +127,7 @@ class AppDebugMessageHandler : public QObject bool m_showFunctionDeclarations; signals: - void messageOutput(quint8 level, const QString & msg, const QMessageLogContext & context); + void messageOutput(quint8 level, const QString & msg); }; // Message handler which is installed using qInstallMessageHandler. This needs to be global. diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index 5b68fcd0b..26c8f57d2 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -12,6 +12,7 @@ images/track.png images/track0.png images/taranison.png + images/simulator/icons/svg/arrow_click.svg images/simulator/icons/svg/camera.svg images/simulator/icons/svg/camera-active.svg images/simulator/icons/svg/console.svg @@ -32,6 +33,7 @@ images/simulator/icons/svg/joystick_settings.svg images/simulator/icons/svg/joystick_settings-active.svg images/simulator/icons/svg/line_horiz.svg + images/simulator/icons/svg/mouse.svg images/simulator/icons/svg/toggle_lock.svg images/simulator/icons/svg/toggle_lock-on.svg images/simulator/icons/svg/radio_outputs.svg diff --git a/companion/src/images/simulator/icons/svg/arrow_click.svg b/companion/src/images/simulator/icons/svg/arrow_click.svg new file mode 100644 index 000000000..a6433b6fe --- /dev/null +++ b/companion/src/images/simulator/icons/svg/arrow_click.svg @@ -0,0 +1,27 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/mouse.svg b/companion/src/images/simulator/icons/svg/mouse.svg new file mode 100644 index 000000000..c6eac360c --- /dev/null +++ b/companion/src/images/simulator/icons/svg/mouse.svg @@ -0,0 +1,20 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index d29ed78a9..210f6a44f 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -40,6 +40,7 @@ set(simulation_HDRS radiouiaction.h simulateduiwidget.h # simulator.h + simulatorinterface.h simulatormainwindow.h simulatorstartupdialog.h simulatorwidget.h @@ -49,6 +50,7 @@ set(simulation_HDRS widgets/lcdwidget.h widgets/radiowidget.h widgets/radiofaderwidget.h + widgets/radiokeywidget.h widgets/radioknobwidget.h widgets/radioswitchwidget.h widgets/radiotrimwidget.h diff --git a/companion/src/simulation/debugoutput.cpp b/companion/src/simulation/debugoutput.cpp index 2bba42b9f..c031084f0 100644 --- a/companion/src/simulation/debugoutput.cpp +++ b/companion/src/simulation/debugoutput.cpp @@ -36,20 +36,13 @@ extern AppData g; // ensure what "g" means -FilteredTextBuffer * DebugOutput::m_dataBufferDevice = Q_NULLPTR; const quint16 DebugOutput::m_savedViewStateVersion = 1; -void firmwareTraceCb(const char * text) -{ - if (DebugOutput::m_dataBufferDevice) { - DebugOutput::m_dataBufferDevice->write(text); - } -} - DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): QWidget(parent), ui(new Ui::DebugOutput), m_simulator(simulator), + m_dataBufferDevice(NULL), m_radioProfileId(g.sessionId()), m_filterEnable(false), m_filterExclude(false) @@ -101,33 +94,28 @@ DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): connect(ui->actionToggleFilter, &QAction::toggled, this, &DebugOutput::onFilterToggled); connect(ui->filterText, &QComboBox::currentTextChanged, this, &DebugOutput::onFilterTextChanged); - if (AppDebugMessageHandler::instance()) { -#if (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) // https://bugreports.qt.io/browse/QTBUG-36119 - connect(AppDebugMessageHandler::instance(), SIGNAL(messageOutput(quint8,QString,QMessageLogContext)), this, SLOT(onAppDebugMessage(quint8,QString,QMessageLogContext))); -#else + if (AppDebugMessageHandler::instance()) connect(AppDebugMessageHandler::instance(), &AppDebugMessageHandler::messageOutput, this, &DebugOutput::onAppDebugMessage); -#endif - } // send firmware TRACE events to our data collector - m_simulator->installTraceHook(firmwareTraceCb); + m_simulator->addTracebackDevice(m_dataBufferDevice); } DebugOutput::~DebugOutput() { - m_simulator->installTraceHook(NULL); - saveState(); - if (AppDebugMessageHandler::instance()) disconnect(AppDebugMessageHandler::instance(), 0, this, 0); if (m_dataBufferDevice) { + m_simulator->removeTracebackDevice(m_dataBufferDevice); disconnect(m_dataBufferDevice, 0, this, 0); disconnect(this, 0, m_dataBufferDevice, 0); - m_dataBufferDevice->deleteLater(); + delete m_dataBufferDevice; m_dataBufferDevice = Q_NULLPTR; } + saveState(); + delete ui; } @@ -213,11 +201,10 @@ void DebugOutput::onDataBufferOverflow(const qint64 len) } } -void DebugOutput::onAppDebugMessage(quint8 level, const QString & msg, const QMessageLogContext & context) +void DebugOutput::onAppDebugMessage(quint8 level, const QString & msg) { - if (level > 0) { - firmwareTraceCb(qPrintable(msg)); - firmwareTraceCb("\n"); + if (level > 0 && m_dataBufferDevice) { + m_dataBufferDevice->write(qPrintable(msg % "\n")); } } diff --git a/companion/src/simulation/debugoutput.h b/companion/src/simulation/debugoutput.h index 6cf67acdc..b7a325b46 100644 --- a/companion/src/simulation/debugoutput.h +++ b/companion/src/simulation/debugoutput.h @@ -60,20 +60,19 @@ class DebugOutput : public QWidget static QRegularExpression makeRegEx(const QString & input, bool * isExlusive = NULL); - static FilteredTextBuffer * m_dataBufferDevice; - signals: void filterExprChanged(const QRegularExpression & expr); void filterEnabledChanged(const bool enabled); void filterExclusiveChanged(const bool exlusive); void filterChanged(bool enable, bool exclusive, const QRegularExpression & expr); + void tracebackDeviceChange(QIODevice * device); protected slots: void saveState(); void restoreState(); void processBytesReceived(); void onDataBufferOverflow(const qint64 len); - void onAppDebugMessage(quint8 level, const QString & msg, const QMessageLogContext & context); + void onAppDebugMessage(quint8 level, const QString & msg); void onFilterStateChanged(); void onFilterTextChanged(const QString &); void onFilterToggled(bool enable); @@ -85,6 +84,7 @@ class DebugOutput : public QWidget protected: Ui::DebugOutput * ui; SimulatorInterface * m_simulator; + FilteredTextBuffer * m_dataBufferDevice; int m_radioProfileId; bool m_filterEnable; bool m_filterExclude; diff --git a/companion/src/simulation/radiooutputswidget.cpp b/companion/src/simulation/radiooutputswidget.cpp index d2cdd686d..775a517e6 100644 --- a/companion/src/simulation/radiooutputswidget.cpp +++ b/companion/src/simulation/radiooutputswidget.cpp @@ -26,39 +26,32 @@ #include "eeprominterface.h" #include "radiodata.h" #include "simulator.h" -#include "simulatorinterface.h" extern AppData g; // ensure what "g" means -const int RadioOutputsWidget::m_dataUpdateFreqDefault = 10; // ms const quint16 RadioOutputsWidget::m_savedViewStateVersion = 1; RadioOutputsWidget::RadioOutputsWidget(SimulatorInterface * simulator, Firmware * firmware, QWidget *parent) : QWidget(parent), m_simulator(simulator), m_firmware(firmware), - m_tmrUpdateData(new QTimer), m_radioProfileId(g.sessionId()), - m_lastFlightPhase(-1), - m_started(false), ui(new Ui::RadioOutputsWidget) { ui->setupUi(this); restoreState(); - m_dataUpdateFreq = m_dataUpdateFreqDefault; - m_tmrUpdateData->setInterval(m_dataUpdateFreq); - - connect(m_tmrUpdateData, &QTimer::timeout, this, &RadioOutputsWidget::setValues); + connect(m_simulator, &SimulatorInterface::channelOutValueChange, this, &RadioOutputsWidget::onChannelOutValueChange); + connect(m_simulator, &SimulatorInterface::virtualSwValueChange, this, &RadioOutputsWidget::onVirtSwValueChange); + connect(m_simulator, &SimulatorInterface::gVarValueChange, this, &RadioOutputsWidget::onGVarValueChange); + connect(m_simulator, &SimulatorInterface::phaseChanged, this, &RadioOutputsWidget::onPhaseChanged); } RadioOutputsWidget::~RadioOutputsWidget() { - stop(); + //stop(); saveState(); - if (m_tmrUpdateData) - delete m_tmrUpdateData; delete ui; } @@ -74,36 +67,20 @@ void RadioOutputsWidget::changeEvent(QEvent *e) } } -void RadioOutputsWidget::showEvent(QShowEvent * event) -{ - if (m_started) - m_tmrUpdateData->start(); -} - -void RadioOutputsWidget::hideEvent(QHideEvent * event) -{ - m_tmrUpdateData->stop(); -} - void RadioOutputsWidget::start() { setupChannelsDisplay(); setupGVarsDisplay(); setupLsDisplay(); - m_lastFlightPhase = -1; - m_tmrUpdateData->start(); - m_started = true; } -void RadioOutputsWidget::stop() -{ - m_tmrUpdateData->stop(); - m_started = false; -} +//void RadioOutputsWidget::stop() +//{ +//} void RadioOutputsWidget::restart() { - stop(); + //stop(); start(); } @@ -139,7 +116,6 @@ void RadioOutputsWidget::restoreState() ui->splitter->restoreState(splitterState); } - void RadioOutputsWidget::setupChannelsDisplay() { int outputs = std::min(32, m_firmware->getCapability(Capability(Outputs))); @@ -297,68 +273,73 @@ QWidget * RadioOutputsWidget::createLogicalSwitch(QWidget * parent, int switchNo return swtch; } -// Read various values from firmware simulator and populate values in this UI -void RadioOutputsWidget::setValues() +void RadioOutputsWidget::onChannelOutValueChange(quint8 index, qint32 value) { - static TxOutputs prevOutputs = TxOutputs(); - int currentPhase; - TxOutputs outputs; - QFont font; - - m_simulator->getValues(outputs); - currentPhase = m_simulator->getPhase(); - - if (ui->channelsWidget->isVisible()) { - QHash >::const_iterator ch; - for (ch = m_channelsMap.constBegin(); ch != m_channelsMap.constEnd(); ++ch) { - if (ch.key() >= CPN_MAX_CHNOUT) - continue; - ch.value().first->setText(QString("%1%").arg(calcRESXto100(outputs.chans[ch.key()]))); - ch.value().second->setValue(qMin(1024, qMax(-1024, outputs.chans[ch.key()]))); - } + if (m_channelsMap.contains(index)) { + QPair ch = m_channelsMap.value(index); + ch.first->setText(QString("%1%").arg(calcRESXto100(value))); + ch.second->setValue(qMin(1024, qMax(-1024, value))); } + //qDebug() << index << value; +} - if (ui->logicalSwitchesWidget->isVisible()) { - QHash::const_iterator ls; - for (ls = m_logicSwitchMap.constBegin(); ls != m_logicSwitchMap.constEnd(); ++ls) { - if (ls.key() >= CPN_MAX_LOGICAL_SWITCHES || (prevOutputs.vsw[ls.key()] == outputs.vsw[ls.key()] && m_lastFlightPhase > -1)) - continue; - ls.value()->setBackgroundRole(outputs.vsw[ls.key()] ? QPalette::Dark : QPalette::Background); - ls.value()->setForegroundRole(outputs.vsw[ls.key()] ? QPalette::BrightText : QPalette::WindowText); - ls.value()->setFrameShadow(outputs.vsw[ls.key()] ? QFrame::Sunken : QFrame::Raised); - font = ls.value()->font(); - font.setBold(outputs.vsw[ls.key()]); - ls.value()->setFont(font); - prevOutputs.vsw[ls.key()] = outputs.vsw[ls.key()]; - } +void RadioOutputsWidget::onVirtSwValueChange(quint8 index, qint32 value) +{ + if (!m_logicSwitchMap.contains(index)) + return; + + QLabel * ls = m_logicSwitchMap.value(index); + ls->setBackgroundRole(value ? QPalette::Dark : QPalette::Background); + ls->setForegroundRole(value ? QPalette::BrightText : QPalette::WindowText); + ls->setFrameShadow(value ? QFrame::Sunken : QFrame::Raised); + QFont font = ls->font(); + font.setBold((bool)value); + ls->setFont(font); + //qDebug() << index << value; +} + +void RadioOutputsWidget::onGVarValueChange(quint8 index, qint32 value) +{ + if (!m_globalVarsMap.contains(index)) + return; + + QHash fmMap = m_globalVarsMap.value(index); + SimulatorInterface::gVarMode_t gv(value); + + if (fmMap.contains(gv.mode)) { + QLabel * lbl = fmMap.value(gv.mode); + if (lbl) + lbl->setText(QString::number(gv.value)); } + //qDebug() << index << value << gv.mode << gv.value; +} + +void RadioOutputsWidget::onPhaseChanged(qint32 phase, const QString &) +{ + QPalette::ColorRole fgrole, bgrole; + QLabel * lbl; + QFont font; + QHash >::const_iterator gv; + QHash::const_iterator fm; - if (ui->globalVarsWidget->isVisible()) { - QPalette::ColorRole bgrole; - QHash >::const_iterator gv; - QHash::const_iterator fm; - for (gv = m_globalVarsMap.constBegin(); gv != m_globalVarsMap.constEnd(); ++gv) { - if (gv.key() >= CPN_MAX_GVARS) - continue; - for (fm = gv.value().constBegin(); fm != gv.value().constEnd(); ++fm) { - if (fm.key() >= CPN_MAX_FLIGHT_MODES) - continue; - if (currentPhase != m_lastFlightPhase || prevOutputs.gvars[fm.key()][gv.key()] != outputs.gvars[fm.key()][gv.key()]) { - if (fm.key() == currentPhase) - bgrole = QPalette::Dark; - else - bgrole = ((gv.key() % 2) ? QPalette::Background : QPalette::AlternateBase); - fm.value()->setBackgroundRole(bgrole); - fm.value()->setForegroundRole(fm.key() == currentPhase ? QPalette::BrightText : QPalette::WindowText); - font = fm.value()->font(); - font.setBold(fm.key() == currentPhase); - fm.value()->setFont(font); - fm.value()->setText(QString::number(outputs.gvars[fm.key()][gv.key()])); - prevOutputs.gvars[fm.key()][gv.key()] = outputs.gvars[fm.key()][gv.key()]; - } + for (gv = m_globalVarsMap.constBegin(); gv != m_globalVarsMap.constEnd(); ++gv) { + for (fm = gv.value().constBegin(); fm != gv.value().constEnd(); ++fm) { + lbl = fm.value(); + font = lbl->font(); + if (fm.key() == phase) { + fgrole = QPalette::BrightText; + bgrole = QPalette::Dark; + font.setBold(true); } + else { + fgrole = QPalette::WindowText; + bgrole = ((gv.key() % 2) ? QPalette::Background : QPalette::AlternateBase); + font.setBold(false); + } + lbl->setForegroundRole(fgrole); + lbl->setBackgroundRole(bgrole); + lbl->setFont(font); } } - - m_lastFlightPhase = currentPhase; + //qDebug() << phase; } diff --git a/companion/src/simulation/radiooutputswidget.h b/companion/src/simulation/radiooutputswidget.h index dbcd82d90..1f699e99d 100644 --- a/companion/src/simulation/radiooutputswidget.h +++ b/companion/src/simulation/radiooutputswidget.h @@ -22,6 +22,7 @@ #define RADIOOUTPUTSWIDGET_H #include "simulator.h" +#include "simulatorinterface.h" #include #include @@ -31,7 +32,7 @@ class RadioOutputsWidget; } class Firmware; -class SimulatorInterface; +//class SimulatorInterface; class QFrame; class QLabel; @@ -47,16 +48,18 @@ class RadioOutputsWidget : public QWidget explicit RadioOutputsWidget(SimulatorInterface * simulator, Firmware * firmware, QWidget * parent = 0); ~RadioOutputsWidget(); + public slots: void start(); - void stop(); + //void stop(); void restart(); protected slots: void saveState(); void restoreState(); - void setValues(); - void showEvent(QShowEvent *event); - void hideEvent(QHideEvent *event); + void onChannelOutValueChange(quint8 index, qint32 value); + void onVirtSwValueChange(quint8 index, qint32 value); + void onGVarValueChange(quint8 index, qint32 value); + void onPhaseChanged(qint32 phase, const QString &); protected: void changeEvent(QEvent *e); @@ -67,7 +70,6 @@ class RadioOutputsWidget : public QWidget SimulatorInterface * m_simulator; Firmware * m_firmware; - QTimer * m_tmrUpdateData; QHash > m_channelsMap; // m_channelsMap[chanIndex] = {QLabel*, QSlider*} QHash m_logicSwitchMap; // m_logicSwitchMap[lsIndex] = QLabel* @@ -75,10 +77,7 @@ class RadioOutputsWidget : public QWidget int m_radioProfileId; int m_dataUpdateFreq; - int m_lastFlightPhase; - bool m_started; - const static int m_dataUpdateFreqDefault; const static quint16 m_savedViewStateVersion; private: diff --git a/companion/src/simulation/radiouiaction.h b/companion/src/simulation/radiouiaction.h index cc3a4b5e1..d2904c491 100644 --- a/companion/src/simulation/radiouiaction.h +++ b/companion/src/simulation/radiouiaction.h @@ -37,12 +37,12 @@ class RadioUiAction : public QObject /* * @param index Typically this is the hardware array index corresponding to button on current radio model, * but it could be anything. Use -1 or any other negative value for non-hardware indices. - * @param key An optional Qt:Key code for shortcut. + * @param key An optional Qt:Key code for shortcut (zero for none). * @param parent Parent widget, required for handling keyboard events. * @param text Optional title for this action. The text and description are currently used in generated help text. * @param descript Optional longer description text for this action. */ - RadioUiAction(int index = -1, int key = 0, QWidget * parent = NULL, const QString &text = "", const QString &descript = ""): + RadioUiAction(int index = -1, int key = 0, const QString &text = "", const QString &descript = "", QWidget * parent = NULL): m_hwIndex(index), m_active(false), m_keys(QList()), @@ -50,36 +50,50 @@ class RadioUiAction : public QObject m_description(descript), m_parent(parent) { - if (key) - m_keys.append(key); - - init(); + addKey(key); } /* * @param keys QList of Qt:Key codes to use as shortcuts. * [See above for other params.] */ - RadioUiAction(int index, QList keys, QWidget * parent = NULL, const QString &text = "", const QString &descript = ""): - m_hwIndex(index), - m_active(false), - m_keys(keys), - m_text(text), - m_description(descript), - m_parent(parent) + RadioUiAction(int index, QList keys, const QString &text = "", const QString &descript = "", QWidget * parent = NULL): + RadioUiAction(index, 0, text, descript, parent) { - init(); + addKeys(keys); } - void init() + void addKey(const int & key) { - if (m_keys.size()) + if (key > 0 && !m_keys.contains(key)) { + m_keys.append(key); addShortcut(); + } + } + + void addKeys(const QList & keys) + { + foreach (int key, keys) + addKey(key); } void addShortcut() { - if (m_parent) - m_parent->installEventFilter(this); + if (m_parent) { + m_parent->removeEventFilter(this); + if (m_keys.size()) + m_parent->installEventFilter(this); + } + } + + // override + void setParent(QObject * parent) + { + QObject::setParent(parent); + QWidget * w = qobject_cast(parent); + if (w && w != m_parent) { + m_parent = w; + addShortcut(); + } } void setDescription(const QString & description) { m_description = description; } diff --git a/companion/src/simulation/simulateduiwidget.cpp b/companion/src/simulation/simulateduiwidget.cpp index fc4ed35bb..c3db6f4ef 100644 --- a/companion/src/simulation/simulateduiwidget.cpp +++ b/companion/src/simulation/simulateduiwidget.cpp @@ -22,8 +22,13 @@ #include "eeprominterface.h" #include "lcdwidget.h" #include "radiouiaction.h" +#include "radiokeywidget.h" #include "simulatorinterface.h" +//#define FLASH_DURATION 10 +//#define CBEEP_ON "QLabel { background-color: #FF364E }" +//#define CBEEP_OFF "QLabel { }" + SimulatedUIWidget::SimulatedUIWidget(SimulatorInterface * simulator, QWidget * parent) : QWidget(parent), m_simulator(simulator), @@ -31,16 +36,18 @@ SimulatedUIWidget::SimulatedUIWidget(SimulatorInterface * simulator, QWidget * p m_lcd(NULL), m_scrollUpAction(NULL), m_scrollDnAction(NULL), - m_rotEncClickAction(NULL), + m_mouseMidClickAction(NULL), + m_screenshotAction(NULL), m_board(getCurrentBoard()), m_backLight(0), - m_lightOn(false), m_beepShow(0), m_beepVal(0) { - m_rotEncClickAction = addRadioUiAction(-1, 0, tr("Rotary encoder click")); - m_screenshotAction = addRadioUiAction(-1, Qt::Key_Print, tr("Take Screenshot")); + m_screenshotAction = new RadioUiAction(-1, Qt::Key_Print); connect(m_screenshotAction, static_cast(&RadioUiAction::pushed), this, &SimulatedUIWidget::captureScreenshot); + + connect(m_simulator, &SimulatorInterface::lcdChange, this, &SimulatedUIWidget::onLcdChange); + connect(this, &SimulatedUIWidget::simulatorWheelEvent, m_simulator, &SimulatorInterface::rotaryEncoderEvent); } SimulatedUIWidget::~SimulatedUIWidget() @@ -49,28 +56,44 @@ SimulatedUIWidget::~SimulatedUIWidget() if (act) delete act; } + foreach (RadioWidget * w, m_widgets) { + if (w) + delete w; + } } -RadioUiAction * SimulatedUIWidget::addRadioUiAction(RadioUiAction * act) +RadioWidget * SimulatedUIWidget::addRadioWidget(RadioWidget * widget) { - if (act) { + if (widget && !m_widgets.contains(widget)) { + m_widgets.append(widget); + // TODO : connect to actions instead + connect(widget, &RadioWidget::valueChange, this, &SimulatedUIWidget::controlValueChange); + if (widget->getAction()) + addRadioAction(widget->getAction()); + } + return widget; +} + +RadioUiAction * SimulatedUIWidget::addRadioAction(RadioUiAction * act) +{ + if (act && !m_actions.contains(act)) { + act->setParent(m_parent); m_actions.append(act); - if (!act->getText().isEmpty() && !act->getDescription().isEmpty()) - m_keymapHelp.append(keymapHelp_t(act->getText(), act->getDescription())); } return act; } -RadioUiAction * SimulatedUIWidget::addRadioUiAction(int index, int key, const QString & text, const QString & descript) +QVector SimulatedUIWidget::getKeymapHelp() const { - return addRadioUiAction(new RadioUiAction(index, key, m_parent, text, descript)); -} - -RadioUiAction * SimulatedUIWidget::addRadioUiAction(int index, QList keys, const QString & text, const QString & descript) -{ - return addRadioUiAction(new RadioUiAction(index, keys, m_parent, text, descript)); + QVector keymapHelp; + foreach (RadioUiAction * act, m_actions) { + if (act && !act->getText().isEmpty()) + keymapHelp.append(Simulator::keymapHelp_t(act->getText(), act->getDescription())); + } + return keymapHelp; } +// static QPolygon SimulatedUIWidget::polyArc(int ctrX, int ctrY, int radius, int startAngle, int endAngle, int step) { QPolygon polygon; @@ -83,20 +106,10 @@ QPolygon SimulatedUIWidget::polyArc(int ctrX, int ctrY, int radius, int startAng return polygon; } +/* TODO : beep indicator void SimulatedUIWidget::updateUi() { //static quint32 loop = 0; - if (m_lcd->isVisible()) { - bool lightEnable; - if (m_simulator->lcdChanged(lightEnable)) { - m_lcd->onLcdChanged(lightEnable); - if (m_lightOn != lightEnable) { - setLightOn(lightEnable); - m_lightOn = lightEnable; - } - } - } - /* TODO : beep indicator if (!(loop % 5)) { TxOutputs outputs; simulator->getValues(outputs); @@ -111,7 +124,16 @@ void SimulatedUIWidget::updateUi() beepShow--; } ui->label_beep->setStyleSheet(beepShow ? CBEEP_ON : CBEEP_OFF); - } */ + } +} */ + +void SimulatedUIWidget::onLcdChange(bool backlightEnable) +{ + if (!m_lcd || !m_lcd->isVisible()) + return; + + m_lcd->onLcdChanged(backlightEnable); + setLightOn(backlightEnable); } void SimulatedUIWidget::captureScreenshot() @@ -136,32 +158,27 @@ void SimulatedUIWidget::captureScreenshot() m_lcd->makeScreenshot(fileName); } -// steps can be negative or positive to determine direction (negative is UP/RIGHT scroll) -void SimulatedUIWidget::simulatorWheelEvent(qint8 steps) -{ - m_simulator->wheelEvent(steps); -} - void SimulatedUIWidget::wheelEvent(QWheelEvent * event) { if (event->angleDelta().isNull()) return; + // steps can be negative or positive to determine direction (negative is UP/LEFT scroll) QPoint numSteps = event->angleDelta() / 8 / 15 * -1; // one step per 15deg - simulatorWheelEvent(numSteps.y()); + emit simulatorWheelEvent(numSteps.y()); } void SimulatedUIWidget::mousePressEvent(QMouseEvent * event) { - if (event->button() == Qt::MidButton && m_rotEncClickAction) - m_rotEncClickAction->trigger(true); + if (event->button() == Qt::MidButton && m_mouseMidClickAction) + m_mouseMidClickAction->trigger(true); else event->ignore(); } void SimulatedUIWidget::mouseReleaseEvent(QMouseEvent * event) { - if (event->button() == Qt::MidButton && m_rotEncClickAction) - m_rotEncClickAction->trigger(false); + if (event->button() == Qt::MidButton && m_mouseMidClickAction) + m_mouseMidClickAction->trigger(false); else event->ignore(); } @@ -184,13 +201,19 @@ void SimulatedUIWidget::setLcd(LcdWidget * lcd) void SimulatedUIWidget::connectScrollActions() { - connect(m_scrollUpAction, static_cast(&RadioUiAction::pushed), [this](void) { - this->simulatorWheelEvent(-1); - m_scrollUpAction->toggle(false); - }); + if (m_scrollUpAction) { + addRadioAction(m_scrollUpAction); + connect(m_scrollUpAction, static_cast(&RadioUiAction::pushed), [this](void) { + emit simulatorWheelEvent(-1); + m_scrollUpAction->toggle(false); + }); + } - connect(m_scrollDnAction, static_cast(&RadioUiAction::pushed), [this](void) { - simulatorWheelEvent(1); - m_scrollDnAction->toggle(false); - }); + if (m_scrollDnAction) { + addRadioAction(m_scrollDnAction); + connect(m_scrollDnAction, static_cast(&RadioUiAction::pushed), [this](void) { + emit simulatorWheelEvent(1); + m_scrollDnAction->toggle(false); + }); + } } diff --git a/companion/src/simulation/simulateduiwidget.h b/companion/src/simulation/simulateduiwidget.h index dba6e7435..225fb65fe 100644 --- a/companion/src/simulation/simulateduiwidget.h +++ b/companion/src/simulation/simulateduiwidget.h @@ -23,17 +23,18 @@ #include "boards.h" #include "constants.h" +#include "radiowidget.h" #include "simulator.h" +#include "simulator_strings.h" #include #include class SimulatorInterface; class LcdWidget; +class RadioKeyWidget; class RadioUiAction; -using namespace Simulator; - /* * This is a base class for the main hardware-specific radio user interface, including LCD screen and navigation buttons/widgets. * It is responsible for hanlding all interactions with this part of the simulation (vs. common radio widgets like sticks/switches/knobs). @@ -52,51 +53,51 @@ class SimulatedUIWidget : public QWidget ~SimulatedUIWidget(); - RadioUiAction * addRadioUiAction(RadioUiAction * act); - RadioUiAction * addRadioUiAction(int index = -1, int key = 0, const QString &text = "", const QString &descript = ""); - RadioUiAction * addRadioUiAction(int index, QList keys, const QString &text = "", const QString &descript = ""); + RadioWidget * addRadioWidget(RadioWidget * keyWidget); + RadioUiAction * addRadioAction(RadioUiAction * act); - QVector * getKeymapHelp() { return &m_keymapHelp; } - QList getActions() const { return m_actions; } - RadioUiAction * getRotEncAction() const { return m_rotEncClickAction; } + QVector getKeymapHelp() const; - QPolygon polyArc(int ctrX, int ctrY, int radius, int startAngle = 0, int endAngle = 360, int step = 10); + static QPolygon polyArc(int ctrX, int ctrY, int radius, int startAngle = 0, int endAngle = 360, int step = 10); public slots: - void updateUi(); void captureScreenshot(); - void simulatorWheelEvent(qint8 steps); void wheelEvent(QWheelEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); - protected: + signals: + + void controlValueChange(RadioWidget::RadioWidgetType type, int index, int value); + void customStyleRequest(const QString & style); + void simulatorWheelEvent(qint8 steps); + + protected slots: void setLcd(LcdWidget * lcd); void connectScrollActions(); + void onLcdChange(bool backlightEnable); virtual void setLightOn(bool enable) { } + protected: + SimulatorInterface * m_simulator; QWidget * m_parent; LcdWidget * m_lcd; QVector m_backlightColors; - QVector m_keymapHelp; QList m_actions; + QList m_widgets; RadioUiAction * m_scrollUpAction; RadioUiAction * m_scrollDnAction; - RadioUiAction * m_rotEncClickAction; + RadioUiAction * m_mouseMidClickAction; RadioUiAction * m_screenshotAction; Board::Type m_board; unsigned int m_backLight; - bool m_lightOn; int m_beepShow; int m_beepVal; - signals: - void customStyleRequest(const QString & style); - }; diff --git a/companion/src/simulation/simulateduiwidget9X.cpp b/companion/src/simulation/simulateduiwidget9X.cpp index 725511528..daa809a20 100644 --- a/companion/src/simulation/simulateduiwidget9X.cpp +++ b/companion/src/simulation/simulateduiwidget9X.cpp @@ -20,6 +20,7 @@ #include "simulateduiwidget.h" #include "ui_simulateduiwidget9X.h" +#include "eeprominterface.h" SimulatedUIWidget9X::SimulatedUIWidget9X(SimulatorInterface * simulator, QWidget * parent): SimulatedUIWidget(simulator, parent), @@ -30,39 +31,49 @@ SimulatedUIWidget9X::SimulatedUIWidget9X(SimulatorInterface * simulator, QWidget ui->setupUi(this); + bool hasRotEnc = getCurrentFirmware()->getCapability(Capability::RotaryEncoders); + // add actions in order of appearance on the help menu int x = 68, y = 91, oR = 63; polygon << QPoint(x, y) << polyArc(x, y, oR, -45, 45); - act = addRadioUiAction(3, QList() << Qt::Key_Up << Qt::Key_PageUp, tr("UP/PG-UP"), tr("[ UP ]")); - ui->leftbuttons->addArea(polygon, "9X/9xcursup.png", act); + act = new RadioUiAction(3, QList() << Qt::Key_Up << Qt::Key_PageUp, tr(SIMU_STR_HLP_KEYS_GO_UP) % (hasRotEnc ? "" : tr("|" SIMU_STR_HLP_MOUSE_UP)), tr(SIMU_STR_HLP_ACT_UP)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "9X/9xcursup.png", act)); polygon.clear(); polygon << QPoint(x, y) << polyArc(x, y, oR, 135, 225); - act = addRadioUiAction(2, QList() << Qt::Key_Down << Qt::Key_PageDown, tr("DN/PG-DN"), tr("[ DN ]")); - ui->leftbuttons->addArea(polygon, "9X/9xcursdown.png", act); + act = new RadioUiAction(2, QList() << Qt::Key_Down << Qt::Key_PageDown, tr(SIMU_STR_HLP_KEYS_GO_DN) % (hasRotEnc ? "" : tr("|" SIMU_STR_HLP_MOUSE_DN)), tr(SIMU_STR_HLP_ACT_DN)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "9X/9xcursdown.png", act)); polygon.clear(); polygon << QPoint(x, y) << polyArc(x, y, oR, 45, 135); - act = addRadioUiAction(4, QList() << Qt::Key_Right << Qt::Key_Plus << Qt::Key_Equal, tr("RIGHT/+"), tr("[ + ]")); - ui->leftbuttons->addArea(polygon, "9X/9xcursmin.png", act); + act = new RadioUiAction(4, QList() << Qt::Key_Right << Qt::Key_Minus, tr(SIMU_STR_HLP_KEY_RGT "|" SIMU_STR_HLP_KEY_MIN), tr(SIMU_STR_HLP_ACT_MIN)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "9X/9xcursmin.png", act)); polygon.clear(); polygon << QPoint(x, y) << polyArc(x, y, oR, 225, 315); - act = addRadioUiAction(5, QList() << Qt::Key_Left << Qt::Key_Minus, tr("LEFT/-"), tr("[ - ]")); - ui->leftbuttons->addArea(polygon, "9X/9xcursplus.png", act); + act = new RadioUiAction(5, QList() << Qt::Key_Left << Qt::Key_Plus << Qt::Key_Equal, tr(SIMU_STR_HLP_KEY_LFT "|" SIMU_STR_HLP_KEY_PLS), tr(SIMU_STR_HLP_ACT_PLS)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "9X/9xcursplus.png", act)); - act = addRadioUiAction(0, QList() << Qt::Key_Enter << Qt::Key_Return, tr("ENTER/MOUSE-MID"), tr("[ MENU ]")); - m_rotEncClickAction = act; - ui->rightbuttons->addArea(25, 60, 71, 81, "9X/9xmenumenu.png", act); + act = new RadioUiAction(0, QList() << Qt::Key_Enter << Qt::Key_Return, tr(SIMU_STR_HLP_KEY_ENTER), tr(SIMU_STR_HLP_ACT_MENU)); + addRadioWidget(ui->rightbuttons->addArea(QRect(16, 54, 60, 34), "9X/9xmenumenu.png", act)); - act = addRadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr("DEL/BKSP/ESC"), tr("[ EXIT ]")); - ui->rightbuttons->addArea(25, 117, 71, 139, "9X/9xmenuexit.png", act); + if (!hasRotEnc) { + m_mouseMidClickAction = act; + m_mouseMidClickAction->setText(act->getText() % "|" SIMU_STR_HLP_MOUSE_MID); + } - ui->leftbuttons->addArea(-1, 148, 39, 182, "9X/9xcursphoto.png", m_screenshotAction); + act = new RadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr(SIMU_STR_HLP_KEYS_EXIT), tr(SIMU_STR_HLP_ACT_EXIT)); + addRadioWidget(ui->rightbuttons->addArea(QRect(16, 114, 60, 34), "9X/9xmenuexit.png", act)); - m_keymapHelp.append(keymapHelp_t(tr("WHEEL/PAD SCRL"), tr("[ UP ]/[ DN ] or Rotary Sel."))); + addRadioWidget(ui->leftbuttons->addArea(QRect(6, 149, 30, 30), "9X/9xcursphoto.png", m_screenshotAction)); + + if (hasRotEnc) { + addRadioAction(new RadioUiAction(-1, 0, tr(SIMU_STR_HLP_MOUSE_SCRL), tr(SIMU_STR_HLP_ROTENC " " SIMU_STR_HLP_ROTENC_LR))); + m_mouseMidClickAction = new RadioUiAction(14, Qt::Key_Insert, tr(SIMU_STR_HLP_KEY_INS "|" SIMU_STR_HLP_MOUSE_MID), tr(SIMU_STR_HLP_ACT_ROT_DN)); + addRadioWidget(ui->leftbuttons->addArea(QRect(0, 0, 0, 0), "9X/9xcurs.png", m_mouseMidClickAction)); + } m_backlightColors << QColor(159,165,247); m_backlightColors << QColor(166,247,159); diff --git a/companion/src/simulation/simulateduiwidgetX12.cpp b/companion/src/simulation/simulateduiwidgetX12.cpp index 7a3658799..6b58fd962 100644 --- a/companion/src/simulation/simulateduiwidgetX12.cpp +++ b/companion/src/simulation/simulateduiwidgetX12.cpp @@ -35,48 +35,47 @@ SimulatedUIWidgetX12::SimulatedUIWidgetX12(SimulatorInterface *simulator, QWidge int x = 74, y = 190, oR = 63, iR = 40; polygon << polyArc(x, y, oR, 225, 315) << polyArc(x, y, iR, 225, 315); - act = addRadioUiAction(0, QList() << Qt::Key_PageUp, tr("PG-UP"), tr("[ PgUp ]")); - ui->leftbuttons->addArea(polygon, "Horus/left_btn1.png", act); + act = new RadioUiAction(0, QList() << Qt::Key_PageUp, tr(SIMU_STR_HLP_KEY_PGUP), tr(SIMU_STR_HLP_ACT_PGUP)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "Horus/left_btn1.png", act)); polygon.clear(); polygon << polyArc(x, y, oR, 135, 225) << polyArc(x, y, iR, 135, 225); - act = addRadioUiAction(1, QList() << Qt::Key_PageDown, tr("PG-DN"), tr("[ PgDn ]")); - ui->leftbuttons->addArea(polygon, "Horus/left_btn2.png", act); + act = new RadioUiAction(1, QList() << Qt::Key_PageDown, tr(SIMU_STR_HLP_KEY_PGDN), tr(SIMU_STR_HLP_ACT_PGDN)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "Horus/left_btn2.png", act)); polygon.clear(); polygon << polyArc(x, y, oR, -45, 45) << polyArc(x, y, iR, -45, 45); - act = addRadioUiAction(3, QList() << Qt::Key_Up, tr("UP"), tr("[ MDL ]")); - ui->rightbuttons->addArea(polygon, "Horus/right_btnU.png", act); - - polygon.clear(); - polygon << polyArc(x, y, oR, 135, 225) << polyArc(x, y, iR, 135, 225); - act = addRadioUiAction(4, QList() << Qt::Key_Down << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr("DN/DEL/BKSP"), tr("[ RTN ]")); - ui->rightbuttons->addArea(polygon, "Horus/right_btnD.png", act); + act = new RadioUiAction(3, QList() << Qt::Key_Up, tr(SIMU_STR_HLP_KEY_UP), tr(SIMU_STR_HLP_ACT_MDL)); + addRadioWidget(ui->rightbuttons->addArea(polygon, "Horus/right_btnU.png", act)); polygon.clear(); polygon << polyArc(x, y, oR, 225, 315) << polyArc(x, y, iR, 225, 315); - act = addRadioUiAction(6, QList() << Qt::Key_Left, tr("LEFT"), tr("[ SYS ]")); - ui->rightbuttons->addArea(polygon, "Horus/right_btnL.png", act); + act = new RadioUiAction(6, QList() << Qt::Key_Left, tr(SIMU_STR_HLP_KEY_LFT), tr(SIMU_STR_HLP_ACT_SYS)); + addRadioWidget(ui->rightbuttons->addArea(polygon, "Horus/right_btnL.png", act)); polygon.clear(); polygon << polyArc(x, y, oR, 45, 135) << polyArc(x, y, iR, 45, 135); - act = addRadioUiAction(5, QList() << Qt::Key_Right, tr("RIGHT"), tr("[ TELE ]")); - ui->rightbuttons->addArea(polygon, "Horus/right_btnR.png", act); + act = new RadioUiAction(5, QList() << Qt::Key_Right, tr(SIMU_STR_HLP_KEY_RGT), tr(SIMU_STR_HLP_ACT_TELE)); + addRadioWidget(ui->rightbuttons->addArea(polygon, "Horus/right_btnR.png", act)); - m_keymapHelp.append(keymapHelp_t(tr("WHEEL/PAD SCRL"), tr("Rotary Selector"))); + polygon.clear(); + polygon << polyArc(x, y, oR, 135, 225) << polyArc(x, y, iR, 135, 225); + act = new RadioUiAction(4, QList() << Qt::Key_Down << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, + tr(SIMU_STR_HLP_KEY_DN "
" SIMU_STR_HLP_KEYS_EXIT), tr(SIMU_STR_HLP_ACT_RTN)); + addRadioWidget(ui->rightbuttons->addArea(polygon, "Horus/right_btnD.png", act)); - m_scrollUpAction = addRadioUiAction(-1, QList() << Qt::Key_Minus << Qt::Key_X, tr("-/X"), tr("Rotary UP")); - m_scrollDnAction = addRadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Equal << Qt::Key_C, tr("+/C"), tr("Rotary DOWN")); + m_scrollUpAction = new RadioUiAction(-1, QList() << Qt::Key_Minus, tr(SIMU_STR_HLP_KEY_MIN "|" SIMU_STR_HLP_MOUSE_UP), tr(SIMU_STR_HLP_ACT_ROT_LFT)); + m_scrollDnAction = new RadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Equal, tr(SIMU_STR_HLP_KEY_PLS "|" SIMU_STR_HLP_MOUSE_DN), tr(SIMU_STR_HLP_ACT_ROT_RGT)); + connectScrollActions(); - act = addRadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr("ENTER/MOUSE-MID"), tr("Selector Press")); - ui->rightbuttons->addArea(polyArc(x, y, iR), "Horus/right_ent.png", act); + m_mouseMidClickAction = new RadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr(SIMU_STR_HLP_KEYS_ACTIVATE), tr(SIMU_STR_HLP_ACT_ROT_DN)); + addRadioWidget(ui->rightbuttons->addArea(polyArc(x, y, iR), "Horus/right_ent.png", m_mouseMidClickAction)); - ui->leftbuttons->addArea(9, 259, 34, 282, "Horus/left_scrnsht.png", m_screenshotAction); + addRadioWidget(ui->leftbuttons->addArea(QRect(9, 259, 30, 30), "Horus/left_scrnsht.png", m_screenshotAction)); m_backlightColors << QColor(47, 123, 227); setLcd(ui->lcd); - connectScrollActions(); } SimulatedUIWidgetX12::~SimulatedUIWidgetX12() diff --git a/companion/src/simulation/simulateduiwidgetX7.cpp b/companion/src/simulation/simulateduiwidgetX7.cpp index 993d01460..0da8dc89e 100644 --- a/companion/src/simulation/simulateduiwidgetX7.cpp +++ b/companion/src/simulation/simulateduiwidgetX7.cpp @@ -14,26 +14,25 @@ SimulatedUIWidgetX7::SimulatedUIWidgetX7(SimulatorInterface *simulator, QWidget QPoint ctr(70, 91); polygon << polyArc(ctr.x(), ctr.y(), 50, -90, 90) << polyArc(ctr.x(), ctr.y(), 22, -90, 90); - act = addRadioUiAction(3, QList() << Qt::Key_PageUp, tr("PG-UP"), tr("[ PAGE ]")); - ui->leftbuttons->addArea(polygon, "X7/left_page.png", act); + act = new RadioUiAction(3, QList() << Qt::Key_PageUp << Qt::Key_Up, tr(SIMU_STR_HLP_KEYS_GO_UP), tr(SIMU_STR_HLP_ACT_PAGE)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "X7/left_page.png", act)); - act = addRadioUiAction(0, QList() << Qt::Key_PageDown, tr("PG-DN"), tr("[ MENU ]")); - ui->leftbuttons->addArea(polyArc(ctr.x(), ctr.y(), 20), "X7/left_menu.png", act); + act = new RadioUiAction(0, QList() << Qt::Key_PageDown << Qt::Key_Down, tr(SIMU_STR_HLP_KEYS_GO_DN), tr(SIMU_STR_HLP_ACT_MENU_ICN)); + addRadioWidget(ui->leftbuttons->addArea(polyArc(ctr.x(), ctr.y(), 20), "X7/left_menu.png", act)); polygon.clear(); polygon << polyArc(ctr.x(), ctr.y(), 50, 90, 270) << polyArc(ctr.x(), ctr.y(), 22, 90, 270); - act = addRadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr("DEL/BKSP/ESC"), tr("[ EXIT ]")); - ui->leftbuttons->addArea(polygon, "X7/left_exit.png", act); + act = new RadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr(SIMU_STR_HLP_KEYS_EXIT), tr(SIMU_STR_HLP_ACT_EXIT)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "X7/left_exit.png", act)); - m_keymapHelp.append(keymapHelp_t(tr("WHEEL/PAD SCRL"), tr("Rotary Selector"))); - m_scrollUpAction = addRadioUiAction(-1, QList() << Qt::Key_Minus << Qt::Key_Equal << Qt::Key_Up, tr("-/UP"), tr("Rotary UP")); - m_scrollDnAction = addRadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Down, tr("+/DN"), tr("Rotary DOWN")); + m_scrollUpAction = new RadioUiAction(-1, QList() << Qt::Key_Minus << Qt::Key_Equal << Qt::Key_Left, tr(SIMU_STR_HLP_KEYS_GO_LFT), tr(SIMU_STR_HLP_ACT_ROT_LFT)); + m_scrollDnAction = new RadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Right, tr(SIMU_STR_HLP_KEYS_GO_RGT), tr(SIMU_STR_HLP_ACT_ROT_RGT)); connectScrollActions(); - act = addRadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr("ENTER/MOUSE-MID"), tr("Selector Press")); - ui->rightbuttons->addArea(polyArc(88, 92, 33), "X7/right_ent.png", act); + m_mouseMidClickAction = new RadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr(SIMU_STR_HLP_KEYS_ACTIVATE), tr(SIMU_STR_HLP_ACT_ROT_DN)); + addRadioWidget(ui->rightbuttons->addArea(polyArc(88, 92, 33), "X7/right_ent.png", m_mouseMidClickAction)); - ui->leftbuttons->addArea(9, 154, 34, 177, "X7/left_scrnshot.png", m_screenshotAction); + addRadioWidget(ui->leftbuttons->addArea(QRect(9, 154, 30, 30), "X7/left_scrnshot.png", m_screenshotAction)); m_backlightColors << QColor(215, 243, 255); // X7 Blue m_backlightColors << QColor(166,247,159); diff --git a/companion/src/simulation/simulateduiwidgetX7.ui b/companion/src/simulation/simulateduiwidgetX7.ui index 573be197b..46d6092ea 100644 --- a/companion/src/simulation/simulateduiwidgetX7.ui +++ b/companion/src/simulation/simulateduiwidgetX7.ui @@ -29,7 +29,7 @@ - Taranis X7 Simulator + Taranis X7 Simulator background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 255), stop:0.757062 rgba(241, 238, 238, 255), stop:1 rgba(247, 245, 245, 255)); diff --git a/companion/src/simulation/simulateduiwidgetX9.cpp b/companion/src/simulation/simulateduiwidgetX9.cpp index a3767fb7d..0e3e3948c 100644 --- a/companion/src/simulation/simulateduiwidgetX9.cpp +++ b/companion/src/simulation/simulateduiwidgetX9.cpp @@ -26,40 +26,46 @@ SimulatedUIWidgetX9::SimulatedUIWidgetX9(SimulatorInterface *simulator, QWidget ui(new Ui::SimulatedUIWidgetX9) { RadioUiAction * act; - QPolygon polygon; ui->setupUi(this); // add actions in order of appearance on the help menu - act = addRadioUiAction(0, QList() << Qt::Key_PageUp, tr("PG-UP"), tr("[ MENU ]")); - polygon.setPoints(6, 20, 59, 27, 50, 45, 52, 56, 59, 50, 71, 26, 72); - ui->leftbuttons->addArea(polygon, "Taranis/x9l1.png", act); + // button placement, size, spacing + const int btop = 45, vsp = 17; + QRect btn(16, btop, 46, 31); - act = addRadioUiAction(3, QList() << Qt::Key_PageDown, tr("PG-DN"), tr("[ PAGE ]")); - polygon.setPoints(6, 23, 107, 30, 99, 46, 100, 55, 106, 47, 117, 28, 117); - ui->leftbuttons->addArea(polygon, "Taranis/x9l2.png", act); + // left side - act = addRadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr("DEL/BKSP/ESC"), tr("[ EXIT ]")); - polygon.setPoints(6, 24, 154, 32, 144, 46, 146, 57, 156, 46, 167, 29, 166); - ui->leftbuttons->addArea(polygon, "Taranis/x9l3.png", act); + act = new RadioUiAction(0, QList() << Qt::Key_Up << Qt::Key_PageUp, tr(SIMU_STR_HLP_KEYS_GO_UP), tr(SIMU_STR_HLP_ACT_MENU)); + addRadioWidget(ui->leftbuttons->addArea(btn, "Taranis/x9l1.png", act)); + btn.moveTop(btn.top() + btn.height() + vsp); - m_scrollUpAction = addRadioUiAction(4, QList() << Qt::Key_Plus << Qt::Key_Equal << Qt::Key_Up, tr("+/UP"), tr("[ + ]")); - m_scrollDnAction = addRadioUiAction(5, QList() << Qt::Key_Minus << Qt::Key_Down, tr("-/DN"), tr("[ - ]")); + act = new RadioUiAction(3, QList() << Qt::Key_Down << Qt::Key_PageDown, tr(SIMU_STR_HLP_KEYS_GO_DN), tr(SIMU_STR_HLP_ACT_PAGE)); + addRadioWidget(ui->leftbuttons->addArea(btn, "Taranis/x9l2.png", act)); + btn.moveTop(btn.top() + btn.height() + vsp); - polygon.setPoints(6, 64, 60, 71, 50, 90, 50, 100, 60, 90, 73, 72, 73); - ui->rightbuttons->addArea(polygon, "Taranis/x9r1.png", m_scrollUpAction); + act = new RadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr(SIMU_STR_HLP_KEYS_EXIT), tr(SIMU_STR_HLP_ACT_EXIT)); + addRadioWidget(ui->leftbuttons->addArea(btn, "Taranis/x9l3.png", act)); - polygon.setPoints(6, 63, 109, 73, 100, 88, 100, 98, 109, 88, 119, 72, 119); - ui->rightbuttons->addArea(polygon, "Taranis/x9r2.png", m_scrollDnAction); + m_scrollUpAction = new RadioUiAction(4, QList() << Qt::Key_Plus << Qt::Key_Equal << Qt::Key_Left, + tr(SIMU_STR_HLP_KEY_LFT "|" SIMU_STR_HLP_KEY_PLS "|" SIMU_STR_HLP_MOUSE_UP), tr(SIMU_STR_HLP_ACT_PLS)); + m_scrollDnAction = new RadioUiAction(5, QList() << Qt::Key_Minus << Qt::Key_Right, + tr(SIMU_STR_HLP_KEY_RGT "|" SIMU_STR_HLP_KEY_MIN "|" SIMU_STR_HLP_MOUSE_DN), tr(SIMU_STR_HLP_ACT_MIN)); - act = addRadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr("ENTER/MOUSE-MID"), tr("[ ENT ]")); - polygon.setPoints(6, 63, 155, 72, 146, 90, 146, 98, 155, 88, 166, 72, 166); - ui->rightbuttons->addArea(polygon, "Taranis/x9r3.png", act); + // right side + btn.moveTopLeft(QPoint(58, btop)); - ui->leftbuttons->addArea(90, 177, 118, 197, "Taranis/x9l4.png", m_screenshotAction); + addRadioWidget(ui->rightbuttons->addArea(btn, "Taranis/x9r1.png", m_scrollUpAction)); + btn.moveTop(btn.top() + btn.height() + vsp); - m_keymapHelp.append(keymapHelp_t(tr("WHEEL/PAD SCRL"), tr("[ + ]/[ - ]"))); + addRadioWidget(ui->rightbuttons->addArea(btn, "Taranis/x9r2.png", m_scrollDnAction)); + btn.moveTop(btn.top() + btn.height() + vsp); + + m_mouseMidClickAction = new RadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr(SIMU_STR_HLP_KEYS_ACTIVATE), tr(SIMU_STR_HLP_ACT_ENT)); + addRadioWidget(ui->rightbuttons->addArea(btn, "Taranis/x9r3.png", m_mouseMidClickAction)); + + addRadioWidget(ui->leftbuttons->addArea(QRect(89, 177, 30, 20), "Taranis/x9l4.png", m_screenshotAction)); m_backlightColors << QColor(47, 123, 227); // Taranis Blue m_backlightColors << QColor(166,247,159); diff --git a/companion/src/simulation/simulateduiwidgetX9E.cpp b/companion/src/simulation/simulateduiwidgetX9E.cpp index 8f3f97008..58d307d3d 100644 --- a/companion/src/simulation/simulateduiwidgetX9E.cpp +++ b/companion/src/simulation/simulateduiwidgetX9E.cpp @@ -35,28 +35,27 @@ SimulatedUIWidgetX9E::SimulatedUIWidgetX9E(SimulatorInterface *simulator, QWidge QPoint ctr(70, 100); polygon << polyArc(ctr.x(), ctr.y(), 60, -90, 0) << ctr; - act = addRadioUiAction(0, QList() << Qt::Key_PageUp, tr("PG-UP"), tr("[ MENU ]")); - ui->leftbuttons->addArea(polygon, "X9E/left_menu.png", act); + act = new RadioUiAction(0, QList() << Qt::Key_Up << Qt::Key_PageUp, tr(SIMU_STR_HLP_KEYS_GO_UP), tr(SIMU_STR_HLP_ACT_MENU)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "X9E/left_menu.png", act)); polygon.clear(); polygon << polyArc(ctr.x(), ctr.y(), 45, 0, 180); - act = addRadioUiAction(3, QList() << Qt::Key_PageDown, tr("PG-DN"), tr("[ PAGE ]")); - ui->leftbuttons->addArea(polygon, "X9E/left_page.png", act); + act = new RadioUiAction(3, QList() << Qt::Key_Down << Qt::Key_PageDown, tr(SIMU_STR_HLP_KEYS_GO_DN), tr(SIMU_STR_HLP_ACT_PAGE)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "X9E/left_page.png", act)); polygon.clear(); polygon << polyArc(ctr.x(), ctr.y(), 60, 180, 270) << ctr; - act = addRadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr("DEL/BKSP/ESC"), tr("[ EXIT ]")); - ui->leftbuttons->addArea(polygon, "X9E/left_exit.png", act); + act = new RadioUiAction(1, QList() << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, tr(SIMU_STR_HLP_KEYS_EXIT), tr(SIMU_STR_HLP_ACT_EXIT)); + addRadioWidget(ui->leftbuttons->addArea(polygon, "X9E/left_exit.png", act)); - m_keymapHelp.append(keymapHelp_t(tr("WHEEL/PAD SCRL"), tr("Rotary Selector"))); - m_scrollUpAction = addRadioUiAction(-1, QList() << Qt::Key_Minus << Qt::Key_Equal << Qt::Key_Up, tr("-/UP"), tr("Rotary UP")); - m_scrollDnAction = addRadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Down, tr("+/DN"), tr("Rotary DOWN")); + m_scrollUpAction = addRadioAction(new RadioUiAction(-1, QList() << Qt::Key_Minus << Qt::Key_Left, tr(SIMU_STR_HLP_KEYS_GO_LFT), tr(SIMU_STR_HLP_ACT_ROT_LFT))); + m_scrollDnAction = addRadioAction(new RadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Equal << Qt::Key_Right, tr(SIMU_STR_HLP_KEYS_GO_RGT), tr(SIMU_STR_HLP_ACT_ROT_RGT))); connectScrollActions(); - act = addRadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr("ENTER/MOUSE-MID"), tr("Selector Press")); - ui->rightbuttons->addArea(polyArc(90, 100, 50), "X9E/right_enter.png", act); + m_mouseMidClickAction = new RadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, tr(SIMU_STR_HLP_KEYS_ACTIVATE), tr(SIMU_STR_HLP_ACT_ROT_DN)); + addRadioWidget(ui->rightbuttons->addArea(polyArc(90, 100, 50), "X9E/right_enter.png", m_mouseMidClickAction)); - ui->leftbuttons->addArea(10, 170, 40, 200, "X9E/left_scrnshot.png", m_screenshotAction); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 170, 30, 30), "X9E/left_scrnshot.png", m_screenshotAction)); m_backlightColors << QColor(47, 123, 227); // Taranis Blue m_backlightColors << QColor(166,247,159); diff --git a/companion/src/simulation/simulator_strings.h b/companion/src/simulation/simulator_strings.h new file mode 100644 index 000000000..4a6982d77 --- /dev/null +++ b/companion/src/simulation/simulator_strings.h @@ -0,0 +1,66 @@ +#ifndef SIMULATOR_STRINGS_H +#define SIMULATOR_STRINGS_H + +#include + +#define SIMU_STR_HLP_KEY_ENTER QT_TRANSLATE_NOOP("SimulatedUIWidget", "ENTER") +#define SIMU_STR_HLP_KEY_PGUP QT_TRANSLATE_NOOP("SimulatedUIWidget", "PG-UP") +#define SIMU_STR_HLP_KEY_PGDN QT_TRANSLATE_NOOP("SimulatedUIWidget", "PG-DN") +#define SIMU_STR_HLP_KEY_DEL QT_TRANSLATE_NOOP("SimulatedUIWidget", "DEL") +#define SIMU_STR_HLP_KEY_BKSP QT_TRANSLATE_NOOP("SimulatedUIWidget", "BKSP") +#define SIMU_STR_HLP_KEY_ESC QT_TRANSLATE_NOOP("SimulatedUIWidget", "ESC") +#define SIMU_STR_HLP_KEY_INS QT_TRANSLATE_NOOP("SimulatedUIWidget", "INS") +#define SIMU_STR_HLP_KEY_PLS QT_TRANSLATE_NOOP("SimulatedUIWidget", "+") +#define SIMU_STR_HLP_KEY_MIN QT_TRANSLATE_NOOP("SimulatedUIWidget", "-") + +#define SIMU_STR_HLP_KEY_LFT QT_TRANSLATE_NOOP("SimulatedUIWidget", "←") // ← +#define SIMU_STR_HLP_KEY_RGT QT_TRANSLATE_NOOP("SimulatedUIWidget", "→") // → +#define SIMU_STR_HLP_KEY_UP QT_TRANSLATE_NOOP("SimulatedUIWidget", "↑") // ↑ +#define SIMU_STR_HLP_KEY_DN QT_TRANSLATE_NOOP("SimulatedUIWidget", "↓") // ↓ + +#define SIMU_STR_HLP_ROTENC QT_TRANSLATE_NOOP("SimulatedUIWidget", "⚆") // ⚆ +#define SIMU_STR_HLP_ROTENC_LFT QT_TRANSLATE_NOOP("SimulatedUIWidget", "↶") // ↶ +#define SIMU_STR_HLP_ROTENC_RGT QT_TRANSLATE_NOOP("SimulatedUIWidget", "↷") // ↷ +#define SIMU_STR_HLP_ROTENC_LR QT_TRANSLATE_NOOP("SimulatedUIWidget", "⇆") // ⇆ +#define SIMU_STR_HLP_ROTENC_DN QT_TRANSLATE_NOOP("SimulatedUIWidget", "⇓") // ⇓ + +#define SIMU_STR_HLP_SCRL_UP QT_TRANSLATE_NOOP("SimulatedUIWidget", "⇑") // ⇑ +#define SIMU_STR_HLP_SCRL_DN QT_TRANSLATE_NOOP("SimulatedUIWidget", "⇓") // ⇓ +#define SIMU_STR_HLP_SCRL_UD QT_TRANSLATE_NOOP("SimulatedUIWidget", "⇕") // ⇕ + +#define SIMU_STR_HLP_MOUSE QT_TRANSLATE_NOOP("SimulatedUIWidget", "") +#define SIMU_STR_HLP_CLICK QT_TRANSLATE_NOOP("SimulatedUIWidget", "") +#define SIMU_STR_HLP_MOUSE_MID SIMU_STR_HLP_MOUSE SIMU_STR_HLP_CLICK +#define SIMU_STR_HLP_MOUSE_UP SIMU_STR_HLP_MOUSE SIMU_STR_HLP_SCRL_UP +#define SIMU_STR_HLP_MOUSE_DN SIMU_STR_HLP_MOUSE SIMU_STR_HLP_SCRL_DN +#define SIMU_STR_HLP_MOUSE_SCRL SIMU_STR_HLP_MOUSE SIMU_STR_HLP_SCRL_UD + +#define SIMU_STR_HLP_ACT_MENU QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ MENU ]
") +#define SIMU_STR_HLP_ACT_PAGE QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ PAGE ]
") +#define SIMU_STR_HLP_ACT_EXIT QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ EXIT ]
") +#define SIMU_STR_HLP_ACT_ENT QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ ENT ]
") +#define SIMU_STR_HLP_ACT_UP QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ UP ]
") +#define SIMU_STR_HLP_ACT_DN QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ DN ]
") +#define SIMU_STR_HLP_ACT_PLS QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ + ]
") +#define SIMU_STR_HLP_ACT_MIN QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ - ]
") +#define SIMU_STR_HLP_ACT_PGUP QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ PgUp ]
") +#define SIMU_STR_HLP_ACT_PGDN QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ PgDn ]
") +#define SIMU_STR_HLP_ACT_MDL QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ MDL ]
") +#define SIMU_STR_HLP_ACT_RTN QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ RTN ]
") +#define SIMU_STR_HLP_ACT_SYS QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ SYS ]
") +#define SIMU_STR_HLP_ACT_TELE QT_TRANSLATE_NOOP("SimulatedUIWidget", "
[ TELE ]
") +#define SIMU_STR_HLP_ACT_MENU_ICN QT_TRANSLATE_NOOP("SimulatedUIWidget", "≡") // ≡ + + +#define SIMU_STR_HLP_KEYS_EXIT SIMU_STR_HLP_KEY_DEL "|" SIMU_STR_HLP_KEY_BKSP "|" SIMU_STR_HLP_KEY_ESC +#define SIMU_STR_HLP_KEYS_ACTIVATE SIMU_STR_HLP_KEY_ENTER "|" SIMU_STR_HLP_MOUSE_MID +#define SIMU_STR_HLP_KEYS_GO_UP SIMU_STR_HLP_KEY_UP "|" SIMU_STR_HLP_KEY_PGUP +#define SIMU_STR_HLP_KEYS_GO_DN SIMU_STR_HLP_KEY_DN "|" SIMU_STR_HLP_KEY_PGDN +#define SIMU_STR_HLP_KEYS_GO_LFT SIMU_STR_HLP_KEY_LFT "|" SIMU_STR_HLP_KEY_MIN "|" SIMU_STR_HLP_MOUSE_UP +#define SIMU_STR_HLP_KEYS_GO_RGT SIMU_STR_HLP_KEY_RGT "|" SIMU_STR_HLP_KEY_PLS "|" SIMU_STR_HLP_MOUSE_DN + +#define SIMU_STR_HLP_ACT_ROT_LFT SIMU_STR_HLP_ROTENC " " SIMU_STR_HLP_ROTENC_LFT +#define SIMU_STR_HLP_ACT_ROT_RGT SIMU_STR_HLP_ROTENC " " SIMU_STR_HLP_ROTENC_RGT +#define SIMU_STR_HLP_ACT_ROT_DN SIMU_STR_HLP_ROTENC " " SIMU_STR_HLP_ROTENC_DN + +#endif // SIMULATOR_STRINGS_H diff --git a/companion/src/simulation/simulatorimport.h b/companion/src/simulation/simulatorimport.h deleted file mode 100644 index 3ec4527fa..000000000 --- a/companion/src/simulation/simulatorimport.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 - -#if !defined(MAX_LOGICAL_SWITCHES) && defined(NUM_CSW) -#define MAX_LOGICAL_SWITCHES NUM_CSW -#endif - -#ifdef INIT_IMPORT -#undef INIT_IMPORT -#ifdef TELEMETRY_FRSKY -telemetryStreaming = 20; -#endif -#endif - -#ifdef SETVALUES_IMPORT -#undef SETVALUES_IMPORT -for (int i=0; iPIO_PDSR &= ~0x40; - else - PIOB->PIO_PDSR |= 0x40; -#else - if (inputs.rotenc) - simuSetKey(KEY_ENTER, true); -#endif -#endif // SETVALUES_IMPORT - -#ifdef GETVALUES_IMPORT -#undef GETVALUES_IMPORT -memset(outputs.chans, 0, sizeof(outputs.chans)); -for (unsigned int i=0; i #include -struct TxInputs -{ - int sticks[CPN_MAX_STICKS]; /* lh lv rv rh */ - int pots[CPN_MAX_POTS]; - int switches[CPN_MAX_SWITCHES]; - bool keys[CPN_MAX_KEYS]; - bool rotenc; - bool trims[CPN_MAX_TRIM_SW]; -}; +#define SIMULATOR_INTERFACE_HEARTBEAT_PERIOD 1000 // ms -class TxOutputs +class SimulatorInterface : public QObject { + Q_OBJECT + public: - TxOutputs() { memset(this, 0, sizeof(TxOutputs)); } - int chans[CPN_MAX_CHNOUT]; - bool vsw[CPN_MAX_LOGICAL_SWITCHES]; - int gvars[CPN_MAX_FLIGHT_MODES][CPN_MAX_GVARS]; - unsigned int beep; - // uint8_t phase; -}; -struct Trims -{ - int values[CPN_MAX_TRIMS]; /* lh lv rv rh t5 t6 */ - bool extended; -}; + enum InputSourceType { + INPUT_SRC_NONE = 0, + INPUT_SRC_ANALOG, // any analog source, index into g_anas[] + INPUT_SRC_STICK, // Board::StickAxes, g_anas[index] + INPUT_SRC_KNOB, // g_anas[index + StickAxes] + INPUT_SRC_SLIDER, // g_anas[index + StickAxes + num_pots] + INPUT_SRC_TXVIN, // g_anas[Analogs::TX_VOLTAGE] + INPUT_SRC_SWITCH, // Named 2/3-pos switches + INPUT_SRC_TRIM_SW, // Board::TrimSwitches + INPUT_SRC_TRIM, // Board::TrimAxes + INPUT_SRC_KEY, // UI key/pushbutton + INPUT_SRC_ROTENC, // Rotary encoder (TODO) + INPUT_SRC_TRAINER, // Virtual trainer input + INPUT_SRC_ENUM_COUNT + }; -class SimulatorInterface -{ - public: + enum OutputSourceType { + OUTPUT_SRC_NONE = 0, + OUTPUT_SRC_CHAN_OUT, + OUTPUT_SRC_CHAN_MIX, + OUTPUT_SRC_TRIM_VALUE, + OUTPUT_SRC_TRIM_RANGE, + OUTPUT_SRC_VIRTUAL_SW, + OUTPUT_SRC_PHASE, + OUTPUT_SRC_GVAR, + OUTPUT_SRC_ENUM_COUNT + }; + + // only for data not available from Boards or Firmware, eg. compile-time options + enum Capability { + CAP_LUA, // LUA + CAP_ROTARY_ENC, // ROTARY_ENCODERS + CAP_ROTARY_ENC_NAV, // ROTARY_ENCODER_NAVIGATION + CAP_TELEM_FRSKY_SPORT, // TELEMETRY_FRSKY_SPORT + CAP_ENUM_COUNT + }; + + // This allows automatic en/decoding of flight mode + gvar value to/from any int32 + struct gVarMode_t { + int16_t value; + uint8_t mode; + + gVarMode_t(int i = 0) { + set(i); + } + void set(int i) { + mode = (i >> 16) & 0xFF; + value = (i & 0xFFFF); + } + operator int() { + return ((mode << 16) | (value & 0xFFFF)); + } + gVarMode_t & operator =(const int i) { + set(i); + return *this; + } + }; + + struct TxOutputs { + TxOutputs() { clear(); } + void clear() { memset(this, 0, sizeof(TxOutputs)); } + + int16_t chans[CPN_MAX_CHNOUT]; // final channel outputs + int16_t ex_chans[CPN_MAX_CHNOUT]; // raw mix outputs + int16_t gvars[CPN_MAX_FLIGHT_MODES][CPN_MAX_GVARS]; + int trims[CPN_MAX_TRIMS]; // Board::TrimAxes enum + bool vsw[CPN_MAX_LOGICAL_SWITCHES]; // virtual/logic switches + int8_t phase; + qint16 trimRange; // TRIM_MAX or TRIM_EXTENDED_MAX + // bool beep; + }; virtual ~SimulatorInterface() {} - virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = "") { } - - virtual void setVolumeGain(int value) { } - - virtual void start(QByteArray & eeprom, bool tests=true) = 0; - - virtual void start(const char * filename, bool tests=true) = 0; - - virtual void stop() = 0; - - virtual void readEepromData(QByteArray & dest) = 0; - - virtual bool timer10ms() = 0; - + virtual QString name() = 0; + virtual bool isRunning() = 0; + virtual void readRadioData(QByteArray & dest) = 0; virtual uint8_t * getLcd() = 0; - - virtual bool lcdChanged(bool &lightEnable) = 0; - - virtual void setValues(TxInputs &inputs) = 0; - - virtual void getValues(TxOutputs &outputs) = 0; - - virtual void setTrim(unsigned int idx, int value) = 0; - - virtual void getTrims(Trims &trims) = 0; - - virtual unsigned int getPhase() = 0; - - virtual const char * getPhaseName(unsigned int phase) = 0; - - virtual void wheelEvent(int steps) { } - - virtual const char * getError() = 0; - - virtual void sendTelemetry(uint8_t * data, unsigned int len) = 0; - virtual uint8_t getSensorInstance(uint16_t id, uint8_t defaultValue = 0) = 0; - virtual uint16_t getSensorRatio(uint16_t id) = 0; + virtual const int getCapability(Capability cap) = 0; + public slots: + + virtual void init() = 0; + virtual void start(const char * filename = NULL, bool tests = true) = 0; + virtual void stop() = 0; + virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = "") = 0; + virtual void setVolumeGain(const int value) = 0; + virtual void setRadioData(const QByteArray & data) = 0; + virtual void setAnalogValue(uint8_t index, int16_t value) = 0; + virtual void setKey(uint8_t key, bool state) = 0; + virtual void setSwitch(uint8_t swtch, int8_t state) = 0; + virtual void setTrim(unsigned int idx, int value) = 0; + virtual void setTrimSwitch(uint8_t trim, bool state) = 0; virtual void setTrainerInput(unsigned int inputNumber, int16_t value) = 0; - - virtual void installTraceHook(void (*callback)(const char *)) = 0; - + virtual void setInputValue(int type, uint8_t index, int16_t value) = 0; + virtual void rotaryEncoderEvent(int steps) = 0; + virtual void setTrainerTimeout(uint16_t ms) = 0; + virtual void sendTelemetry(uint8_t * data, unsigned int len) = 0; virtual void setLuaStateReloadPermanentScripts() = 0; + virtual void addTracebackDevice(QIODevice * device) = 0; + virtual void removeTracebackDevice(QIODevice * device) = 0; + + signals: + + void started(); + void stopped(); + void heartbeat(qint32 loops, qint64 timestamp); + void runtimeError(const QString & error); + void lcdChange(bool backlightEnable); + void phaseChanged(qint8 phase, const QString & name); + void channelOutValueChange(quint8 index, qint32 value); + void channelMixValueChange(quint8 index, qint32 value); + void virtualSwValueChange(quint8 index, qint32 value); + void trimValueChange(quint8 index, qint32 value); + void trimRangeChange(quint8 index, qint32 min, qint16 max); + void gVarValueChange(quint8 index, qint32 value); + void outputValueChange(int type, quint8 index, qint32 value); }; class SimulatorFactory { public: - virtual ~SimulatorFactory() { } - virtual QString name() = 0; - virtual Board::Type type() = 0; - virtual SimulatorInterface *create() = 0; }; diff --git a/companion/src/simulation/simulatormainwindow.cpp b/companion/src/simulation/simulatormainwindow.cpp index 41a708e5c..9fe5e969e 100644 --- a/companion/src/simulation/simulatormainwindow.cpp +++ b/companion/src/simulation/simulatormainwindow.cpp @@ -69,6 +69,9 @@ SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, const QString & firmwa return; } + m_simulator->moveToThread(&simuThread); + simuThread.start(); + ui->setupUi(this); setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); @@ -103,38 +106,42 @@ SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, const QString & firmwa ui->menuView->insertSeparator(ui->actionToggleMenuBar); ui->menuView->insertAction(ui->actionToggleMenuBar, ui->toolBar->toggleViewAction()); - // Hide some actions based on board capabilities. - Firmware * firmware = getCurrentFirmware(); - if(!firmware->getCapability(Capability(LuaInputsPerScript))) + // Hide some actions based on simulator capabilities. + if(!m_simulator->getCapability(SimulatorInterface::CAP_LUA)) ui->actionReloadLua->setDisabled(true); - if (!firmware->getCapability(Capability(SportTelemetry))) + if(!m_simulator->getCapability(SimulatorInterface::CAP_TELEM_FRSKY_SPORT)) m_telemetryDockWidget->toggleViewAction()->setDisabled(true); #ifndef JOYSTICKS ui->actionJoystickSettings->setDisabled(true); #endif - // Add radio-specific help text from simulator widget - foreach (keymapHelp_t item, *m_simulatorWidget->getKeymapHelp()) - m_keymapHelp.append(item); - restoreUiState(); setStyleSheet(SimulatorStyle::styleSheet()); connect(ui->actionShowKeymap, &QAction::triggered, this, &SimulatorMainWindow::showHelp); connect(ui->actionJoystickSettings, &QAction::triggered, this, &SimulatorMainWindow::openJoystickDialog); - connect(ui->actionReloadLua, &QAction::triggered, this, &SimulatorMainWindow::luaReload); connect(ui->actionToggleMenuBar, &QAction::toggled, this, &SimulatorMainWindow::showMenuBar); connect(ui->actionFixedRadioWidth, &QAction::toggled, this, &SimulatorMainWindow::showRadioFixedWidth); connect(ui->actionFixedRadioHeight, &QAction::toggled, this, &SimulatorMainWindow::showRadioFixedHeight); connect(ui->actionDockRadio, &QAction::toggled, this, &SimulatorMainWindow::showRadioDocked); + connect(ui->actionReloadRadioData, &QAction::triggered, this, &SimulatorMainWindow::simulatorRestart); + + connect(ui->actionReloadLua, &QAction::triggered, m_simulator, &SimulatorInterface::setLuaStateReloadPermanentScripts); + + if (m_outputsWidget) { + connect(this, &SimulatorMainWindow::simulatorStart, m_outputsWidget, &RadioOutputsWidget::start); + connect(this, &SimulatorMainWindow::simulatorRestart, m_outputsWidget, &RadioOutputsWidget::restart); + } + if (m_simulatorWidget) { + connect(this, &SimulatorMainWindow::simulatorStart, m_simulatorWidget, &SimulatorWidget::start); + connect(this, &SimulatorMainWindow::simulatorRestart, m_simulatorWidget, &SimulatorWidget::restart); connect(ui->actionScreenshot, &QAction::triggered, m_simulatorWidget, &SimulatorWidget::captureScreenshot); - connect(ui->actionReloadRadioData, &QAction::triggered, m_simulatorWidget, &SimulatorWidget::restart); connect(m_simulatorWidget, &SimulatorWidget::windowTitleChanged, this, &SimulatorMainWindow::setWindowTitle); } - if (m_outputsWidget) - connect(ui->actionReloadRadioData, &QAction::triggered, m_outputsWidget, &RadioOutputsWidget::restart); + + } SimulatorMainWindow::~SimulatorMainWindow() @@ -154,9 +161,11 @@ SimulatorMainWindow::~SimulatorMainWindow() delete ui; - if (m_simulator) + if (m_simulator) { + simuThread.quit(); + simuThread.wait(); delete m_simulator; - + } SimulatorLoader::unloadSimulator(m_simulatorId); } @@ -256,11 +265,6 @@ bool SimulatorMainWindow::setOptions(SimulatorOptions & options, bool withSave) void SimulatorMainWindow::start() { - if (m_simulatorWidget) - m_simulatorWidget->start(); - if (m_outputsWidget) - m_outputsWidget->start(); - emit simulatorStart(); } @@ -282,8 +286,6 @@ void SimulatorMainWindow::createDockWidgets() m_telemetryDockWidget->setWidget(telem); m_telemetryDockWidget->setObjectName("TELEMETRY_SIMULATOR"); addTool(m_telemetryDockWidget, Qt::LeftDockWidgetArea, icon, QKeySequence(tr("F4"))); - connect(this, &SimulatorMainWindow::simulatorStart, telem, &TelemetrySimulator::onSimulatorStarted); - connect(ui->actionReloadRadioData, &QAction::triggered, telem, &TelemetrySimulator::onSimulatorStarted); } if (!m_trainerDockWidget) { @@ -453,13 +455,6 @@ void SimulatorMainWindow::toggleRadioDocked(bool dock) } -void SimulatorMainWindow::luaReload(bool) -{ - // force a reload of the lua environment - if (m_simulator) - m_simulator->setLuaStateReloadPermanentScripts(); -} - void SimulatorMainWindow::openJoystickDialog(bool) { #ifdef JOYSTICKS @@ -472,19 +467,34 @@ void SimulatorMainWindow::openJoystickDialog(bool) void SimulatorMainWindow::showHelp(bool show) { - QString helpText = tr("Simulator Controls:"); + QString helpText = "" + ""; + helpText += tr("Simulator Controls:"); helpText += ""; - helpText += tr(""); - QString keyTemplate = ""; - foreach (keymapHelp_t pair, m_keymapHelp) + helpText += tr("", "note: must match html layout of each table row (keyTemplate)."); + + QString keyTemplate = tr("", "note: must match html layout of help text table header."); + keymapHelp_t pair; + // Add our own help text (if any) + foreach (pair, m_keymapHelp) helpText += keyTemplate.arg(pair.first, pair.second); + // Add any radio-specific help text from simulator widget + foreach (pair, m_simulatorWidget->getKeymapHelp()) + helpText += keyTemplate.arg(pair.first, pair.second); + helpText += "
Key/MouseAction
%1
%2
Key/MouseAction
%1%2
"; QMessageBox * msgBox = new QMessageBox(this); + msgBox->setObjectName("SimulatorHelpText"); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint); msgBox->setStandardButtons( QMessageBox::Ok ); msgBox->setWindowTitle(tr("Simulator Help")); + msgBox->setTextFormat(Qt::RichText); msgBox->setText(helpText); msgBox->setModal(false); msgBox->show(); diff --git a/companion/src/simulation/simulatormainwindow.h b/companion/src/simulation/simulatormainwindow.h index 5e9da9a3f..eb134a63f 100644 --- a/companion/src/simulation/simulatormainwindow.h +++ b/companion/src/simulation/simulatormainwindow.h @@ -25,6 +25,7 @@ #include #include +#include class DebugOutput; class RadioData; @@ -67,6 +68,7 @@ class SimulatorMainWindow : public QMainWindow signals: void simulatorStart(); + void simulatorRestart(); protected slots: virtual void closeEvent(QCloseEvent *); @@ -76,7 +78,6 @@ class SimulatorMainWindow : public QMainWindow void toggleMenuBar(bool show); void setRadioSizePolicy(int fixType); void toggleRadioDocked(bool dock); - void luaReload(bool); void openJoystickDialog(bool); void showHelp(bool show); @@ -97,6 +98,7 @@ class SimulatorMainWindow : public QMainWindow QDockWidget * m_trainerDockWidget; QDockWidget * m_outputsDockWidget; + QThread simuThread; QVector m_keymapHelp; QString m_simulatorId; QString m_exitStatusMsg; diff --git a/companion/src/simulation/simulatorwidget.cpp b/companion/src/simulation/simulatorwidget.cpp index 058c3516a..c687c2226 100644 --- a/companion/src/simulation/simulatorwidget.cpp +++ b/companion/src/simulation/simulatorwidget.cpp @@ -24,12 +24,13 @@ #include "appdata.h" #include "appdebugmessagehandler.h" #include "radiofaderwidget.h" +#include "radiokeywidget.h" #include "radioknobwidget.h" #include "radioswitchwidget.h" +#include "radiotrimwidget.h" #include "radiouiaction.h" #include "sdcard.h" #include "simulateduiwidget.h" -#include "simulatorinterface.h" #include "storage.h" #include "virtualjoystickwidget.h" #ifdef JOYSTICKS @@ -41,36 +42,27 @@ #include #include -SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface *simulator, quint8 flags): +using namespace Simulator; + +SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface * simulator, quint8 flags): QWidget(parent), ui(new Ui::SimulatorWidget), simulator(simulator), firmware(getCurrentFirmware()), radioSettings(GeneralSettings()), - timer(new QTimer(this)), radioUiWidget(NULL), vJoyLeft(NULL), vJoyRight(NULL), m_board(getCurrentBoard()), flags(flags), - lastPhase(-1), - buttonPressed(0), - trimPressed(255), startupFromFile(false), deleteTempRadioData(false), - saveTempRadioData(false), - middleButtonPressed(false), - firstShow(true) -{ - + saveTempRadioData(false) #ifdef JOYSTICKS - joystick = NULL; + , joystick(NULL) #endif - // defaults - setRadioProfileId(g.sessionId()); - setSdPath(g.profile[radioProfileId].sdPath()); - +{ ui->setupUi(this); windowName = tr("Radio Simulator (%1)").arg(firmware->getName()); @@ -96,7 +88,7 @@ SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface *simulator break; } - foreach (keymapHelp_t item, *radioUiWidget->getKeymapHelp()) + foreach (keymapHelp_t item, radioUiWidget->getKeymapHelp()) keymapHelp.append(item); ui->radioUiWidget->layout()->removeItem(ui->radioUiTempSpacer); @@ -105,6 +97,7 @@ SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface *simulator radioUiWidget->setFocusPolicy(Qt::WheelFocus); radioUiWidget->setFocus(); + connect(radioUiWidget, &SimulatedUIWidget::controlValueChange, this, &SimulatorWidget::onRadioWidgetValueChange); connect(radioUiWidget, &SimulatedUIWidget::customStyleRequest, this, &SimulatorWidget::setUiAreaStyle); vJoyLeft = new VirtualJoystickWidget(this, 'L'); @@ -113,25 +106,43 @@ SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface *simulator vJoyRight = new VirtualJoystickWidget(this, 'R'); ui->rightStickLayout->addWidget(vJoyRight); - connect(vJoyLeft, &VirtualJoystickWidget::trimButtonPressed, this, &SimulatorWidget::onTrimPressed); - connect(vJoyLeft, &VirtualJoystickWidget::trimButtonReleased, this, &SimulatorWidget::onTrimReleased); - connect(vJoyLeft, &VirtualJoystickWidget::trimSliderMoved, this, &SimulatorWidget::onTrimSliderMoved); + connect(vJoyLeft, &VirtualJoystickWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange); + connect(vJoyRight, &VirtualJoystickWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange); + connect(this, &SimulatorWidget::stickModeChange, vJoyLeft, &VirtualJoystickWidget::loadDefaultsForMode); + connect(this, &SimulatorWidget::stickModeChange, vJoyRight, &VirtualJoystickWidget::loadDefaultsForMode); + connect(simulator, &SimulatorInterface::trimValueChange, vJoyLeft, &VirtualJoystickWidget::setTrimValue); + connect(simulator, &SimulatorInterface::trimValueChange, vJoyRight, &VirtualJoystickWidget::setTrimValue); + connect(simulator, &SimulatorInterface::trimRangeChange, vJoyLeft, &VirtualJoystickWidget::setTrimRange); + connect(simulator, &SimulatorInterface::trimRangeChange, vJoyRight, &VirtualJoystickWidget::setTrimRange); - connect(vJoyRight, &VirtualJoystickWidget::trimButtonPressed, this, &SimulatorWidget::onTrimPressed); - connect(vJoyRight, &VirtualJoystickWidget::trimButtonReleased, this, &SimulatorWidget::onTrimReleased); - connect(vJoyRight, &VirtualJoystickWidget::trimSliderMoved, this, &SimulatorWidget::onTrimSliderMoved); + connect(this, &SimulatorWidget::simulatorInit, simulator, &SimulatorInterface::init); + connect(this, &SimulatorWidget::simulatorStart, simulator, &SimulatorInterface::start); + connect(this, &SimulatorWidget::simulatorStop, simulator, &SimulatorInterface::stop); + //connect(this, &SimulatorWidget::simulatorSetData, simulator, &SimulatorInterface::setRadioData); + connect(this, &SimulatorWidget::inputValueChange, simulator, &SimulatorInterface::setInputValue); + connect(this, &SimulatorWidget::simulatorSdPathChange, simulator, &SimulatorInterface::setSdPath); + connect(this, &SimulatorWidget::simulatorVolumeGainChange, simulator, &SimulatorInterface::setVolumeGain); - timer->setInterval(10); - connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); - connect(timer, SIGNAL(timeout()), radioUiWidget, SLOT(updateUi())); + connect(simulator, &SimulatorInterface::started, this, &SimulatorWidget::onSimulatorStarted); + connect(simulator, &SimulatorInterface::stopped, this, &SimulatorWidget::onSimulatorStopped); + connect(simulator, &SimulatorInterface::heartbeat, this, &SimulatorWidget::onSimulatorHeartbeat); + connect(simulator, &SimulatorInterface::runtimeError, this, &SimulatorWidget::onSimulatorError); + connect(simulator, &SimulatorInterface::phaseChanged, this, &SimulatorWidget::onPhaseChanged); + + m_timer.setInterval(SIMULATOR_INTERFACE_HEARTBEAT_PERIOD * 6); + connect(&m_timer, &QTimer::timeout, this, &SimulatorWidget::onTimerEvent); + + setupJoysticks(); + + // defaults + setRadioProfileId(g.sessionId()); + setSdPath(g.profile[radioProfileId].sdPath()); } SimulatorWidget::~SimulatorWidget() { shutdown(); - if (timer) - timer->deleteLater(); if (radioUiWidget) delete radioUiWidget; if (vJoyLeft) @@ -142,10 +153,7 @@ SimulatorWidget::~SimulatorWidget() if (joystick) delete joystick; #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. - + firmware = NULL; delete ui; } @@ -168,8 +176,7 @@ void SimulatorWidget::setPaths(const QString & sdPath, const QString & dataPath) { sdCardPath = sdPath; radioDataPath = dataPath; - if (simulator) - simulator->setSdPath(sdPath, dataPath); + emit simulatorSdPathChange(sdPath, dataPath); } void SimulatorWidget::setRadioSettings(const GeneralSettings settings) @@ -264,7 +271,7 @@ bool SimulatorWidget::setRadioData(RadioData * radioData) if (ret) { if (radioDataPath.isEmpty()) { - startupData.fill(0, getEEpromSize(m_board)); + startupData.fill(0, Boards::getEEpromSize(m_board)); if (firmware->getEEpromInterface()->save((uint8_t *)startupData.data(), *radioData, 0, firmware->getCapability(SimulatorVariant)) <= 0) { ret = false; } @@ -410,8 +417,7 @@ void SimulatorWidget::deleteTempData() void SimulatorWidget::saveState() { SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions(); - //opts.windowGeometry = saveGeometry(); - opts.controlsState = saveRadioWidgetsState(); + saveRadioWidgetsState(opts.controlsState); g.profile[radioProfileId].simulatorOptions(opts); } @@ -432,35 +438,58 @@ void SimulatorWidget::captureScreenshot(bool) void SimulatorWidget::start() { + emit simulatorInit(); // init simulator default I/O values + setupRadioWidgets(); - setupJoysticks(); restoreRadioWidgetsState(); - if (startupData.isEmpty()) - simulator->start((const char *)0); - else if (startupFromFile) - simulator->start(startupData.constData()); - else - simulator->start(startupData, (flags & SIMULATOR_FLAGS_NOTX) ? false : true); - - setTrims(); - getValues(); - setupTimer(); + bool tests = !(flags & SIMULATOR_FLAGS_NOTX); + if (!startupData.isEmpty()) { + if (startupFromFile) { + emit simulatorStart(startupData.constData(), tests); + } + else { + simulator->setRadioData(startupData); + emit simulatorStart((const char *)0, tests); + } + } + else { + emit simulatorStart((const char *)0, tests); + } } void SimulatorWidget::stop() { - if (timer) - timer->stop(); - if (simulator) { - simulator->stop(); - if (saveTempRadioData) { - startupData.fill(0, getEEpromSize(m_board)); - simulator->readEepromData(startupData); + emit simulatorStop(); + QElapsedTimer tmout; + tmout.start(); + // block until simulator stops or times out + while (simulator->isRunning()) { + if (tmout.hasExpired(2000)) { + onSimulatorError("Timeout while trying to stop simulation!"); + onSimulatorStopped(); + return; } } } +void SimulatorWidget::onSimulatorStarted() +{ + m_heartbeatTimer.start(); + m_timer.start(); +} + +void SimulatorWidget::onSimulatorStopped() +{ + m_timer.stop(); + m_heartbeatTimer.invalidate(); + + if (simulator && !simulator->isRunning() && saveTempRadioData) { + startupData.fill(0, getEEpromSize(m_board)); + simulator->readRadioData(startupData); + } +} + void SimulatorWidget::restart() { stop(); @@ -486,100 +515,112 @@ void SimulatorWidget::shutdown() void SimulatorWidget::setRadioProfileId(int value) { radioProfileId = value; - if (simulator) - simulator->setVolumeGain(g.profile[radioProfileId].volumeGain()); + emit simulatorVolumeGainChange(g.profile[radioProfileId].volumeGain()); } void SimulatorWidget::setupRadioWidgets() { - int i, midpos, aIdx; QString wname; - Board::Type board = firmware->getBoard(); + int i, midpos; + const int ttlSticks = Boards::getCapability(m_board, Board::Sticks); + const int ttlSwitches = Boards::getCapability(m_board, Board::Switches); + const int ttlKnobs = Boards::getCapability(m_board, Board::Pots); + const int ttlFaders = Boards::getCapability(m_board, Board::Sliders); + const int extraTrims = Boards::getCapability(m_board, Board::NumTrims) - ttlSticks; // First clear out any existing widgets. - if (switches.size()) { - foreach (RadioWidget * w, switches) { - ui->radioWidgetsHTLayout->removeWidget(w); - w->deleteLater(); + foreach (RadioWidget * rw, m_radioWidgets) { + switch(rw->getType()) { + case RadioWidget::RADIO_WIDGET_SWITCH : + case RadioWidget::RADIO_WIDGET_KNOB : + ui->radioWidgetsHTLayout->removeWidget(rw); + break; + case RadioWidget::RADIO_WIDGET_FADER : + case RadioWidget::RADIO_WIDGET_TRIM : + ui->VCGridLayout->removeWidget(rw); + break; + default : + break; } - switches.clear(); - } - if (analogs.size()) { - foreach (RadioWidget * w, analogs) { - if (w->getType() == RadioWidget::RADIO_WIDGET_KNOB) - ui->radioWidgetsHTLayout->removeWidget(w); - else - ui->VCGridLayout->removeWidget(w); - w->deleteLater(); - } - analogs.clear(); + disconnect(rw, 0, this, 0); + disconnect(this, 0, rw, 0); + rw->deleteLater(); } + m_radioWidgets.clear(); // Now set up new widgets. // switches - Board::SwitchInfo switchInfo; Board::SwitchType swcfg; - for (i = 0; i < getBoardCapability(board, Board::Switches) && i < CPN_MAX_SWITCHES; ++i) { + for (i = 0; i < ttlSwitches; ++i) { if (radioSettings.switchConfig[i] == Board::SWITCH_NOT_AVAILABLE) continue; swcfg = Board::SwitchType(radioSettings.switchConfig[i]); - - if ((wname = QString(radioSettings.switchName[i])).isEmpty()) { - switchInfo = getSwitchInfo(board, i); - wname = QString(switchInfo.name); - } + wname = RawSource(RawSourceType::SOURCE_TYPE_SWITCH, i).toString(NULL, &radioSettings); RadioSwitchWidget * sw = new RadioSwitchWidget(swcfg, wname, -1, ui->radioWidgetsHT); sw->setIndex(i); ui->radioWidgetsHTLayout->addWidget(sw); - switches.append(sw); + + m_radioWidgets.append(sw); } - midpos = (int)floorf(switches.size() / 2.0f); - aIdx = 0; + midpos = (int)floorf(m_radioWidgets.size() / 2.0f); // pots in middle of switches - for (i = 0; i < getBoardCapability(board, Board::Pots) && i < CPN_MAX_POTS; ++i) { + for (i = 0; i < ttlKnobs; ++i) { if (!radioSettings.isPotAvailable(i)) continue; - if ((wname = QString(radioSettings.potName[i])).isEmpty()) - wname = firmware->getAnalogInputName(4 + aIdx + i); - + wname = RawSource(RawSourceType::SOURCE_TYPE_STICK, ttlSticks + i).toString(NULL, &radioSettings); RadioKnobWidget * pot = new RadioKnobWidget(Board::PotType(radioSettings.potConfig[i]), wname, 0, ui->radioWidgetsHT); - pot->setIndex(aIdx + i); - // FIXME : total hack here -- this needs to follow the exception in radio/src/mixer.cpp:evalInputs() - if (i == 0 && IS_TARANIS(board) && !IS_TARANIS_X7(board)) - pot->setInvertValue(true); + pot->setIndex(i); ui->radioWidgetsHTLayout->insertWidget(midpos++, pot); - analogs.append(pot); + + m_radioWidgets.append(pot); } - aIdx += i; - // faders between sticks - int r = 0, c = 0; - for (i = 0; i < getBoardCapability(board, Board::Sliders) && i + aIdx < CPN_MAX_POTS; ++i) { + int c = extraTrims / 2; // leave space for any extra trims + for (i = 0; i < ttlFaders; ++i) { if (!radioSettings.isSliderAvailable(i)) continue; - if ((wname = QString(radioSettings.sliderName[i])).isEmpty()) - wname = firmware->getAnalogInputName(4 + aIdx + i); - + wname = RawSource(RawSourceType::SOURCE_TYPE_STICK, ttlSticks + ttlKnobs + i).toString(NULL, &radioSettings); RadioFaderWidget * sl = new RadioFaderWidget(wname, 0, ui->radioWidgetsVC); - sl->setIndex(aIdx + i); - // FIXME : total hack here -- this needs to follow the exception in radio/src/mixer.cpp:evalInputs() - if (i == 0 && IS_TARANIS(board) && !IS_TARANIS_X7(board)) - sl->setInvertValue(true); - /* 2-row option - if (!(i % 2)) { - ++r; - c = 0; - } */ - ui->VCGridLayout->addWidget(sl, r, c++, 1, 1); - analogs.append(sl); + sl->setIndex(i); + ui->VCGridLayout->addWidget(sl, 0, c++, 1, 1); + + m_radioWidgets.append(sl); } + + // extra trims around faders + int tridx = Board::TRIM_AXIS_COUNT; + int trswidx = Board::TRIM_SW_COUNT; + for (i = extraTrims; i > 0; --i) { + trswidx -= 2; + --tridx; + wname = RawSource(RawSourceType::SOURCE_TYPE_TRIM, tridx).toString(NULL, &radioSettings); + wname = wname.left(1) % wname.right(1); + RadioTrimWidget * tw = new RadioTrimWidget(Qt::Vertical, ui->radioWidgetsVC); + tw->setIndices(tridx, trswidx, trswidx + 1); + tw->setLabelText(wname); + if (i <= extraTrims / 2) + c = 0; + ui->VCGridLayout->addWidget(tw, 0, c++, 1, 1); + + connect(simulator, &SimulatorInterface::trimValueChange, tw, &RadioTrimWidget::setTrimValue); + connect(simulator, &SimulatorInterface::trimRangeChange, tw, &RadioTrimWidget::setTrimRangeQual); + m_radioWidgets.append(tw); + } + + // connect all the widgets + foreach (RadioWidget * rw, m_radioWidgets) { + connect(rw, &RadioWidget::valueChange, this, &SimulatorWidget::onRadioWidgetValueChange); + connect(this, &SimulatorWidget::widgetValueChange, rw, &RadioWidget::setValueQual); + connect(this, &SimulatorWidget::widgetStateChange, rw, &RadioWidget::setStateData); + } + } void SimulatorWidget::setupJoysticks() @@ -600,6 +641,10 @@ void SimulatorWidget::setupJoysticks() joystick->deadzones[j] = 0; } connect(joystick, &Joystick::axisValueChanged, this, &SimulatorWidget::onjoystickAxisValueChanged); + if (vJoyLeft) + connect(this, &SimulatorWidget::stickValueChange, vJoyLeft, &VirtualJoystickWidget::setStickAxisValue); + if (vJoyRight) + connect(this, &SimulatorWidget::stickValueChange, vJoyRight, &VirtualJoystickWidget::setStickAxisValue); joysticksEnabled = true; } else { @@ -609,164 +654,50 @@ void SimulatorWidget::setupJoysticks() else if (joystick) { joystick->close(); disconnect(joystick, 0, this, 0); + if (vJoyLeft) + disconnect(this, 0, vJoyLeft, 0); + if (vJoyRight) + disconnect(this, 0, vJoyRight, 0); joystick->deleteLater(); joystick = NULL; } - if (vJoyRight) { - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, joysticksEnabled); - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, joysticksEnabled); - } - if (vJoyLeft) { - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, joysticksEnabled); - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, joysticksEnabled); - } + if (vJoyRight) + vJoyRight->setStickConstraint((VirtualJoystickWidget::HOLD_X | VirtualJoystickWidget::HOLD_Y), joysticksEnabled); + if (vJoyLeft) + vJoyLeft->setStickConstraint((VirtualJoystickWidget::HOLD_X | VirtualJoystickWidget::HOLD_Y), joysticksEnabled); #endif } -void SimulatorWidget::setupTimer() -{ - if (!timer) - return; - - if (timer->isActive()) - timer->stop(); - - timer->start(); -} - void SimulatorWidget::restoreRadioWidgetsState() { + // All RadioWidgets RadioWidget::RadioWidgetState state; - QMap switchesMap; - QMap analogsMap; QList states = g.profile[radioProfileId].simulatorOptions().controlsState; - foreach (QByteArray ba, states) { QDataStream stream(ba); stream >> state; - if (state.type == RadioWidget::RADIO_WIDGET_SWITCH) - switchesMap.insert(state.index, ba); - else - analogsMap.insert(state.index, ba); - } - - for (int i = 0; i < analogs.size(); ++i) { - if (analogsMap.contains(analogs[i]->getIndex())) - analogs[i]->setStateData(analogsMap.value(analogs[i]->getIndex())); - } - - for (int i = 0; i < switches.size(); ++i) { - if (switchesMap.contains(switches[i]->getIndex())) - switches[i]->setStateData(switchesMap.value(switches[i]->getIndex())); + emit widgetStateChange(state); } // Set throttle stick down and locked, side depends on mode - if (radioSettings.stickMode & 1) { - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyLeft->setStickY(1); - } - else { - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyRight->setStickY(1); - } + emit stickModeChange(radioSettings.stickMode); + + // TODO : custom voltages + qint16 volts = radioSettings.vBatWarn + 2; + if (firmware->getCapability(Capability::HasBatMeterRange)) + volts = (radioSettings.vBatMin + 90) + ((radioSettings.vBatMax + 30) - radioSettings.vBatMin) / 2; + emit inputValueChange(SimulatorInterface::INPUT_SRC_TXVIN, 0, volts); } -QList SimulatorWidget::saveRadioWidgetsState() +void SimulatorWidget::saveRadioWidgetsState(QList & state) { - QList states; - - for (int i = 0; i < analogs.size(); ++i) - states.append(analogs[i]->getStateData()); - - for (int i = 0; i < switches.size(); ++i) - states.append(switches[i]->getStateData()); - - return states; -} - -/* - * Input/Output handlers for SimulatorInterface - */ - -// Read various values from firmware simulator and populate values in this UI -void SimulatorWidget::setValues() -{ - int currentPhase = simulator->getPhase(); - - // display current flight mode in window title - if (currentPhase != lastPhase) { - lastPhase = 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)); + if (m_radioWidgets.size()) { + state.clear(); + foreach (RadioWidget * rw, m_radioWidgets) + state.append(rw->getStateData()); } } -// "get" values from this UI and send them to the firmware simulator. -void SimulatorWidget::getValues() -{ - static const int numTrims = getBoardCapability(m_board, Board::NumTrimSwitches); - int i; - - TxInputs inp; - memset(&inp, 0, sizeof(TxInputs)); - - inp.sticks[0] = int(1024 * vJoyLeft->getStickX()); // LEFT HORZ - inp.sticks[1] = int(-1024 * vJoyLeft->getStickY()); // LEFT VERT - inp.sticks[2] = int(-1024 * vJoyRight->getStickY()); // RGHT VERT - inp.sticks[3] = int(1024 * vJoyRight->getStickX()); // RGHT HORZ - - for (i = 0; i < analogs.size() && i < CPN_MAX_POTS; ++i) - inp.pots[analogs[i]->getIndex()] = analogs[i]->getValue(); - - for (i = 0; i < switches.size() && i < CPN_MAX_SWITCHES; ++i) - inp.switches[switches[i]->getIndex()] = switches[i]->getValue(); - - for (i = 0; i < numTrims; ++i) - inp.trims[i] = (trimPressed == i); - - foreach (RadioUiAction * act, radioUiWidget->getActions()) { - if (act->getIndex() > -1 && act->getIndex() < CPN_MAX_KEYS) - inp.keys[act->getIndex()] = act->isActive(); - } - - if (radioUiWidget->getRotEncAction()) - inp.rotenc = radioUiWidget->getRotEncAction()->isActive(); - - simulator->setValues(inp); -} - -// Read stick trim values from firmware simulator and set joystick widgets as needed. -void SimulatorWidget::setTrims() -{ - using namespace Board; - static Trims lastTrims; - Trims trims; - simulator->getTrims(trims); - - if (trims.values[TRIM_AXIS_LH] != lastTrims.values[TRIM_AXIS_LH]) - vJoyLeft->setTrimValue(TRIM_AXIS_LH, trims.values[TRIM_AXIS_LH]); - if (trims.values[TRIM_AXIS_LV] != lastTrims.values[TRIM_AXIS_LV]) - vJoyLeft->setTrimValue(TRIM_AXIS_LV, trims.values[TRIM_AXIS_LV]); - if (trims.values[TRIM_AXIS_RV] != lastTrims.values[TRIM_AXIS_RV]) - vJoyRight->setTrimValue(TRIM_AXIS_RV, trims.values[TRIM_AXIS_RV]); - if (trims.values[TRIM_AXIS_RH] != lastTrims.values[TRIM_AXIS_RH]) - vJoyRight->setTrimValue(TRIM_AXIS_RH, trims.values[TRIM_AXIS_RH]); - - if (trims.extended != lastTrims.extended) { - int trimMin = -125, trimMax = +125; - if (trims.extended) { - trimMin = -500; - trimMax = +500; - } - vJoyLeft->setTrimRange(TRIM_AXIS_LH, trimMin, trimMax); - vJoyLeft->setTrimRange(TRIM_AXIS_LV, trimMin, trimMax); - vJoyRight->setTrimRange(TRIM_AXIS_RV, trimMin, trimMax); - vJoyRight->setTrimRange(TRIM_AXIS_RH, trimMin, trimMax); - } - lastTrims = trims; -} /* * Event handlers/private slots @@ -792,51 +723,95 @@ void SimulatorWidget::wheelEvent(QWheelEvent *event) void SimulatorWidget::onTimerEvent() { - static unsigned int lcd_counter = 0; + if (m_heartbeatTimer.isValid() && m_heartbeatTimer.hasExpired(m_timer.interval())) { + onSimulatorError("Heartbeat timeout!"); + onSimulatorStopped(); + } +} - if (!simulator->timer10ms()) { - QMessageBox::critical(this, "Companion", tr("Firmware %1 error: %2").arg(firmware->getName()).arg(simulator->getError())); - timer->stop(); +void SimulatorWidget::onSimulatorHeartbeat(qint32 loops, qint64 timestamp) +{ + Q_UNUSED(loops) + Q_UNUSED(timestamp) + m_heartbeatTimer.start(); + +#if 0 + static qint64 lastTs = 0; + if (!(loops % 1000)) { + qDebug() << "loops:" << loops << "ts:" << timestamp << "ts-delta:" << timestamp - lastTs << "This:" << QThread::currentThread() << "Simu:" << simulator->thread(); + lastTs = timestamp; + } +#endif +} + +void SimulatorWidget::onSimulatorError(const QString & error) +{ + QMessageBox::critical(this, windowName, tr("Radio firmware error: %1").arg(error.isEmpty() ? "Unknown reason" : error)); +} + +void SimulatorWidget::onPhaseChanged(qint32 phase, const QString & name) +{ + setWindowTitle(windowName + tr(" - Flight Mode %1 (#%2)").arg(name).arg(phase)); +} + +void SimulatorWidget::onRadioWidgetValueChange(const RadioWidget::RadioWidgetType type, const int index, int value) +{ + //qDebug() << type << index << value; + if (!simulator || index < 0) return; + + SimulatorInterface::InputSourceType inpType = SimulatorInterface::INPUT_SRC_NONE; + + switch (type) { + case RadioWidget::RADIO_WIDGET_SWITCH : + inpType = SimulatorInterface::INPUT_SRC_SWITCH; + break; + + case RadioWidget::RADIO_WIDGET_KNOB : + inpType = SimulatorInterface::INPUT_SRC_KNOB; + break; + + case RadioWidget::RADIO_WIDGET_FADER : + inpType = SimulatorInterface::INPUT_SRC_SLIDER; + break; + + case RadioWidget::RADIO_WIDGET_TRIM : + switch (value) { + case RadioWidget::RADIO_TRIM_BTN_ON : + inpType = SimulatorInterface::INPUT_SRC_TRIM_SW; + value = 1; + break; + case RadioWidget::RADIO_TRIM_BTN_OFF : + inpType = SimulatorInterface::INPUT_SRC_TRIM_SW; + value = 0; + break; + default : + inpType = SimulatorInterface::INPUT_SRC_TRIM; + break; + } + break; + + case RadioWidget::RADIO_WIDGET_STICK : + inpType = SimulatorInterface::INPUT_SRC_STICK; + break; + + case RadioWidget::RADIO_WIDGET_KEY : + inpType = SimulatorInterface::INPUT_SRC_KEY; + break; + + default : + return; } - getValues(); - - if (!(lcd_counter++ % 5)) { - setValues(); - setTrims(); - centerSticks(); - } -} - -void SimulatorWidget::onTrimPressed(int index) -{ - trimPressed = index; -} - -void SimulatorWidget::onTrimReleased(int) -{ - trimPressed = 255; -} - -void SimulatorWidget::onTrimSliderMoved(int index, int value) -{ - simulator->setTrim(index, value); -} - -void SimulatorWidget::centerSticks() -{ - if (vJoyLeft) - vJoyLeft->centerStick(); - - if (vJoyRight) - vJoyRight->centerStick(); + emit inputValueChange(inpType, index, value); } void SimulatorWidget::onjoystickAxisValueChanged(int axis, int value) { #ifdef JOYSTICKS - static const int ttlSticks = CPN_MAX_STICKS; + static const int ttlSticks = 4; + static const int ttlKnobs = Boards::getCapability(m_board, Board::Pots); + static const int ttlFaders = Boards::getCapability(m_board, Board::Sliders); static const int valueRange = 1024; if (!joystick || axis >= MAX_JOYSTICKS) @@ -845,7 +820,7 @@ void SimulatorWidget::onjoystickAxisValueChanged(int axis, int value) int dlta; int stick = g.joystick[axis].stick_axe(); - if (stick < 0 || stick >= ttlSticks + analogs.count()) + if (stick < 0 || stick >= ttlSticks + ttlKnobs + ttlFaders) return; int stickval = valueRange * (value - g.joystick[axis].stick_med()); @@ -861,20 +836,15 @@ void SimulatorWidget::onjoystickAxisValueChanged(int axis, int value) if (g.joystick[axis].stick_inv()) stickval *= -1; - if (stick==1 ) { - vJoyRight->setStickY(-stickval/1024.0); + if (stick < ttlSticks) { + emit stickValueChange(stick, stickval); } - else if (stick==2) { - vJoyRight->setStickX(stickval/1024.0); - } - else if (stick==3) { - vJoyLeft->setStickY(-stickval/1024.0); - } - else if (stick==4) { - vJoyLeft->setStickX(stickval/1024.0); - } - else if (stick > ttlSticks) { - analogs[stick-ttlSticks-1]->setValue(stickval); + else { + stick -= ttlSticks; + if (stick < ttlKnobs) + emit widgetValueChange(RadioWidget::RADIO_WIDGET_KNOB, stick, stickval); + else + emit widgetValueChange(RadioWidget::RADIO_WIDGET_FADER, stick, stickval); } #endif diff --git a/companion/src/simulation/simulatorwidget.h b/companion/src/simulation/simulatorwidget.h index e7398039a..e3377084b 100644 --- a/companion/src/simulation/simulatorwidget.h +++ b/companion/src/simulation/simulatorwidget.h @@ -24,24 +24,20 @@ #include "constants.h" #include "helpers.h" #include "radiodata.h" +#include "radiowidget.h" #include "simulator.h" +#include "simulatorinterface.h" +#include #include #include #include -#define FLASH_DURATION 10 - -#define CBEEP_ON "QLabel { background-color: #FF364E }" -#define CBEEP_OFF "QLabel { }" - void traceCb(const char * text); class Firmware; class SimulatorInterface; class SimulatedUIWidget; -class RadioWidget; -class RadioSwitchWidget; class VirtualJoystickWidget; #ifdef JOYSTICKS class Joystick; @@ -56,8 +52,6 @@ namespace Ui { class SimulatorWidget; } -using namespace Simulator; - class SimulatorWidget : public QWidget { Q_OBJECT @@ -84,7 +78,7 @@ class SimulatorWidget : public QWidget QString getSdPath() const { return sdCardPath; } QString getDataPath() const { return radioDataPath; } - QVector * getKeymapHelp() { return &keymapHelp; } + QVector getKeymapHelp() const { return keymapHelp; } public slots: void start(); @@ -92,52 +86,18 @@ class SimulatorWidget : public QWidget void restart(); void shutdown(); - private: - void setRadioProfileId(int value); - void setupRadioWidgets(); - void setupTimer(); - void restoreRadioWidgetsState(); - QList saveRadioWidgetsState(); - - void setValues(); - void getValues(); - void setTrims(); - - - Ui::SimulatorWidget * ui; - SimulatorInterface * simulator; - Firmware * firmware; - GeneralSettings radioSettings; - - QTimer * timer; - QString windowName; - QVector keymapHelp; - - SimulatedUIWidget * radioUiWidget; - VirtualJoystickWidget * vJoyLeft; - VirtualJoystickWidget * vJoyRight; - - QVector switches; - QVector analogs; - - QString sdCardPath; - QString radioDataPath; - QByteArray startupData; - Board::Type m_board; - quint8 flags; - int radioProfileId; - int lastPhase; - int buttonPressed; - int trimPressed; - bool startupFromFile; - bool deleteTempRadioData; - bool saveTempRadioData; - bool middleButtonPressed; - bool firstShow; - -#ifdef JOYSTICKS - Joystick *joystick; -#endif + signals: + void stickValueChange(int axis, int value); + void stickModeChange(const unsigned mode); + void widgetValueChange(const RadioWidget::RadioWidgetType type, const int index, const int value); + void widgetStateChange(const RadioWidget::RadioWidgetState & state); + void inputValueChange(int type, quint8 index, qint16 value); + void simulatorSetData(const QByteArray & data); + void simulatorInit(); + void simulatorStart(const char * filename, bool tests); + void simulatorStop(); + void simulatorSdPathChange(const QString & sdPath, const QString & dataPath); + void simulatorVolumeGainChange(const int gain); private slots: virtual void mousePressEvent(QMouseEvent *event); @@ -145,12 +105,50 @@ class SimulatorWidget : public QWidget virtual void wheelEvent(QWheelEvent *event); void onTimerEvent(); - void onTrimPressed(int index); - void onTrimReleased(int); - void onTrimSliderMoved(int index, int value); - void centerSticks(); + void onSimulatorStarted(); + void onSimulatorStopped(); + void onSimulatorHeartbeat(qint32 loops, qint64 timestamp); + void onPhaseChanged(qint32 phase, const QString & name); + void onSimulatorError(const QString & error); + void onRadioWidgetValueChange(const RadioWidget::RadioWidgetType type, const int index, int value); void onjoystickAxisValueChanged(int axis, int value); + void setRadioProfileId(int value); + void setupRadioWidgets(); + void restoreRadioWidgetsState(); + + private: + void saveRadioWidgetsState(QList & state); + + Ui::SimulatorWidget * ui; + SimulatorInterface * simulator; + Firmware * firmware; + GeneralSettings radioSettings; + + QTimer m_timer; + QString windowName; + QVector keymapHelp; + QElapsedTimer m_heartbeatTimer; + + SimulatedUIWidget * radioUiWidget; + VirtualJoystickWidget * vJoyLeft; + VirtualJoystickWidget * vJoyRight; + QVector m_radioWidgets; + + QString sdCardPath; + QString radioDataPath; + QByteArray startupData; + Board::Type m_board; + quint8 flags; + int radioProfileId; + bool startupFromFile; + bool deleteTempRadioData; + bool saveTempRadioData; + +#ifdef JOYSTICKS + Joystick *joystick; +#endif + }; #endif // _SIMULATORWIDGET_H_ diff --git a/companion/src/simulation/telemetrysimu.cpp b/companion/src/simulation/telemetrysimu.cpp index 258b6cee0..28e330a72 100644 --- a/companion/src/simulation/telemetrysimu.cpp +++ b/companion/src/simulation/telemetrysimu.cpp @@ -27,25 +27,13 @@ TelemetrySimulator::TelemetrySimulator(QWidget * parent, SimulatorInterface * simulator): QWidget(parent), ui(new Ui::TelemetrySimulator), - simulator(simulator) + simulator(simulator), + m_simuStarted(false), + m_telemEnable(false), + m_logReplayEnable(false) { ui->setupUi(this); - timer.setInterval(10); - connect(&timer, &QTimer::timeout, this, &TelemetrySimulator::generateTelemetryFrame); - - connect(&logTimer, &QTimer::timeout, this, &TelemetrySimulator::onLogTimerEvent); - - connect(ui->Simulate, SIGNAL(toggled(bool)), this, SLOT(onSimulateToggled(bool))); - connect(ui->loadLogFile, SIGNAL(released()), this, SLOT(onLoadLogFile())); - connect(ui->play, SIGNAL(released()), this, SLOT(onPlay())); - connect(ui->rewind, SIGNAL(clicked()), this, SLOT(onRewind())); - connect(ui->stepForward, SIGNAL(clicked()), this, SLOT(onStepForward())); - connect(ui->stepBack, SIGNAL(clicked()), this, SLOT(onStepBack())); - connect(ui->stop, SIGNAL(clicked()), this, SLOT(onStop())); - connect(ui->positionIndicator, SIGNAL(valueChanged(int)), this, SLOT(onPositionIndicatorChanged(int))); - connect(ui->replayRate, SIGNAL(valueChanged(int)), this, SLOT(onReplayRateChanged(int))); - ui->A1->setSpecialValueText(" "); ui->A2->setSpecialValueText(" "); ui->A3->setSpecialValueText(" "); @@ -57,7 +45,27 @@ TelemetrySimulator::TelemetrySimulator(QWidget * parent, SimulatorInterface * si ui->A1_ratio->setEnabled(false); ui->A2_ratio->setEnabled(false); + + timer.setInterval(10); + connect(&timer, &QTimer::timeout, this, &TelemetrySimulator::generateTelemetryFrame); + + connect(&logTimer, &QTimer::timeout, this, &TelemetrySimulator::onLogTimerEvent); + logPlayback = new LogPlaybackController(ui); + + connect(ui->Simulate, SIGNAL(toggled(bool)), this, SLOT(onSimulateToggled(bool))); + connect(ui->loadLogFile, SIGNAL(released()), this, SLOT(onLoadLogFile())); + connect(ui->play, SIGNAL(released()), this, SLOT(onPlay())); + connect(ui->rewind, SIGNAL(clicked()), this, SLOT(onRewind())); + connect(ui->stepForward, SIGNAL(clicked()), this, SLOT(onStepForward())); + connect(ui->stepBack, SIGNAL(clicked()), this, SLOT(onStepBack())); + connect(ui->stop, SIGNAL(clicked()), this, SLOT(onStop())); + connect(ui->positionIndicator, SIGNAL(valueChanged(int)), this, SLOT(onPositionIndicatorChanged(int))); + connect(ui->replayRate, SIGNAL(valueChanged(int)), this, SLOT(onReplayRateChanged(int))); + + connect(this, &TelemetrySimulator::telemetryDataChanged, simulator, &SimulatorInterface::sendTelemetry); + connect(simulator, &SimulatorInterface::started, this, &TelemetrySimulator::onSimulatorStarted); + connect(simulator, &SimulatorInterface::stopped, this, &TelemetrySimulator::onSimulatorStopped); } TelemetrySimulator::~TelemetrySimulator() @@ -70,9 +78,15 @@ TelemetrySimulator::~TelemetrySimulator() void TelemetrySimulator::onSimulatorStarted() { + m_simuStarted = true; setupDataFields(); } +void TelemetrySimulator::onSimulatorStopped() +{ + m_simuStarted = false; +} + void TelemetrySimulator::onSimulateToggled(bool isChecked) { if (isChecked) { @@ -149,13 +163,23 @@ void TelemetrySimulator::onReplayRateChanged(int value) } } -void TelemetrySimulator::closeEvent(QCloseEvent *event) +void TelemetrySimulator::hideEvent(QHideEvent *event) { - timer.stop(); - logTimer.stop(); + m_telemEnable = ui->Simulate->isChecked(); + m_logReplayEnable = logTimer.isActive(); + + ui->Simulate->setChecked(false); + ui->stop->click(); event->accept(); } +void TelemetrySimulator::showEvent(QShowEvent * event) +{ + ui->Simulate->setChecked(m_telemEnable); + if (m_logReplayEnable) + ui->play->click(); +} + #define SET_INSTANCE(control, id, def) ui->control->setText(QString::number(simulator->getSensorInstance(id, ((def) & 0x1F) + 1))) void TelemetrySimulator::setupDataFields() @@ -237,6 +261,9 @@ void TelemetrySimulator::generateTelemetryFrame() static FlvssEmulator *flvss = new FlvssEmulator(); static GPSEmulator *gps = new GPSEmulator(); + if (!m_simuStarted) + return; + memset(buffer, 0, sizeof(buffer)); switch (item++) { @@ -405,7 +432,7 @@ void TelemetrySimulator::generateTelemetryFrame() } if (ok && (buffer[2] || buffer[3])) - simulator->sendTelemetry(buffer, FRSKY_SPORT_PACKET_SIZE); + emit telemetryDataChanged(buffer, FRSKY_SPORT_PACKET_SIZE); else generateTelemetryFrame(); } diff --git a/companion/src/simulation/telemetrysimu.h b/companion/src/simulation/telemetrysimu.h index 5c1af2e42..23fc8948c 100644 --- a/companion/src/simulation/telemetrysimu.h +++ b/companion/src/simulation/telemetrysimu.h @@ -45,14 +45,19 @@ class TelemetrySimulator : public QWidget explicit TelemetrySimulator(QWidget * parent, SimulatorInterface * simulator); virtual ~TelemetrySimulator(); - void onSimulatorStarted(); + + signals: + + void telemetryDataChanged(uint8_t * data, unsigned int len); protected slots: - virtual void closeEvent(QCloseEvent *event); + virtual void hideEvent(QHideEvent *event); + virtual void showEvent(QShowEvent *event); + void onSimulatorStarted(); + void onSimulatorStopped(); void setupDataFields(); void onSimulateToggled(bool isChecked); - void generateTelemetryFrame(); void onLogTimerEvent(); void onLoadLogFile(); void onPlay(); @@ -62,6 +67,7 @@ class TelemetrySimulator : public QWidget void onStop(); void onPositionIndicatorChanged(int value); void onReplayRateChanged(int value); + void generateTelemetryFrame(); protected: @@ -69,6 +75,9 @@ class TelemetrySimulator : public QWidget QTimer timer; QTimer logTimer; SimulatorInterface *simulator; + bool m_simuStarted; + bool m_telemEnable; + bool m_logReplayEnable; // protected classes follow @@ -81,7 +90,7 @@ class TelemetrySimulator : public QWidget void play(); void stop(); void rewind(); - void stepForward(bool focusOnStop); + void stepForward(bool focusOnStop = false); void stepBack(); void updatePositionLabel(int32_t percentage); void setUiDataValues(); diff --git a/companion/src/simulation/trainersimu.cpp b/companion/src/simulation/trainersimu.cpp index 50a506669..f26fa05af 100644 --- a/companion/src/simulation/trainersimu.cpp +++ b/companion/src/simulation/trainersimu.cpp @@ -24,32 +24,40 @@ #include "helpers.h" #include "virtualjoystickwidget.h" +#define TRAINERSIMU_HEARTBEAT_PERIOD 500 // [ms] + TrainerSimulator::TrainerSimulator(QWidget * parent, SimulatorInterface * simulator): QWidget(parent), ui(new Ui::TrainerSimulator), - simulator(simulator) + simulator(simulator), + m_simuStarted(false) { ui->setupUi(this); vJoyLeft = new VirtualJoystickWidget(this, 'L', false); vJoyLeft->setStickColor(Qt::cyan); + vJoyLeft->setStickScale(512); ui->leftStickLayout->addWidget(vJoyLeft); vJoyRight = new VirtualJoystickWidget(this, 'R', false); vJoyRight->setStickColor(Qt::cyan); + vJoyRight->setStickScale(512); ui->rightStickLayout->addWidget(vJoyRight); - timer = new QTimer(this); - timer->setInterval(10); - connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); + connect(vJoyLeft, &VirtualJoystickWidget::valueChange, this, &TrainerSimulator::onRadioWidgetValueChange); + connect(vJoyRight, &VirtualJoystickWidget::valueChange, this, &TrainerSimulator::onRadioWidgetValueChange); + + connect(this, &TrainerSimulator::trainerHeartbeat, simulator, &SimulatorInterface::setTrainerTimeout); + connect(this, &TrainerSimulator::trainerChannelChange, simulator, &SimulatorInterface::setTrainerInput); + connect(simulator, &SimulatorInterface::started, this, &TrainerSimulator::onSimulatorStarted); + connect(simulator, &SimulatorInterface::stopped, this, &TrainerSimulator::onSimulatorStopped); + + timer.setInterval(TRAINERSIMU_HEARTBEAT_PERIOD - 10); + connect(&timer, &QTimer::timeout, this, &TrainerSimulator::emitHeartbeat); } TrainerSimulator::~TrainerSimulator() { - if (timer) { - timer->stop(); - delete timer; - } if (vJoyLeft) delete vJoyLeft; if (vJoyRight) @@ -60,42 +68,44 @@ TrainerSimulator::~TrainerSimulator() void TrainerSimulator::showEvent(QShowEvent *event) { - timer->start(); - event->accept(); + start(); } -void TrainerSimulator::closeEvent(QCloseEvent *event) +void TrainerSimulator::hideEvent(QHideEvent *event) { - timer->stop(); - event->accept(); + stop(); } -void TrainerSimulator::centerSticks() +void TrainerSimulator::start() { - if (vJoyLeft) - vJoyLeft->centerStick(); - - if (vJoyRight) - vJoyRight->centerStick(); + timer.start(); } -void TrainerSimulator::onTimerEvent() +void TrainerSimulator::stop() { - centerSticks(); - setTrainerInputs(); + timer.stop(); } -void TrainerSimulator::setTrainerInputs() +void TrainerSimulator::onSimulatorStarted() { - if (!simulator) - return; + m_simuStarted = true; +} - if (vJoyLeft) { - simulator->setTrainerInput(0, int( 512 * vJoyLeft->getStickX())); // LEFT HORZ - simulator->setTrainerInput(1, int(-512 * vJoyLeft->getStickY())); // LEFT VERT - } - if (vJoyRight) { - simulator->setTrainerInput(2, int(-512 * vJoyRight->getStickY())); // RGHT VERT - simulator->setTrainerInput(3, int( 512 * vJoyRight->getStickX())); // RGHT HORZ +void TrainerSimulator::onSimulatorStopped() +{ + m_simuStarted = false; + stop(); +} + +void TrainerSimulator::emitHeartbeat() +{ + emit trainerHeartbeat(TRAINERSIMU_HEARTBEAT_PERIOD); +} + +void TrainerSimulator::onRadioWidgetValueChange(RadioWidget::RadioWidgetType type, int index, int value) +{ + if (type == RadioWidget::RADIO_WIDGET_STICK && m_simuStarted && timer.isActive()) { + emit trainerChannelChange(index, value); + emitHeartbeat(); } } diff --git a/companion/src/simulation/trainersimu.h b/companion/src/simulation/trainersimu.h index 520df6d6c..ec1198c5b 100644 --- a/companion/src/simulation/trainersimu.h +++ b/companion/src/simulation/trainersimu.h @@ -26,6 +26,7 @@ #include #include #include +#include "radiowidget.h" #include "simulatorinterface.h" namespace Ui { @@ -42,21 +43,27 @@ class TrainerSimulator : public QWidget explicit TrainerSimulator(QWidget * parent, SimulatorInterface * simulator); virtual ~TrainerSimulator(); + signals: + void trainerHeartbeat(quint16 ms); + void trainerChannelChange(quint8 index, qint16 value); protected slots: + void start(); + void stop(); virtual void showEvent(QShowEvent *event); - virtual void closeEvent(QCloseEvent *event); - void centerSticks(); - void setTrainerInputs(); - void onTimerEvent(); + virtual void hideEvent(QHideEvent *event); + void onSimulatorStarted(); + void onSimulatorStopped(); + void onRadioWidgetValueChange(RadioWidget::RadioWidgetType type, int index, int value); + void emitHeartbeat(); protected: Ui::TrainerSimulator * ui; - QTimer * timer; SimulatorInterface *simulator; - VirtualJoystickWidget * vJoyLeft; VirtualJoystickWidget * vJoyRight; + QTimer timer; + bool m_simuStarted; }; diff --git a/companion/src/simulation/widgets/buttonswidget.h b/companion/src/simulation/widgets/buttonswidget.h index 074ca0851..4fef883e6 100644 --- a/companion/src/simulation/widgets/buttonswidget.h +++ b/companion/src/simulation/widgets/buttonswidget.h @@ -22,47 +22,12 @@ #define _BUTTONSWIDGET_H_ #include "radiouiaction.h" +#include "radiokeywidget.h" #include #include #include -class Area : public QObject -{ - Q_OBJECT - - public: - explicit Area(const QPolygon & polygon, const QString &image, RadioUiAction * action = NULL, QObject * parent = NULL): - QObject(parent), - polygon(polygon), - imgFile(image), - action(action) - { - if (action) - connect(action, &RadioUiAction::toggled, this, &Area::onActionToggled); - } - - bool contains(int x, int y) - { - return polygon.containsPoint(QPoint(x, y), Qt::OddEvenFill); - } - - void onActionToggled(int, bool active) - { - if (active) - emit imageChanged(imgFile); - else - emit imageChanged(""); - } - - QPolygon polygon; - QString imgFile; - RadioUiAction * action; - - signals: - void imageChanged(const QString image); -}; - class ButtonsWidget : public QWidget { Q_OBJECT @@ -80,18 +45,17 @@ class ButtonsWidget : public QWidget QWidget::setStyleSheet(sheet); } - void addArea(int x1, int y1, int x2, int y2, const char * image, RadioUiAction * action = NULL) + RadioKeyWidget * addArea(const QRect & rect, const char * image, RadioUiAction * action = NULL) { - QPolygon polygon; - polygon.setPoints(4, x1, y1, x1, y2, x2, y2, x2, y1); - addArea(polygon, image, action); + return addArea(QPolygon(rect), image, action); } - void addArea(const QPolygon & polygon, const char * image, RadioUiAction * action = NULL) + RadioKeyWidget * addArea(const QPolygon & polygon, const char * image, RadioUiAction * action = NULL) { - Area * area = new Area(polygon, image, action, this); - areas.push_back(area); - connect(area, &Area::imageChanged, this, &ButtonsWidget::setBitmap); + RadioKeyWidget * btn = new RadioKeyWidget(polygon, image, action, this); + m_buttons.append(btn); + connect(btn, &RadioKeyWidget::imageChanged, this, &ButtonsWidget::setBitmap); + return btn; } protected: @@ -113,16 +77,14 @@ class ButtonsWidget : public QWidget return; } - foreach(Area * area, areas) { - if (!area->action) - continue; - if (area->contains(event->x(), event->y())) { - area->action->trigger(press); + foreach(RadioKeyWidget * key, m_buttons) { + if (key->contains(event->pos())) { + key->toggle(press); event->accept(); return; } - else if (area->action->isActive()) { - area->action->trigger(false); + else if (key->getValue()) { + key->toggle(false); } } event->ignore(); @@ -145,7 +107,7 @@ class ButtonsWidget : public QWidget style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } - QList areas; + QList m_buttons; QString defaultStyleSheet; }; diff --git a/companion/src/simulation/widgets/radiokeywidget.h b/companion/src/simulation/widgets/radiokeywidget.h new file mode 100644 index 000000000..ee63c3b71 --- /dev/null +++ b/companion/src/simulation/widgets/radiokeywidget.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef RADIOKEYWIDGET_H +#define RADIOKEYWIDGET_H + +#include "radiouiaction.h" +#include "radiowidget.h" + +class RadioKeyWidget : public RadioWidget +{ + Q_OBJECT + + public: + + explicit RadioKeyWidget(const QPolygon & polygon, const QString &image, RadioUiAction * action = NULL, QWidget * parent = NULL, Qt::WindowFlags f = Qt::WindowFlags()): + RadioWidget(action, parent, f), + polygon(polygon), + imgFile(image) + { + m_type = RADIO_WIDGET_KEY; + setValue(0); + hide(); // we're a "virtual" button for now + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + } + + virtual void setAction(RadioUiAction * action) + { + if (m_action) + disconnect(m_action, 0, this, 0); + RadioWidget::setAction(action); + if (m_action) + connect(m_action, &RadioUiAction::toggled, this, &RadioKeyWidget::onActionToggled); + } + + virtual int getValue() const + { + return (m_action && m_action->isActive()) ? 1 : 0; + } + + void press() { + if (m_action) + m_action->trigger(true); + } + + void release() { + if (m_action) + m_action->trigger(false); + } + + void toggle(bool down) + { + if (down) + press(); + else + release(); + } + + bool contains(const QPoint & point) + { + return polygon.containsPoint(point, Qt::OddEvenFill); + } + + signals: + + void imageChanged(const QString image); + + protected: + + virtual void onActionToggled(int index, bool active) + { + RadioWidget::onActionToggled(index, active); + emit imageChanged(active ? imgFile : ""); + } + + QPolygon polygon; + QString imgFile; + +}; + +#endif // RADIOKEYWIDGET_H + + diff --git a/companion/src/simulation/widgets/radioknobwidget.h b/companion/src/simulation/widgets/radioknobwidget.h index be74a9ee9..d66eddf18 100644 --- a/companion/src/simulation/widgets/radioknobwidget.h +++ b/companion/src/simulation/widgets/radioknobwidget.h @@ -26,7 +26,7 @@ #include #include -#include +#include #include class RadioKnobWidget : public RadioWidget @@ -65,7 +65,6 @@ class RadioKnobWidget : public RadioWidget Board::PotType m_potType; - class KnobWidget : public QDial { public: @@ -73,6 +72,8 @@ class RadioKnobWidget : public RadioWidget explicit KnobWidget(Board::PotType type, QWidget * parent = 0): QDial(parent) { + m_toolTip = tr("

Value (input): %1

"); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setFixedSize(QSize(42, 42)); setNotchesVisible(true); @@ -88,7 +89,7 @@ class RadioKnobWidget : public RadioWidget } else { m_stepSize = 1; - setToolTip(tr("Right-double-click to reset to center.")); + m_toolTip.append(tr("Right-double-click to reset to center.")); setMinimum(-1024); setMaximum(1024); setPageStep(128); @@ -104,6 +105,19 @@ class RadioKnobWidget : public RadioWidget QDial::setValue(value); } + + bool event(QEvent *event) + { + if (event->type() == QEvent::ToolTip) { + QHelpEvent * helpEvent = static_cast(event); + if (helpEvent) { + QToolTip::showText(helpEvent->globalPos(), m_toolTip.arg(this->value())); + return true; + } + } + return QWidget::event(event); + } + void mousePressEvent(QMouseEvent * event) { if (m_stepSize == 1 && event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { @@ -129,6 +143,7 @@ class RadioKnobWidget : public RadioWidget } quint16 m_stepSize; + QString m_toolTip; }; }; diff --git a/companion/src/simulation/widgets/radioswitchwidget.h b/companion/src/simulation/widgets/radioswitchwidget.h index 45c50d710..39878a648 100644 --- a/companion/src/simulation/widgets/radioswitchwidget.h +++ b/companion/src/simulation/widgets/radioswitchwidget.h @@ -110,9 +110,8 @@ class RadioSwitchWidget : public RadioWidget void setToggleLocked(bool lock) { if (m_flags != (quint16)lock) { - m_flags = lock; + setFlags((quint16)lock); setValue((int)lock); - emit flagsChanged(m_flags); } } diff --git a/companion/src/simulation/widgets/radiotrimwidget.h b/companion/src/simulation/widgets/radiotrimwidget.h index a6f46bc89..54659b0a4 100644 --- a/companion/src/simulation/widgets/radiotrimwidget.h +++ b/companion/src/simulation/widgets/radiotrimwidget.h @@ -22,6 +22,7 @@ #define _RADIOTRIMWIDGET_H_ #include "radiowidget.h" +#include "boards.h" #include "sliderwidget.h" #include @@ -38,12 +39,6 @@ class RadioTrimWidget : public RadioWidget { init(orientation); } - explicit RadioTrimWidget(const QString & labelText, Qt::Orientation orientation = Qt::Vertical, int value = 0, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : - RadioWidget(labelText, value, parent, f), - m_slider(NULL) - { - init(orientation); - } void setIndices(int sliderIdx = -1, int decrBtnIdx = -1, int incrBtnIdx = -1) { @@ -58,11 +53,27 @@ class RadioTrimWidget : public RadioWidget m_slider->setRange(min, max); } - signals: + void setTrimRangeQual(const int index, const int min, const int max) + { + if (index == m_index || index == Board::TRIM_AXIS_COUNT) + setTrimRange(min, max); + } - void trimButtonPressed(int index); - void trimButtonReleased(int index); - void trimSliderMoved(int index, int value); + void setTrimValue(const int index, const int value) + { + if (index == m_index || index == Board::TRIM_AXIS_COUNT) + setValue(value); + } + + void setValue(const int & value) + { + if (sender() && qobject_cast(sender())) { + RadioWidget::setValue(value); + } + else if (m_slider) { + m_slider->setValue(value); + } + } protected slots: @@ -75,20 +86,20 @@ class RadioTrimWidget : public RadioWidget setTrimRange(-125, 125); setIndices(); - QSize btnIcnSz(12, 12); + const QSize btnSz(18, 18); QWidget * trimWidget = new QWidget(this); QBoxLayout * trimLayout = new QVBoxLayout(trimWidget); - trimLayout->setSpacing(5); - trimLayout->setContentsMargins(8, 8, 8, 8); + trimLayout->setSpacing(4); QToolButton * trimBtnInc = new QToolButton(trimWidget); - trimBtnInc->setIconSize(btnIcnSz); + trimBtnInc->setMaximumSize(btnSz); QToolButton * trimBtnDec = new QToolButton(trimWidget); - trimBtnDec->setIconSize(btnIcnSz); + trimBtnDec->setMaximumSize(btnSz); Qt::Alignment algn; if (orientation == Qt::Horizontal) { trimWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); trimLayout->setDirection(QBoxLayout::RightToLeft); + trimLayout->setContentsMargins(0, 8, 0, 8); trimBtnInc->setArrowType(Qt::RightArrow); trimBtnDec->setArrowType(Qt::LeftArrow); algn = Qt::AlignVCenter; @@ -96,6 +107,7 @@ class RadioTrimWidget : public RadioWidget else { trimWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); trimLayout->setDirection(QBoxLayout::TopToBottom); + trimLayout->setContentsMargins(8, 0, 8, 0); trimBtnInc->setArrowType(Qt::UpArrow); trimBtnDec->setArrowType(Qt::DownArrow); algn = Qt::AlignHCenter; @@ -107,21 +119,11 @@ class RadioTrimWidget : public RadioWidget setWidget(trimWidget, algn); - connect(m_slider, &SliderWidget::valueChanged, this, &RadioWidget::setValue); - connect(this, &RadioWidget::valueChanged, this, &RadioTrimWidget::onValueChanged); - - connect(trimBtnInc, &QToolButton::pressed, [this]() { emit trimButtonPressed(m_btnIncIndex); }); - connect(trimBtnInc, &QToolButton::released, [this]() { emit trimButtonReleased(m_btnIncIndex); }); - connect(trimBtnDec, &QToolButton::pressed, [this]() { emit trimButtonPressed(m_btnDecIndex); }); - connect(trimBtnDec, &QToolButton::released, [this]() { emit trimButtonReleased(m_btnDecIndex); }); - } - - void onValueChanged(int value) - { - m_slider->blockSignals(true); - m_slider->setValue(value); - m_slider->blockSignals(false); - emit trimSliderMoved(m_index, m_value); + connect(m_slider, &SliderWidget::valueChanged, this, &RadioTrimWidget::setValue); + connect(trimBtnInc, &QToolButton::pressed, [this]() { emit valueChange(m_type, m_btnIncIndex, RADIO_TRIM_BTN_ON); }); + connect(trimBtnInc, &QToolButton::released, [this]() { emit valueChange(m_type, m_btnIncIndex, RADIO_TRIM_BTN_OFF); }); + connect(trimBtnDec, &QToolButton::pressed, [this]() { emit valueChange(m_type, m_btnDecIndex, RADIO_TRIM_BTN_ON); }); + connect(trimBtnDec, &QToolButton::released, [this]() { emit valueChange(m_type, m_btnDecIndex, RADIO_TRIM_BTN_OFF); }); } protected: @@ -130,5 +132,4 @@ class RadioTrimWidget : public RadioWidget int m_btnIncIndex; }; - #endif // _RADIOTRIMWIDGET_H_ diff --git a/companion/src/simulation/widgets/radiowidget.cpp b/companion/src/simulation/widgets/radiowidget.cpp index 2e6d213dd..9e4d9177a 100644 --- a/companion/src/simulation/widgets/radiowidget.cpp +++ b/companion/src/simulation/widgets/radiowidget.cpp @@ -18,51 +18,66 @@ * GNU General Public License for more details. */ +#include "radiouiaction.h" #include "radiowidget.h" RadioWidget::RadioWidget(QWidget * parent, Qt::WindowFlags f) : QWidget(parent, f), + m_gridLayout(NULL), + m_controlWidget(NULL), + m_nameLabel(NULL), + m_action(NULL), m_value(0), m_flags(0), - m_invertValue(false), + m_valueReset(false), m_showLabel(false), - m_labelText(""), - m_type(RADIO_WIDGET_NONE) + m_labelText("") { - init(); + qRegisterMetaType(); + setIndex(-1); + setType(RADIO_WIDGET_NONE); +} + +RadioWidget::RadioWidget(RadioUiAction * action, QWidget * parent, Qt::WindowFlags f) : + RadioWidget(parent, f) +{ + setAction(action); } RadioWidget::RadioWidget(const QString & labelText, int value, QWidget * parent, Qt::WindowFlags f) : - QWidget(parent, f), - m_value(value), - m_flags(0), - m_invertValue(false), - m_showLabel(true), - m_labelText(labelText), - m_type(RADIO_WIDGET_NONE) + RadioWidget(parent, f) { - init(); + setValue(value); + setLabelText(labelText); } -void RadioWidget::setIndex(int index) +void RadioWidget::setIndex(const int & index) { m_index = index; } -void RadioWidget::setInvertValue(bool invertValue) +void RadioWidget::setType(const RadioWidgetType & type) { - m_invertValue = invertValue; + m_type = type; } -void RadioWidget::setValue(int value) +void RadioWidget::setValue(const int & value) { - if (value != m_value) { + if (value != m_value || m_valueReset) { m_value = value; + m_valueReset = false; emit valueChanged(value); + emit valueChange(m_type, m_index, value); } } -void RadioWidget::setFlags(quint16 flags) +void RadioWidget::setValueQual(const RadioWidget::RadioWidgetType & type, const int & index, const int & value) +{ + if (type == m_type && index == m_index) + setValue(value); +} + +void RadioWidget::setFlags(const quint16 & flags) { if (flags != m_flags) { m_flags = flags; @@ -70,7 +85,7 @@ void RadioWidget::setFlags(quint16 flags) } } -void RadioWidget::setShowLabel(bool show) +void RadioWidget::setShowLabel(const bool show) { if (show != m_showLabel) { m_showLabel = show; @@ -85,13 +100,12 @@ void RadioWidget::setLabelText(const QString & labelText, bool showLabel) addLabel(); } -void RadioWidget::setStateData(const QByteArray & data) +void RadioWidget::setStateData(const RadioWidgetState & state) { - RadioWidgetState state; - QDataStream stream(data); - stream >> state; + if (state.type != m_type || state.index != m_index) + return; - //setIndex(state.index); + m_valueReset = true; setValue(state.value); setFlags(state.flags); } @@ -109,9 +123,26 @@ void RadioWidget::changeVisibility(bool visible) } } +void RadioWidget::setAction(RadioUiAction * action) +{ + if (m_action) { + disconnect(m_action, 0, this, 0); + delete m_action; + m_action = NULL; + } + m_action = action; + + if (m_action) { + if (m_action->getIndex() > -1) + setIndex(m_action->getIndex()); + connect(m_action, &RadioUiAction::toggled, this, &RadioWidget::onActionToggled); + } +} + + int RadioWidget::getValue() const { - return m_value * (m_invertValue ? -1 : 1); + return m_value; } int RadioWidget::getIndex() const @@ -134,25 +165,34 @@ QByteArray RadioWidget::getStateData() const RadioWidget::RadioWidgetState RadioWidget::getState() const { - return RadioWidgetState(quint8(m_type), qint8(m_index), qint16(m_value), quint16(m_flags));; + return RadioWidgetState(quint8(m_type), qint8(m_index), qint16(m_value), quint16(m_flags)); } -void RadioWidget::init() +RadioUiAction * RadioWidget::getAction() const { - setIndex(0); - m_controlWidget = NULL; - m_nameLabel = NULL; + return m_action; +} - m_gridLayout= new QGridLayout(this); +void RadioWidget::onActionToggled(int index, bool active) +{ + emit valueChange(m_type, index, getValue()); +} + + +void RadioWidget::addLayout() +{ + if (m_gridLayout) + return; + + m_gridLayout = new QGridLayout(this); m_gridLayout->setContentsMargins(0, 0, 0, 0); m_gridLayout->setVerticalSpacing(4); m_gridLayout->setHorizontalSpacing(0); - - addLabel(); } void RadioWidget::addLabel() { + addLayout(); if (m_nameLabel) { m_gridLayout->removeWidget(m_nameLabel); m_nameLabel->deleteLater(); @@ -168,6 +208,7 @@ void RadioWidget::addLabel() void RadioWidget::setWidget(QWidget * widget, Qt::Alignment align) { + addLayout(); if (m_controlWidget) { m_gridLayout->removeWidget(m_controlWidget); m_controlWidget->deleteLater(); diff --git a/companion/src/simulation/widgets/radiowidget.h b/companion/src/simulation/widgets/radiowidget.h index aa9bb81ab..af218495b 100644 --- a/companion/src/simulation/widgets/radiowidget.h +++ b/companion/src/simulation/widgets/radiowidget.h @@ -28,6 +28,8 @@ #define RADIO_WIDGET_STATE_VERSION 1 +class RadioUiAction; + class RadioWidget : public QWidget { Q_OBJECT @@ -41,10 +43,14 @@ class RadioWidget : public QWidget RADIO_WIDGET_SWITCH, RADIO_WIDGET_KNOB, RADIO_WIDGET_FADER, - RADIO_WIDGET_TRIM, - RADIO_WIDGET_STICK // actually one axis of a stick + RADIO_WIDGET_TRIM, // trim axis, usually 2 buttons & slider + RADIO_WIDGET_STICK, // actually one axis of a stick + RADIO_WIDGET_KEY, // UI key/pushbutton + RADIO_WIDGET_ENUM_COUNT }; + enum RadioTrimWidgetButtonStates { RADIO_TRIM_BTN_ON = 1024, RADIO_TRIM_BTN_OFF = -1024 }; + struct RadioWidgetState { public: RadioWidgetState(quint8 type = 0, qint8 index = 0, qint16 value = 0, quint16 flags = 0) : @@ -65,49 +71,58 @@ class RadioWidget : public QWidget quint8 _version = RADIO_WIDGET_STATE_VERSION; // structure definition version }; - explicit RadioWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); - explicit RadioWidget(const QString &labelText, int value = 0, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); - - void setIndex(int index); - void setInvertValue(bool invertValue); - void setValue(int value); - void setFlags(quint16 flags); - void setShowLabel(bool show); - void setLabelText(const QString &labelText, bool showLabel = true); - void setStateData(const QByteArray & data); - void changeVisibility(bool visible); + explicit RadioWidget(QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); + explicit RadioWidget(RadioUiAction * action = NULL, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); + explicit RadioWidget(const QString & labelText, int value = 0, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); virtual int getValue() const; - int getIndex() const; - int getType() const; - QByteArray getStateData() const; - RadioWidgetState getState() const; + virtual int getIndex() const; + virtual int getType() const; + virtual QByteArray getStateData() const; + virtual RadioWidgetState getState() const; + virtual RadioUiAction * getAction() const; + + public slots: + + virtual void setIndex(const int & index); + virtual void setType(const RadioWidgetType & type); + virtual void setValue(const int & value); + virtual void setValueQual(const RadioWidgetType & type, const int & index, const int & value); + virtual void setFlags(const quint16 & flags); + virtual void setShowLabel(const bool show); + virtual void setLabelText(const QString & labelText, bool showLabel = true); + virtual void setStateData(const RadioWidgetState & state); + virtual void changeVisibility(bool visible); + virtual void setAction(RadioUiAction * action); + + signals: + + void valueChange(const RadioWidgetType type, const int index, int value); + void valueChanged(const int value); + void flagsChanged(const quint16 flags); protected: - void init(); + void addLayout(); void addLabel(); void setWidget(QWidget * widget = NULL, Qt::Alignment align = Qt::AlignHCenter); - - int m_value; - int m_index; - quint16 m_flags; - bool m_invertValue; - bool m_showLabel; - QString m_labelText; - RadioWidgetType m_type; - - private: + virtual void onActionToggled(int index, bool active); QGridLayout * m_gridLayout; QWidget * m_controlWidget; QLabel * m_nameLabel; + RadioUiAction * m_action; - signals: - - void valueChanged(int value); - void flagsChanged(quint16 flags); + int m_value; + int m_index; + quint16 m_flags; + bool m_valueReset; + bool m_showLabel; + QString m_labelText; + RadioWidgetType m_type; }; +Q_DECLARE_METATYPE(RadioWidget::RadioWidgetState) + #endif // _RADIOWIDGET_H_ diff --git a/companion/src/simulation/widgets/sliderwidget.h b/companion/src/simulation/widgets/sliderwidget.h index f99ce570e..1ed285ba8 100644 --- a/companion/src/simulation/widgets/sliderwidget.h +++ b/companion/src/simulation/widgets/sliderwidget.h @@ -23,6 +23,7 @@ #include #include +#include class SliderWidget : public QSlider { @@ -33,20 +34,34 @@ class SliderWidget : public QSlider explicit SliderWidget(QWidget * parent = 0): QSlider(parent) { - setToolTip(tr("Right-double-click to reset to center.")); + m_toolTip = tr("

Value (input): %1

") % tr("Right-double-click to reset to center."); } - protected: + protected: - void mousePressEvent(QMouseEvent * event) - { - if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { - setValue(0); - emit sliderMoved(0); - event->accept(); - } - QSlider::mousePressEvent(event); - } + bool event(QEvent *event) + { + if (event->type() == QEvent::ToolTip) { + QHelpEvent * helpEvent = static_cast(event); + if (helpEvent) { + QToolTip::showText(helpEvent->globalPos(), m_toolTip.arg(this->value())); + return true; + } + } + return QWidget::event(event); + } + + void mousePressEvent(QMouseEvent * event) + { + if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { + setValue(0); + emit sliderMoved(0); + event->accept(); + } + QSlider::mousePressEvent(event); + } + + QString m_toolTip; }; #endif // _SLIDERWIDGET_H_ diff --git a/companion/src/simulation/widgets/virtualjoystickwidget.cpp b/companion/src/simulation/widgets/virtualjoystickwidget.cpp index dd250dcad..8e9a4fa45 100644 --- a/companion/src/simulation/widgets/virtualjoystickwidget.cpp +++ b/companion/src/simulation/widgets/virtualjoystickwidget.cpp @@ -21,11 +21,11 @@ #define GBALL_SIZE 25 #define GBALL_SIZE_MN 20 #define GBALL_SIZE_MX 35 +#define CENTER_INTERVAL 50 // ms #include "virtualjoystickwidget.h" #include "boards.h" -#include "constants.h" #include "modeledit/node.h" #include "helpers.h" #include "radiotrimwidget.h" @@ -43,6 +43,7 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s btnFixY(NULL), nodeLabelX(NULL), nodeLabelY(NULL), + m_stickScale(1024), m_stickPressed(false) { ar = (float)size.width() / size.height(); @@ -135,12 +136,18 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s 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); // r4 c0-4: bot v spacer - connect(node, &Node::xChanged, this, &VirtualJoystickWidget::updateNodeValueLabels); - connect(node, &Node::yChanged, this, &VirtualJoystickWidget::updateNodeValueLabels); + setStickIndices(getStickIndex('H'), getStickIndex('V')); + + connect(node, &Node::xChanged, this, &VirtualJoystickWidget::onNodeXChanged); + connect(node, &Node::yChanged, this, &VirtualJoystickWidget::onNodeYChanged); connect(scene, &CustomGraphicsScene::mouseEvent, this, &VirtualJoystickWidget::onGsMouseEvent); setSize(prefSize, frameSize()); + + centerStickTimer.setInterval(CENTER_INTERVAL); + connect(¢erStickTimer, &QTimer::timeout, this, &VirtualJoystickWidget::centerStick); + centerStickTimer.start(); } void VirtualJoystickWidget::setStickX(qreal x) @@ -158,6 +165,31 @@ void VirtualJoystickWidget::setStickPos(QPointF xy) node->setPos(xy); } +void VirtualJoystickWidget::setStickAxisValue(int index, int value) +{ + using namespace Board; + qreal rvalue = value / 1024.0f; + + switch (index) { + case STICK_AXIS_LH : + if (stickSide == 'L') + setStickX(rvalue); + break; + case STICK_AXIS_RH : + if (stickSide == 'R') + setStickX(rvalue); + break; + case STICK_AXIS_LV : + if (stickSide == 'L') + setStickY(rvalue); + break; + case STICK_AXIS_RV : + if (stickSide == 'R') + setStickY(rvalue); + break; + } +} + void VirtualJoystickWidget::centerStick() { node->stepToCenter(); @@ -178,20 +210,55 @@ QPointF VirtualJoystickWidget::getStickPos() return QPointF(node->getX(), node->getY()); } -void VirtualJoystickWidget::setTrimValue(int which, int value) +void VirtualJoystickWidget::setTrimsRange(int min, int max) { - RadioTrimWidget * trim = getTrimWidget(which); + if (hTrimWidget) + hTrimWidget->setTrimRange(min, max); + if (vTrimWidget) + vTrimWidget->setTrimRange(min, max); +} + +void VirtualJoystickWidget::setTrimsRange(int rng) +{ + setTrimsRange(-rng, rng); +} + +void VirtualJoystickWidget::setTrimRange(int index, int min, int max) +{ + if (index == Board::TRIM_AXIS_COUNT) { + setTrimsRange(min, max); + return; + } + RadioTrimWidget * trim = getTrimWidget(index); + if (trim) { + trim->setTrimRange(min, max); + } +} + +void VirtualJoystickWidget::setTrimValue(int index, int value) +{ + RadioTrimWidget * trim = getTrimWidget(index); if (trim) { trim->setValue(value); } } -void VirtualJoystickWidget::setTrimRange(int which, int min, int max) +int VirtualJoystickWidget::getStickScale() const { - RadioTrimWidget * trim = getTrimWidget(which); - if (trim) { - trim->setTrimRange(min, max); - } + return m_stickScale; +} + +void VirtualJoystickWidget::setStickScale(int stickScale) +{ + m_stickScale = stickScale; +} + +void VirtualJoystickWidget::setWidgetValue(const RadioWidget::RadioWidgetType type, const int index, const int value) +{ + if (type == RadioWidget::RADIO_WIDGET_TRIM) + setTrimValue(index, value); + else if (type == RadioWidget::RADIO_WIDGET_STICK) + setStickAxisValue(index, value); } int VirtualJoystickWidget::getTrimValue(int which) @@ -203,27 +270,25 @@ int VirtualJoystickWidget::getTrimValue(int which) return 0; } -void VirtualJoystickWidget::setStickConstraint(int which, bool active) +void VirtualJoystickWidget::setStickIndices(int xIdx, int yIdx) { - if (btnHoldX == NULL) + m_xIndex = xIdx; + m_yIndex = yIdx; +} + +void VirtualJoystickWidget::setStickConstraint(quint8 index, bool active) +{ + if (btnHoldX == NULL || !index) 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; - } + if (index & HOLD_X) + btnHoldX->setChecked(active); + if (index & HOLD_Y) + btnHoldY->setChecked(active); + if (index & FIX_X) + btnFixX->setChecked(active); + if (index & FIX_Y) + btnFixY->setChecked(active); } void VirtualJoystickWidget::setStickColor(const QColor & color) @@ -231,6 +296,15 @@ void VirtualJoystickWidget::setStickColor(const QColor & color) node->setColor(color); } +void VirtualJoystickWidget::loadDefaultsForMode(const unsigned mode) +{ + if (((mode & 1) && stickSide == 'L') || (!(mode & 1) && stickSide == 'R')) { + setStickConstraint(HOLD_Y, true); + setStickY(1.0); + onNodeYChanged(); + } +} + QSize VirtualJoystickWidget::sizeHint() const { return prefSize; } @@ -241,6 +315,18 @@ void VirtualJoystickWidget::resizeEvent(QResizeEvent * event) setSize(event->size(), event->oldSize()); } +void VirtualJoystickWidget::onNodeXChanged() +{ + emit valueChange(RadioWidget::RADIO_WIDGET_STICK, m_xIndex, int(m_stickScale * getStickX())); + updateNodeValueLabels(); +} + +void VirtualJoystickWidget::onNodeYChanged() +{ + emit valueChange(RadioWidget::RADIO_WIDGET_STICK, m_yIndex, int(-m_stickScale * getStickY())); + updateNodeValueLabels(); +} + void VirtualJoystickWidget::setSize(const QSize & size, const QSize &) { float thisAspectRatio = (float)size.width() / size.height(); @@ -301,9 +387,7 @@ RadioTrimWidget * VirtualJoystickWidget::createTrimWidget(QChar type) RadioTrimWidget * trimWidget = new RadioTrimWidget(type == 'H' ? Qt::Horizontal : Qt::Vertical); trimWidget->setIndices(getTrimSliderType(type), getTrimButtonType(type, 0), getTrimButtonType(type, 1)); - connect(trimWidget, &RadioTrimWidget::trimButtonPressed, this, &VirtualJoystickWidget::trimButtonPressed); - connect(trimWidget, &RadioTrimWidget::trimButtonReleased, this, &VirtualJoystickWidget::trimButtonReleased); - connect(trimWidget, &RadioTrimWidget::trimSliderMoved, this, &VirtualJoystickWidget::trimSliderMoved); + connect(trimWidget, &RadioTrimWidget::valueChange, this, &VirtualJoystickWidget::valueChange); return trimWidget; } @@ -371,6 +455,24 @@ QLayout *VirtualJoystickWidget::createNodeValueLayout(QChar type, QLabel *& valL return layout; } +int VirtualJoystickWidget::getStickIndex(QChar type) +{ + using namespace Board; + + if (stickSide == 'L') { + if (type == 'H') + return STICK_AXIS_LH; + else + return STICK_AXIS_LV; + } + else { + if (type == 'H') + return STICK_AXIS_RH; + else + return STICK_AXIS_RV; + } +} + int VirtualJoystickWidget::getTrimSliderType(QChar type) { using namespace Board; @@ -426,10 +528,12 @@ int VirtualJoystickWidget::getTrimButtonType(QChar type, int pos) RadioTrimWidget * VirtualJoystickWidget::getTrimWidget(int which) { - if (which == Board::TRIM_AXIS_LH || which == Board::TRIM_AXIS_RH) + if ((stickSide == 'L' && which == Board::TRIM_AXIS_LH) || (stickSide == 'R' && which == Board::TRIM_AXIS_RH)) return hTrimWidget; - else + else if ((stickSide == 'L' && which == Board::TRIM_AXIS_LV) || (stickSide == 'R' && which == Board::TRIM_AXIS_RV)) return vTrimWidget; + else + return NULL; } void VirtualJoystickWidget::onButtonChange(bool checked) @@ -456,9 +560,9 @@ void VirtualJoystickWidget::onButtonChange(bool checked) void VirtualJoystickWidget::updateNodeValueLabels() { if (nodeLabelX) - nodeLabelX->setText(QString("%1").arg((qreal)node->getX() * 100 + getTrimValue(0) / 5, 2, 'f', 0)); + nodeLabelX->setText(QString("%1").arg(node->getX() * 100.0f, 2, 'f', 0)); if (nodeLabelY) - nodeLabelY->setText(QString("%1").arg((qreal)node->getY() * -100 + getTrimValue(1) / 5, 2, 'f', 0)); + nodeLabelY->setText(QString("%1").arg(node->getY() * -100.0f, 2, 'f', 0)); } void VirtualJoystickWidget::onGsMouseEvent(QGraphicsSceneMouseEvent * event) diff --git a/companion/src/simulation/widgets/virtualjoystickwidget.h b/companion/src/simulation/widgets/virtualjoystickwidget.h index 69faeddc1..5507d46e2 100644 --- a/companion/src/simulation/widgets/virtualjoystickwidget.h +++ b/companion/src/simulation/widgets/virtualjoystickwidget.h @@ -21,6 +21,8 @@ #ifndef VIRTUALJOYSTICKWIDGET_H #define VIRTUALJOYSTICKWIDGET_H +#include "radiowidget.h" + #include #include #include @@ -28,6 +30,7 @@ #include #include #include +#include #include class CustomGraphicsScene; @@ -40,38 +43,47 @@ class VirtualJoystickWidget : public QWidget public: enum ConstraintTypes { - HOLD_X = 0, - HOLD_Y, - FIX_X, - FIX_Y + HOLD_X = 0x01, + HOLD_Y = 0x02, + FIX_X = 0x04, + FIX_Y = 0x08 }; explicit VirtualJoystickWidget(QWidget * parent = NULL, QChar side = 'L', bool showTrims = true, bool showBtns = true, bool showValues = true, QSize size = QSize(125, 125)); - void setStickX(qreal x); - void setStickY(qreal y); - void setStickPos(QPointF xy); - void centerStick(); qreal getStickX(); qreal getStickY(); QPointF getStickPos(); - - void setTrimValue(int which, int value); - void setTrimRange(int which, int min, int max); int getTrimValue(int which); - - void setStickConstraint(int which, bool active); - void setStickColor(const QColor & color); - virtual QSize sizeHint() const; virtual void resizeEvent(QResizeEvent *event); + int getStickScale() const; + void setStickScale(int stickScale); + + public slots: + void setStickX(qreal x); + void setStickY(qreal y); + void setStickPos(QPointF xy); + void setStickAxisValue(int index, int value); + void centerStick(); + void setTrimsRange(int min, int max); + void setTrimsRange(int rng); + void setTrimRange(int index, int min, int max); + void setTrimValue(int index, int value); + void setWidgetValue(const RadioWidget::RadioWidgetType type, const int index, const int value); + + void setStickIndices(int xIdx = -1, int yIdx = -1); + void setStickConstraint(quint8 index, bool active); + void setStickColor(const QColor & color); + void loadDefaultsForMode(const unsigned mode); + signals: - void trimButtonPressed(int index); - void trimButtonReleased(int index); - void trimSliderMoved(int index, int value); + void valueChange(const RadioWidget::RadioWidgetType type, const int index, int value); protected slots: + void onNodeXChanged(); + void onNodeYChanged(); void onButtonChange(bool checked); void updateNodeValueLabels(); void onGsMouseEvent(QGraphicsSceneMouseEvent * event); @@ -81,6 +93,7 @@ class VirtualJoystickWidget : public QWidget RadioTrimWidget * createTrimWidget(QChar type); QToolButton * createButtonWidget(int type); QLayout * createNodeValueLayout(QChar type, QLabel *& valLabel); + int getStickIndex(QChar type); int getTrimSliderType(QChar type); int getTrimButtonType(QChar type, int pos); RadioTrimWidget * getTrimWidget(int which); @@ -97,7 +110,11 @@ class VirtualJoystickWidget : public QWidget QToolButton * btnFixX, * btnFixY; QLabel * nodeLabelX, * nodeLabelY; QSize extraSize; + QTimer centerStickTimer; float ar; // aspect ratio + int m_xIndex; + int m_yIndex; + int m_stickScale; bool m_stickPressed; }; diff --git a/radio/src/mixer.cpp b/radio/src/mixer.cpp index d3ec9d021..c7dd57f21 100644 --- a/radio/src/mixer.cpp +++ b/radio/src/mixer.cpp @@ -442,7 +442,7 @@ void evalInputs(uint8_t mode) if (v < -RESX) v = -RESX; if (v > RESX) v = RESX; -#if defined(PCBTARANIS) && !defined(PCBX7) +#if defined(PCBTARANIS) && !defined(PCBX7) && !defined(SIMU) // TODO why not in the driver? if (i==POT1 || i==SLIDER1) { v = -v; diff --git a/radio/src/targets/horus/keys_driver.cpp b/radio/src/targets/horus/keys_driver.cpp index 728aa1e4e..e881727ba 100644 --- a/radio/src/targets/horus/keys_driver.cpp +++ b/radio/src/targets/horus/keys_driver.cpp @@ -68,7 +68,6 @@ uint32_t readTrims() result |= 0x40; if (~TRIMS_GPIO_REG_RHR & TRIMS_GPIO_PIN_RHR) result |= 0x80; -#if !defined(SIMU) if (~TRIMS_GPIO_REG_LSD & TRIMS_GPIO_PIN_LSD) result |= 0x100; if (~TRIMS_GPIO_REG_LSU & TRIMS_GPIO_PIN_LSU) @@ -77,7 +76,6 @@ uint32_t readTrims() result |= 0x400; if (~TRIMS_GPIO_REG_RSU & TRIMS_GPIO_PIN_RSU) result |= 0x800; -#endif // TRACE("readTrims(): result=0x%04x", result); return result; @@ -164,7 +162,7 @@ uint8_t keyState(uint8_t index) uint32_t switchState(uint8_t index) { uint32_t xxx = 0; - + switch (index) { ADD_3POS_CASE(A, 0); ADD_3POS_CASE(B, 1); diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index 5c5ea5234..332c3b78d 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -26,7 +26,9 @@ if(Qt5Widgets_FOUND) set(SIMULATOR_TARGET ${SIMULATOR_FLAVOUR}-simulator) add_definitions(-DSIMULATOR_FLAVOUR="${SIMULATOR_FLAVOUR}") include_directories(${COMPANION_SRC_DIRECTORY} ${COMPANION_SRC_DIRECTORY}/simulation) - add_library(${SIMULATOR_TARGET} SHARED ${SIMU_SRC} opentxsimulator.cpp) + qt5_wrap_cpp(SIMULATOR_SRC ${COMPANION_SRC_DIRECTORY}/simulation/simulatorinterface.h opentxsimulator.h) + list(APPEND SIMULATOR_SRC ${SIMU_SRC} opentxsimulator.cpp) + add_library(${SIMULATOR_TARGET} SHARED ${SIMULATOR_SRC}) add_dependencies(${SIMULATOR_TARGET} ${FIRMWARE_DEPENDENCIES}) target_compile_definitions(${SIMULATOR_TARGET} PUBLIC ${APP_COMMON_DEFINES}) # set in top-level CMakeLists target_link_libraries(${SIMULATOR_TARGET} ${SDL_LIBRARY}) diff --git a/radio/src/targets/simu/opentxsimulator.cpp b/radio/src/targets/simu/opentxsimulator.cpp index f85be7b9e..70cde32c4 100644 --- a/radio/src/targets/simu/opentxsimulator.cpp +++ b/radio/src/targets/simu/opentxsimulator.cpp @@ -18,7 +18,23 @@ #include "opentx.h" #include "simulcd.h" +#include +#include + +#if !defined(MAX_LOGICAL_SWITCHES) && defined(NUM_CSW) + #define MAX_LOGICAL_SWITCHES NUM_CSW +#endif + +#if defined(CPUARM) + #define GET_SWITCH_BOOL(sw__) getSwitch((sw__), 0); +#else + #define GET_SWITCH_BOOL(sw__) getSwitch(sw__); +#endif + +#define OTXS_DBG qDebug() << "(" << simuTimerMicros() << "us)" + int16_t g_anas[Analogs::NUM_ANALOGS]; +QVector OpenTxSimulator::tracebackDevices; uint16_t anaIn(uint8_t chan) { @@ -30,36 +46,135 @@ uint16_t getAnalogValue(uint8_t index) return anaIn(index); } -bool hasExtendedTrims() +void firmwareTraceCb(const char * text) { - return g_model.extendedTrims; + foreach (QIODevice * dev, OpenTxSimulator::tracebackDevices) { + if (dev) + dev->write(text); + } } -uint8_t getStickMode() +OpenTxSimulator::OpenTxSimulator() : + SimulatorInterface(), + m_timer10ms(NULL), + m_resetOutputsData(true), + m_stopRequested(false) { - return limit(0, g_eeGeneral.stickMode, 3); + tracebackDevices.clear(); + traceCallback = firmwareTraceCb; } - -OpenTxSimulator::OpenTxSimulator() +OpenTxSimulator::~OpenTxSimulator() { + traceCallback = NULL; + tracebackDevices.clear(); + + if (m_timer10ms) + delete m_timer10ms; + + if (isRunning()) { + stop(); + QElapsedTimer tmout; + tmout.start(); + while (isRunning() && !tmout.hasExpired(1000)) + ; + } + //qDebug() << "Deleting OpenTxSimulator"; +} + +QString OpenTxSimulator::name() +{ + return QString(SIMULATOR_FLAVOUR); +} + +bool OpenTxSimulator::isRunning() +{ + QMutexLocker lckr(&m_mtxSimuMain); + return (bool)main_thread_running; +} + +void OpenTxSimulator::init() +{ + if (isRunning()) + return; + OTXS_DBG; + + if (!m_timer10ms) { + // make sure we create & control the timer from current thread + m_timer10ms = new QTimer(); + m_timer10ms->setInterval(10); + connect(m_timer10ms, &QTimer::timeout, this, &OpenTxSimulator::run); + connect(this, SIGNAL(started()), m_timer10ms, SLOT(start())); + connect(this, SIGNAL(stopped()), m_timer10ms, SLOT(stop())); + } + + m_resetOutputsData = true; + setStopRequested(false); + + QMutexLocker lckr(&m_mtxSimuMain); + memset(g_anas, 0, sizeof(g_anas)); + simuInit(); +} + +void OpenTxSimulator::start(const char * filename, bool tests) +{ + if (isRunning()) + return; + OTXS_DBG << "file:" << filename << "tests:" << tests; + + QMutexLocker lckr(&m_mtxSimuMain); + QMutexLocker slckr(&m_mtxSettings); + StartEepromThread(filename); + StartAudioThread(volumeGain); + StartSimu(tests, simuSdDirectory.toLatin1().constData(), simuSettingsDirectory.toLatin1().constData()); + + emit started(); + QTimer::singleShot(0, this, SLOT(run())); // old style for Qt < 5.4 +} + +void OpenTxSimulator::stop() +{ + if (!isRunning()) + return; + OTXS_DBG; + + setStopRequested(true); + + QMutexLocker lckr(&m_mtxSimuMain); + StopSimu(); + StopAudioThread(); + StopEepromThread(); + + emit stopped(); } void OpenTxSimulator::setSdPath(const QString & sdPath, const QString & settingsPath) { + QMutexLocker lckr(&m_mtxSettings); simuSdDirectory = sdPath; simuSettingsDirectory = settingsPath; } -void OpenTxSimulator::setVolumeGain(int value) +void OpenTxSimulator::setVolumeGain(const int value) { + QMutexLocker lckr(&m_mtxSettings); volumeGain = value; } -bool OpenTxSimulator::timer10ms() +void OpenTxSimulator::setRadioData(const QByteArray & data) { -#define TIMER10MS_IMPORT -#include "simulatorimport.h" +#if defined(EEPROM_SIZE) + QMutexLocker lckr(&m_mtxRadioData); + memcpy(eeprom, data.data(), qMin(EEPROM_SIZE, data.size())); +#endif +} + +void OpenTxSimulator::readRadioData(QByteArray & dest) +{ +#if defined(EEPROM_SIZE) + QMutexLocker lckr(&m_mtxRadioData); + memcpy(dest.data(), eeprom, std::min(EEPROM_SIZE, dest.size())); +#endif } uint8_t * OpenTxSimulator::getLcd() @@ -67,100 +182,364 @@ uint8_t * OpenTxSimulator::getLcd() return (uint8_t *)simuLcdBuf; } -bool OpenTxSimulator::lcdChanged(bool & lightEnable) +void OpenTxSimulator::setAnalogValue(uint8_t index, int16_t value) { -#define LCDCHANGED_IMPORT -#include "simulatorimport.h" + static int dim = DIM(g_anas); + if (index < dim) + g_anas[index] = value; } -void OpenTxSimulator::start(QByteArray & ee, bool tests) +void OpenTxSimulator::setSwitch(uint8_t swtch, int8_t state) { -#if defined(EEPROM_SIZE) - memcpy(eeprom, ee.data(), std::min(EEPROM_SIZE, ee.size())); -#endif - start((const char *)0, tests); + simuSetSwitch(swtch, state); } -void OpenTxSimulator::start(const char * filename, bool tests) +void OpenTxSimulator::setKey(uint8_t key, bool state) { - if (main_thread_running) - return; - - StartEepromThread(filename); - StartAudioThread(volumeGain); - StartSimu(tests, simuSdDirectory.toLatin1().constData(), simuSettingsDirectory.toLatin1().constData()); + simuSetKey(key, state); } -void OpenTxSimulator::stop() +void OpenTxSimulator::setTrimSwitch(uint8_t trim, bool state) { - if (!main_thread_running) - return; - - StopSimu(); -#if defined(CPUARM) - StopAudioThread(); -#endif - StopEepromThread(); -} - -void OpenTxSimulator::readEepromData(QByteArray & dest) -{ -#if defined(EEPROM_SIZE) - memcpy(dest.data(), eeprom, std::min(EEPROM_SIZE, dest.size())); -#endif -} - -void OpenTxSimulator::getValues(TxOutputs &outputs) -{ -#define GETVALUES_IMPORT -#define g_chans512 channelOutputs -#include "simulatorimport.h" -} - -void OpenTxSimulator::setValues(TxInputs &inputs) -{ -#define SETVALUES_IMPORT -#include "simulatorimport.h" + simuSetTrim(trim, state); } void OpenTxSimulator::setTrim(unsigned int idx, int value) { - idx = modn12x3[4*getStickMode() + idx]; - uint8_t phase = getTrimFlightMode(getFlightMode(), idx); - setTrimValue(phase, idx, value); + unsigned i = idx; + if (i < 4) // swap axes + i = modn12x3[4 * getStickMode() + idx]; + uint8_t phase = getTrimFlightMode(getFlightMode(), i); + + if (!setTrimValue(phase, i, value)) { + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, [=]() { + emit trimValueChange(idx, 0); + emit outputValueChange(OUTPUT_SRC_TRIM_VALUE, idx, 0); + timer->deleteLater(); + }); + timer->start(350); + } } -void OpenTxSimulator::getTrims(Trims & trims) +void OpenTxSimulator::setTrainerInput(unsigned int inputNumber, int16_t value) { - uint8_t phase = getFlightMode(); - trims.extended = hasExtendedTrims(); - for (uint8_t idx=0; idx < CPN_MAX_TRIMS; idx++) { - trims.values[idx] = getTrimValue(getTrimFlightMode(phase, idx), idx); - } + static unsigned dim = DIM(ppmInput); + //setTrainerTimeout(100); + if (inputNumber < dim) + ppmInput[inputNumber] = qMin(qMax((int16_t)-512, value), (int16_t)512); +} - for (int i=0; i<2; i++) { - uint8_t idx = modn12x3[4*getStickMode() + i]; - int16_t tmp = trims.values[i]; - trims.values[i] = trims.values[idx]; - trims.values[idx] = tmp; +void OpenTxSimulator::setInputValue(int type, uint8_t index, int16_t value) +{ + //qDebug() << type << index << value << this->thread(); + switch (type) { + case INPUT_SRC_ANALOG : + case INPUT_SRC_STICK : + setAnalogValue(index, value); + break; + case INPUT_SRC_KNOB : + setAnalogValue(index + NUM_STICKS, value); + break; + case INPUT_SRC_SLIDER : + setAnalogValue(index + NUM_STICKS + NUM_POTS, value); + break; + case INPUT_SRC_TXVIN : + setAnalogValue(Analogs::TX_VOLTAGE, voltageToAdc(value)); + break; + case INPUT_SRC_SWITCH : + setSwitch(index, (int8_t)value); + break; + case INPUT_SRC_TRIM_SW : + setTrimSwitch(index, (bool)value); + break; + case INPUT_SRC_TRIM : + setTrim(index, value); + break; + case INPUT_SRC_KEY : + setKey(index, (bool)value); + break; + case INPUT_SRC_TRAINER : + setTrainerInput(index, value); + break; + case INPUT_SRC_ROTENC : // TODO + default: + return; } } -void OpenTxSimulator::wheelEvent(int steps) +void OpenTxSimulator::rotaryEncoderEvent(int steps) { #if defined(ROTARY_ENCODER_NAVIGATION) ROTARY_ENCODER_NAVIGATION_VALUE += steps * ROTARY_ENCODER_GRANULARITY; #else + // TODO : this should probably be handled in the GUI + int key; if (steps > 0) - simuSetKey(KEY_MINUS, 1); + key = KEY_MINUS; else if (steps < 0) - simuSetKey(KEY_PLUS, 1); + key = KEY_PLUS; + + setKey(key, 1); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) + QTimer::singleShot(10, [this, key]() { setKey(key, 0); }); +#else + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, [=]() { + setKey(key, 0); + timer->deleteLater(); + } ); + timer->start(10); +#endif +#endif // defined(ROTARY_ENCODER_NAVIGATION) +} + +void OpenTxSimulator::setTrainerTimeout(uint16_t ms) +{ + ppmInputValidityTimer = ms; +} + +void OpenTxSimulator::sendTelemetry(uint8_t * data, unsigned int len) +{ + Q_UNUSED(len) +#if defined(TELEMETRY_FRSKY_SPORT) + sportProcessTelemetryPacket(data); +#else + Q_UNUSED(data) #endif } -unsigned int OpenTxSimulator::getPhase() +uint8_t OpenTxSimulator::getSensorInstance(uint16_t id, uint8_t defaultValue) { - return getFlightMode(); +#if defined(TELEMETRY_FRSKY_SPORT) + for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) { + if (isTelemetryFieldAvailable(i)) { + TelemetrySensor * sensor = &g_model.telemetrySensors[i]; + if (sensor->id == id) { + return sensor->instance; + } + } + } +#else + Q_UNUSED(id) +#endif + return defaultValue; +} + +uint16_t OpenTxSimulator::getSensorRatio(uint16_t id) +{ +#if defined(TELEMETRY_FRSKY_SPORT) + for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) { + if (isTelemetryFieldAvailable(i)) { + TelemetrySensor * sensor = &g_model.telemetrySensors[i]; + if (sensor->id == id) { + return sensor->custom.ratio; + } + } + } +#else + Q_UNUSED(id) +#endif + return 0; +} + +const int OpenTxSimulator::getCapability(Capability cap) +{ + int ret = 0; + switch(cap) { + case CAP_LUA : + #ifdef LUA + ret = 1; + #endif + break; + + case CAP_ROTARY_ENC : + #ifdef ROTARY_ENCODERS + ret = ROTARY_ENCODERS; + #endif + break; + + case CAP_ROTARY_ENC_NAV : + #ifdef ROTARY_ENCODER_NAVIGATION + ret = 1; + #endif + break; + + case CAP_TELEM_FRSKY_SPORT : + #ifdef TELEMETRY_FRSKY_SPORT + ret = 1; + #endif + break; + } + return ret; +} + +void OpenTxSimulator::setLuaStateReloadPermanentScripts() +{ +#if defined(LUA) + luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; +#endif +} + +void OpenTxSimulator::addTracebackDevice(QIODevice * device) +{ + QMutexLocker lckr(&m_mtxTbDevices); + if (device && !tracebackDevices.contains(device)) + tracebackDevices.append(device); +} + +void OpenTxSimulator::removeTracebackDevice(QIODevice * device) +{ + if (device) { + QMutexLocker lckr(&m_mtxTbDevices); + // no QVector::removeAll() in Qt < 5.4 + int i = 0; + foreach (QIODevice * d, tracebackDevices) { + if (d == device) + tracebackDevices.remove(i); + ++i; + } + } +} + + +/*** Protected classes ***/ + +void OpenTxSimulator::run() +{ + static uint32_t loops = 0; + static QElapsedTimer ts; + + if (!loops) + ts.start(); + + if (isStopRequested()) { + return; + } + if (!isRunning()) { + QString err(getError()); + emit runtimeError(err); + emit stopped(); + return; + } + + ++loops; + + per10ms(); + + checkLcdChanged(); + + if (!(loops % 5)) { + checkOutputsChanged(); + } + + if (!(loops % (SIMULATOR_INTERFACE_HEARTBEAT_PERIOD / 10))) { + emit heartbeat(loops, simuTimerMicros() / 1000); + } +} + +bool OpenTxSimulator::isStopRequested() +{ + QMutexLocker lckr(&m_mtxStopReq); + return m_stopRequested; +} + +void OpenTxSimulator::setStopRequested(bool stop) +{ + QMutexLocker lckr(&m_mtxStopReq); + m_stopRequested = stop; +} + +bool OpenTxSimulator::checkLcdChanged() +{ + if (simuLcdRefresh) { + simuLcdRefresh = false; + emit lcdChange(isBacklightEnabled()); + return true; + } + return false; +} + +void OpenTxSimulator::checkOutputsChanged() +{ + static TxOutputs lastOutputs; + static size_t chansDim = DIM(channelOutputs); + qint32 tmpVal; + uint8_t i, idx; + uint8_t phase = getFlightMode(); // opentx.cpp + uint8_t mode = getStickMode(); + + for (i=0; i < chansDim; i++) { + if (lastOutputs.chans[i] != channelOutputs[i] || m_resetOutputsData) { + emit channelOutValueChange(i, channelOutputs[i]); + emit channelMixValueChange(i, ex_chans[i]); + emit outputValueChange(OUTPUT_SRC_CHAN_OUT, i, channelOutputs[i]); + emit outputValueChange(OUTPUT_SRC_CHAN_MIX, i, ex_chans[i]); + lastOutputs.chans[i] = channelOutputs[i]; + } + } + + for (i=0; i < MAX_LOGICAL_SWITCHES; i++) { + tmpVal = (qint32)GET_SWITCH_BOOL(SWSRC_SW1+i); + if (lastOutputs.vsw[i] != (bool)tmpVal || m_resetOutputsData) { + emit virtualSwValueChange(i, tmpVal); + emit outputValueChange(OUTPUT_SRC_VIRTUAL_SW, i, tmpVal); + lastOutputs.vsw[i] = tmpVal; + } + } + + for (i=0; i < Board::TRIM_AXIS_COUNT; i++) { + if (i < 4) // swap axes + idx = modn12x3[4 * mode + i]; + else + idx = i; + + tmpVal = getTrimValue(getTrimFlightMode(phase, idx), idx); + if (lastOutputs.trims[i] != tmpVal || m_resetOutputsData) { + emit trimValueChange(i, tmpVal); + emit outputValueChange(OUTPUT_SRC_TRIM_VALUE, i, tmpVal); + lastOutputs.trims[i] = tmpVal; + } + } + + tmpVal = g_model.extendedTrims ? TRIM_EXTENDED_MAX : TRIM_MAX; + if (lastOutputs.trimRange != tmpVal || m_resetOutputsData) { + emit trimRangeChange(Board::TRIM_AXIS_COUNT, -tmpVal, tmpVal); + emit outputValueChange(OUTPUT_SRC_TRIM_RANGE, Board::TRIM_AXIS_COUNT, tmpVal); + lastOutputs.trimRange = tmpVal; + } + + if (lastOutputs.phase != phase || m_resetOutputsData) { + emit phaseChanged(phase, getCurrentPhaseName()); + emit outputValueChange(OUTPUT_SRC_PHASE, 0, qint16(phase)); + lastOutputs.phase = phase; + } + +#if defined(GVAR_VALUE) && defined(GVARS) + gVarMode_t gvar; + for (uint8_t fm=0; fm < MAX_FLIGHT_MODES; fm++) { + gvar.mode = fm; + for (uint8_t gv=0; gv < MAX_GVARS; gv++) { + tmpVal = GVAR_VALUE(gv, getGVarFlightMode(fm, gv)); + if (lastOutputs.gvars[fm][gv] != tmpVal || m_resetOutputsData) { + lastOutputs.gvars[fm][gv] = tmpVal; + gvar.value = (int16_t)tmpVal; + tmpVal = gvar; + emit gVarValueChange(gv, tmpVal); + emit outputValueChange(OUTPUT_SRC_GVAR, gv, tmpVal); + } + } + } +#endif + + m_resetOutputsData = false; +} + +uint8_t OpenTxSimulator::getStickMode() +{ + return limit(0, g_eeGeneral.stickMode, 3); } const char * OpenTxSimulator::getPhaseName(unsigned int phase) @@ -170,66 +549,39 @@ const char * OpenTxSimulator::getPhaseName(unsigned int phase) return buff; } +const QString OpenTxSimulator::getCurrentPhaseName() +{ + unsigned phase = getFlightMode(); + QString name(getPhaseName(phase)); + if (name.isEmpty()) + name = QString::number(phase); + return name; +} + const char * OpenTxSimulator::getError() { -#define GETERROR_IMPORT -#include "simulatorimport.h" + return main_thread_error; } -void OpenTxSimulator::sendTelemetry(uint8_t * data, unsigned int len) +const int OpenTxSimulator::voltageToAdc(const int volts) { -#if defined(TELEMETRY_FRSKY_SPORT) - sportProcessTelemetryPacket(data); + int ret = 0; +#if defined(PCBHORUS) || defined(PCBX7) + ret = (float)volts * 16.2f; +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBSKY9X) + ret = (float)volts * 13.3f; +#elif defined(PCBGRUVIN9X) + ret = (float)volts * 1.63f; +#else + ret = (float)volts * 14.15f; #endif + return ret; } -uint8_t OpenTxSimulator::getSensorInstance(uint16_t id, uint8_t defaultValue) -{ -#if defined(TELEMETRY_FRSKY_SPORT) - for (int i = 0; iid == id) { - return sensor->instance; - } - } - } -#endif - return defaultValue; -} -uint16_t OpenTxSimulator::getSensorRatio(uint16_t id) -{ -#if defined(TELEMETRY_FRSKY_SPORT) - for (int i = 0; iid == id) { - return sensor->custom.ratio; - } - } - } -#endif - return 0; -} - -void OpenTxSimulator::setTrainerInput(unsigned int inputNumber, int16_t value) -{ -#define SETTRAINER_IMPORT -#include "simulatorimport.h" -} - -void OpenTxSimulator::setLuaStateReloadPermanentScripts() -{ -#if defined(LUA) - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; -#endif -} - -void OpenTxSimulator::installTraceHook(void (*callback)(const char *)) -{ - traceCallback = callback; -} +/* + * OpenTxSimulatorFactory + */ class OpenTxSimulatorFactory: public SimulatorFactory { diff --git a/radio/src/targets/simu/opentxsimulator.h b/radio/src/targets/simu/opentxsimulator.h index 700a56ef3..d3ba6689a 100644 --- a/radio/src/targets/simu/opentxsimulator.h +++ b/radio/src/targets/simu/opentxsimulator.h @@ -19,68 +19,84 @@ #include "simulatorinterface.h" +#include +#include +#include + #if defined __GNUC__ #define DLLEXPORT #else #define DLLEXPORT __declspec(dllexport) #endif -class DLLEXPORT OpenTxSimulator : public SimulatorInterface { - - private: - int volumeGain; - QString simuSdDirectory; - QString simuSettingsDirectory; +class DLLEXPORT OpenTxSimulator : public SimulatorInterface +{ + Q_OBJECT public: OpenTxSimulator(); + virtual ~OpenTxSimulator(); - virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = ""); - - virtual void setVolumeGain(int value); - - virtual void start(QByteArray & eeprom, bool tests=true); - - virtual void start(const char * filename, bool tests=true); - - virtual void stop(); - - virtual void readEepromData(QByteArray & dest); - - virtual bool timer10ms(); - + virtual QString name(); + virtual bool isRunning(); + virtual void readRadioData(QByteArray & dest); virtual uint8_t * getLcd(); - - virtual bool lcdChanged(bool & lightEnable); - - virtual void setValues(TxInputs &inputs); - - virtual void getValues(TxOutputs &outputs); - - virtual void setTrim(unsigned int idx, int value); - - virtual void getTrims(Trims & trims); - - virtual unsigned int getPhase(); - - virtual const char * getPhaseName(unsigned int phase); - - virtual void wheelEvent(int steps); - - virtual const char * getError(); - - virtual void sendTelemetry(uint8_t * data, unsigned int len); - virtual uint8_t getSensorInstance(uint16_t id, uint8_t defaultValue = 0); - virtual uint16_t getSensorRatio(uint16_t id); + virtual const int getCapability(Capability cap); + static QVector tracebackDevices; + + public slots: + + virtual void init(); + virtual void start(const char * filename = NULL, bool tests = true); + virtual void stop(); + virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = ""); + virtual void setVolumeGain(const int value); + virtual void setRadioData(const QByteArray & data); + virtual void setAnalogValue(uint8_t index, int16_t value); + virtual void setKey(uint8_t key, bool state); + virtual void setSwitch(uint8_t swtch, int8_t state); + virtual void setTrim(unsigned int idx, int value); + virtual void setTrimSwitch(uint8_t trim, bool state); virtual void setTrainerInput(unsigned int inputNumber, int16_t value); - - virtual void installTraceHook(void (*callback)(const char *)); - + virtual void setInputValue(int type, uint8_t index, int16_t value); + virtual void rotaryEncoderEvent(int steps); + virtual void setTrainerTimeout(uint16_t ms); + virtual void sendTelemetry(uint8_t * data, unsigned int len); virtual void setLuaStateReloadPermanentScripts(); + virtual void addTracebackDevice(QIODevice * device); + virtual void removeTracebackDevice(QIODevice * device); + + protected slots: + void run(); + + protected: + + bool isStopRequested(); + void setStopRequested(bool stop); + bool checkLcdChanged(); + void checkOutputsChanged(); + uint8_t getStickMode(); + const char * getPhaseName(unsigned int phase); + const QString getCurrentPhaseName(); + const char * getError(); + const int voltageToAdc(const int volts); + + QString simuSdDirectory; + QString simuSettingsDirectory; + QTimer * m_timer10ms; + QMutex m_mtxStopReq; + QMutex m_mtxSimuMain; + QMutex m_mtxRadioData; + QMutex m_mtxSettings; + QMutex m_mtxTbDevices; + int volumeGain; + bool m_resetOutputsData; + bool m_stopRequested; + }; #endif // _OPENTX_SIMULATOR_H_ diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 23ba0aa09..de5d90df2 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -257,10 +257,10 @@ void simuSetTrim(uint8_t trim, bool state) TRIM_CASE(6, TRIMS_GPIO_REG_RHL, TRIMS_GPIO_PIN_RHL) TRIM_CASE(7, TRIMS_GPIO_REG_RHR, TRIMS_GPIO_PIN_RHR) #if defined(PCBHORUS) - TRIM_CASE(8, TRIMS_GPIO_REG_LSU, TRIMS_GPIO_PIN_LSU) - TRIM_CASE(9, TRIMS_GPIO_REG_LSD, TRIMS_GPIO_PIN_RVU) - TRIM_CASE(10, TRIMS_GPIO_REG_RSU, TRIMS_GPIO_PIN_RHL) - TRIM_CASE(11, TRIMS_GPIO_REG_RSD, TRIMS_GPIO_PIN_RHR) + TRIM_CASE(8, TRIMS_GPIO_REG_LSD, TRIMS_GPIO_PIN_LSD) + TRIM_CASE(9, TRIMS_GPIO_REG_LSU, TRIMS_GPIO_PIN_LSU) + TRIM_CASE(10, TRIMS_GPIO_REG_RSD, TRIMS_GPIO_PIN_RSD) + TRIM_CASE(11, TRIMS_GPIO_REG_RSU, TRIMS_GPIO_PIN_RSU) #endif } }