From 6e2474081f216d255853cbec56a997d4ac8eab64 Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Wed, 25 Jan 2017 10:20:45 -0500 Subject: [PATCH] [Simulator] Further Simulator improvements. (#4282) * Refactor CompStoreObj to use templates. Clean up some formatting. * [Simulator] Add SimulatorOptions type to replace individual simulator options; Fix storage handler for custom types; Cleanup some redundant code. * [Simulator][Joystick] Rework the joystick options/calibration dialog. Can now enable/disable/select the Joystick from there. Delay start of calibration until Start is pressed (otherwise looks like last calibration didn't work). And generally refactor a lot of it to be smarter. * Cosmetics. * [Simulator] Add state handling for RadioWidget class. Split code to cpp file. * [Simulator] Add SimulatorStartupDialog as its own UI class. * [Simulator] Multiple changes and new features: All Horus data source options are now fully supported with data properly saved after use (SD path, .otx file, or custom data path); Loading .otx files now works for all radio types (but not saving data back to .otx); Switch and knob/slider states are now saved to per radio profile; Joystick configuration/calibration is now available directly from inside Simulator; Due to refactoring, all previously saved per-profile simulator settings are now invalid; Further consolidated simulator dialog startup/shutdown, now handles temp. paths internally, etc; With WIN_USE_CONSOLE defined, Simulator and Companion when started from Windows command line will now use that console for all messages (instead of opening a new one). This allows full debug output to be seen at all stages of execution and after exit. * [Simulator] Fix build w/out SDL Joystick support. * [Simulator] Fix bogus throttle warning on simulator start; Add forgotten RadioWidgetState flags to stream handlers; Add debug state saver in SimulatorOptions. --- companion/src/CMakeLists.txt | 7 +- companion/src/helpers.cpp | 45 +- companion/src/simulation/CMakeLists.txt | 7 +- companion/src/simulation/joystickdialog.cpp | 268 ++- companion/src/simulation/joystickdialog.h | 32 +- companion/src/simulation/joystickdialog.ui | 1852 +++++++++-------- companion/src/simulation/simulateduiwidget.h | 2 + companion/src/simulation/simulator.h | 89 + companion/src/simulation/simulatordialog.cpp | 413 +++- companion/src/simulation/simulatordialog.h | 26 +- companion/src/simulation/simulatordialog.ui | 24 +- .../src/simulation/simulatorstartupdialog.cpp | 257 +++ .../src/simulation/simulatorstartupdialog.h | 69 + .../src/simulation/simulatorstartupdialog.ui | 357 ++++ .../src/simulation/widgets/radiofaderwidget.h | 4 +- .../src/simulation/widgets/radioknobwidget.h | 4 +- .../simulation/widgets/radioswitchwidget.h | 14 +- .../src/simulation/widgets/radiowidget.cpp | 201 ++ .../src/simulation/widgets/radiowidget.h | 159 +- companion/src/simulator.cpp | 353 +--- companion/src/storage/appdata.cpp | 390 ++-- companion/src/storage/appdata.h | 81 +- 22 files changed, 2869 insertions(+), 1785 deletions(-) create mode 100644 companion/src/simulation/simulator.h create mode 100644 companion/src/simulation/simulatorstartupdialog.cpp create mode 100644 companion/src/simulation/simulatorstartupdialog.h create mode 100644 companion/src/simulation/simulatorstartupdialog.ui create mode 100644 companion/src/simulation/widgets/radiowidget.cpp diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 32e5c47fd..4772ddd72 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -71,6 +71,9 @@ endif() if(WIN32) include_directories(SYSTEM ${WIN_INCLUDE_DIRS}) + if(NOT WIN_USE_CONSOLE) + set(WIN_EXECUTABLE_TYPE WIN32) # GUI (WinMain) app + endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /LD /MP") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS,5.01") @@ -246,7 +249,7 @@ add_custom_target(gen_qrc DEPENDS ${companion_RCC}) add_definitions(-DQT_TRANSLATIONS_DIR="${QT_TRANSLATIONS_DIR}") -add_executable(${COMPANION_NAME} MACOSX_BUNDLE WIN32 ${companion_SRCS} ${companion_RCC} ${companion_QM}) +add_executable(${COMPANION_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE} ${companion_SRCS} ${companion_RCC} ${companion_QM}) add_dependencies(${COMPANION_NAME} gen_qrc) qt5_use_modules(${COMPANION_NAME} Core Widgets Network) @@ -275,7 +278,7 @@ if(WIN32) list(APPEND simu_SRCS icon.rc) endif() -add_executable(${SIMULATOR_NAME} MACOSX_BUNDLE WIN32 ${simu_SRCS} ${companion_RCC}) +add_executable(${SIMULATOR_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE} ${simu_SRCS} ${companion_RCC}) add_dependencies(${SIMULATOR_NAME} gen_qrc) qt5_use_modules(${SIMULATOR_NAME} Core Widgets Multimedia) diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index 95ea61f8f..50756f67b 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -816,18 +816,17 @@ void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) SimulatorInterface * simulator = getCurrentSimulator(); if (simulator) { #if defined(WIN32) && defined(WIN_USE_CONSOLE_STDIO) - AllocConsole(); - SetConsoleTitle("Companion Console"); - freopen("conin$", "r", stdin); - freopen("conout$", "w", stdout); - freopen("conout$", "w", stderr); + bool freeConsoleOnClose = false; + if (!GetConsoleWindow()) { + AllocConsole(); + SetConsoleTitle("Companion Console"); + freeConsoleOnClose = true; + freopen("conin$", "r", stdin); + freopen("conout$", "w", stdout); + freopen("conout$", "w", stderr); + } #endif - Firmware * firmware = getCurrentFirmware(); RadioData * simuData = new RadioData(radioData); - Board::Type board = getCurrentBoard(); - QByteArray eeprom = QByteArray(); - QString sdPath = g.profile[g.id()].sdPath(); - QString settingsPath = ""; unsigned int flags = 0; if (modelIdx >= 0) { @@ -835,29 +834,21 @@ void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) simuData->setCurrentModel(modelIdx); } - if (IS_HORUS(board)) { - QTemporaryDir tmpDir(QDir::tempPath() + "/otx-XXXXXX"); - settingsPath = tmpDir.path(); - tmpDir.setAutoRemove(false); - } - - qDebug() << "Starting" << firmware->getName() << "simulation with SD path" << sdPath << "and models/settings path" << settingsPath; - SimulatorDialog * dialog = new SimulatorDialog(parent, simulator, flags); - dialog->setPaths(sdPath, settingsPath); + if (IS_HORUS(getCurrentBoard()) && !dialog->useTempDataPath(true)) { + QMessageBox::critical(NULL, QObject::tr("Data Load Error"), QObject::tr("Error: Could not create temporary directory in '%1'").arg(QDir::tempPath())); + return; + } dialog->setRadioData(simuData); + qDebug() << __FILE__ << __LINE__ << "Starting" << getCurrentFirmware()->getName() << "simulation with SD path" << dialog->getSdPath() << "and models/settings path" << dialog->getDataPath(); dialog->start(); dialog->exec(); dialog->deleteLater(); - if (!settingsPath.isEmpty()) { - // TODO either delete tmp directory and its contents OR use it to get back data from the simulation - qDebug() << "Simulation finished, deleting temporary settings directory" << settingsPath; - QDir tmp(settingsPath); - tmp.removeRecursively(); - } - delete simuData; + // TODO Horus tmp directory is deleted on simulator close OR we could use it to get back data from the simulation + delete simuData; // TODO same with simuData, could read it back and update the file data #if defined(WIN32) && defined(WIN_USE_CONSOLE_STDIO) - FreeConsole(); + if (freeConsoleOnClose) + FreeConsole(); #endif } else { diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index c29fb9225..6514b6283 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -7,8 +7,10 @@ set(simulation_SRCS simulateduiwidgetX7.cpp simulateduiwidgetX9.cpp simulateduiwidgetX12.cpp + simulatorstartupdialog.cpp telemetrysimu.cpp trainersimu.cpp + widgets/radiowidget.cpp widgets/virtualjoystickwidget.cpp ) @@ -19,6 +21,7 @@ set(simulation_UIS simulateduiwidgetX7.ui simulateduiwidgetX9.ui simulateduiwidgetX12.ui + simulatorstartupdialog.ui telemetrysimu.ui trainersimu.ui ) @@ -26,8 +29,10 @@ set(simulation_UIS set(simulation_HDRS debugoutput.h radiouiaction.h - simulatordialog.h simulateduiwidget.h + # simulator.h + simulatordialog.h + simulatorstartupdialog.h telemetrysimu.h trainersimu.h widgets/buttonswidget.h diff --git a/companion/src/simulation/joystickdialog.cpp b/companion/src/simulation/joystickdialog.cpp index 34acb679b..76a08e5c2 100644 --- a/companion/src/simulation/joystickdialog.cpp +++ b/companion/src/simulation/joystickdialog.cpp @@ -20,12 +20,16 @@ #include "joystickdialog.h" #include "ui_joystickdialog.h" -#include "appdata.h" joystickDialog::joystickDialog(QWidget *parent, int stick) : -QDialog(parent), -ui(new Ui::joystickDialog) { + QDialog(parent), + ui(new Ui::joystickDialog), + step(0), + numAxes(0), + started(false) +{ ui->setupUi(this); + int jscaltmp[8][3] = { {32767, 0, -32767}, {32767, 0, -32767}, @@ -37,137 +41,231 @@ ui(new Ui::joystickDialog) { {32767, 0, -32767} }; memcpy(jscal, jscaltmp, sizeof (jscal)); - ui->okButton->setDisabled(true); - ui->howtoLabel->setText(tr("Move sticks and pots in every direction making full movement\nPress next when finished")); - step = 0; foreach(QComboBox *cb, findChildren(QRegExp("jsmapCB_[0-9]+"))) { - cb->setDisabled(true); + sticks[cb->property("channel").toUInt()] = cb; } foreach(QCheckBox *ib, findChildren(QRegExp("ChInv_[0-9]+"))) { - ib->setDisabled(true); + invert[ib->property("channel").toUInt()] = ib; } - joystickOpen(stick); + foreach(QSlider *sl, findChildren(QRegExp("Ch_[0-9]+"))) { + sliders[sl->property("channel").toUInt()] = sl; + } + + for (int i = 0; i < MAX_JOYSTICKS; ++i) { + if (!g.joystick[i].existsOnDisk()) + continue; + + jscal[i][0] = g.joystick[i].stick_min(); + jscal[i][1] = g.joystick[i].stick_med(); + jscal[i][2] = g.joystick[i].stick_max(); + sliders[i]->setMinimum(jscal[i][0]); + sliders[i]->setMaximum(jscal[i][2]); + invert[i]->setChecked(g.joystick[i].stick_inv()); + sticks[i]->setCurrentIndex(g.joystick[i].stick_axe() + 1); + } + + ui->backButton->setEnabled(false); + + ui->joystickChkB->setChecked(g.jsSupport() || stick > -1); + + if (stick < 0) + stick = g.jsCtrl(); + + loadJoysticks(stick); + loadStep(); + connect(joystick, SIGNAL(axisValueChanged(int, int)), this, SLOT(onjoystickAxisValueChanged(int, int))); + connect(ui->joystickCB, SIGNAL(currentIndexChanged(int)), this, SLOT(joystickOpen(int))); + connect(ui->joystickChkB, SIGNAL(toggled(bool)), this, SLOT(joystickSetEnabled(bool))); } -joystickDialog::~joystickDialog() { +joystickDialog::~joystickDialog() +{ delete ui; } -void joystickDialog::joystickOpen(int stick) { - if (stick == -1) { - this->close(); +void joystickDialog::loadJoysticks(int stick) +{ + QStringList joystickNames; + bool found = false; + + ui->joystickCB->clear(); + joystickNames << tr("No joysticks found"); + joystick = new Joystick(0, false, 0, 0); + if ( joystick ) { + if ( joystick->joystickNames.count() > 0 ) { + joystickNames = joystick->joystickNames; + found = true; + } + joystick->close(); + } + ui->joystickCB->insertItems(0, joystickNames); + if (found && stick < joystickNames.size()) + ui->joystickCB->setCurrentIndex(stick); + else if (!found) + ui->joystickCB->setCurrentIndex(0); + + joystickSetEnabled(found && ui->joystickChkB->isChecked()); +} + +void joystickDialog::joystickOpen(int stick) +{ + if (stick == -1 || !ui->joystickChkB->isChecked() || !ui->joystickCB->isEnabled()) { + ui->calibrationWidget->setDisabled(true); return; } + + ui->calibrationWidget->setEnabled(true); + ui->nextButton->setEnabled(true); + joystick = new Joystick(this, 1, false, 0); - int numAxes=std::min(joystick->numAxes,8); - if (joystick) { - joystick->open(stick); - + if (joystick && joystick->open(stick)) { + numAxes = std::min(joystick->numAxes, MAX_JOYSTICKS); for (int j=0; jsensitivities[j] = 0; - joystick->deadzones[j]=20; + joystick->sensitivities[j] = 0; + joystick->deadzones[j]=20; } } else { QMessageBox::critical(this, tr("Error"), tr("Cannot open joystick.")); - this->close(); } } -void joystickDialog::onjoystickAxisValueChanged(int axis, int value) { - if (value > jscal[axis][2]) { - jscal[axis][2] = value; - } - if (value < jscal[axis][0]) { - jscal[axis][0] = value; - } - QSlider * sl[]= {ui->Ch_1, ui->Ch_2, ui->Ch_3, ui->Ch_4, ui->Ch_5, ui->Ch_6, ui->Ch_7, ui->Ch_8}; - QCheckBox * ib[]={ui->ChInv_1, ui->ChInv_2, ui->ChInv_3, ui->ChInv_4, ui->ChInv_5, ui->ChInv_6, ui->ChInv_7, ui->ChInv_8}; - if (ib[axis]->isChecked()) { - sl[axis]->setInvertedAppearance(true); - } else { - sl[axis]->setInvertedAppearance(false); - } - sl[axis]->setMinimum(jscal[axis][0]); - sl[axis]->setMaximum(jscal[axis][2]); - sl[axis]->setValue(value); +void joystickDialog::joystickSetEnabled(bool enable) +{ + ui->joystickCB->setEnabled(enable); + ui->calibrationWidget->setEnabled(enable); + ui->nextButton->setEnabled(enable && step < 4); + ui->backButton->setEnabled(enable && step); + if (enable) + joystickOpen(ui->joystickCB->currentIndex()); } -void joystickDialog::on_nextButton_clicked() { - int numAxes=std::min(joystick->numAxes,8); +void joystickDialog::onjoystickAxisValueChanged(int axis, int value) +{ + if (axis >= MAX_JOYSTICKS) + return; + + if (started) { + if (value > jscal[axis][2]) + jscal[axis][2] = value; + else if (value < jscal[axis][0]) + jscal[axis][0] = value; + + sliders[axis]->setMinimum(jscal[axis][0]); + sliders[axis]->setMaximum(jscal[axis][2]); + } + sliders[axis]->setInvertedAppearance(invert[axis]->isChecked()); + sliders[axis]->setValue(value); +} + +void joystickDialog::loadStep() +{ switch (step) { case 0: - ui->howtoLabel->setText(tr("Place sticks and pots in middle position.\nPress next when done")); - step++; + ui->howtoLabel->setText(tr("Press the Start button to start the stick range calibration procedure.\n" \ + "You may change the channel assignments or inversion at any time.")); + ui->backButton->setDisabled(true); break; case 1: - for (int i=0; igetAxisValue(i); - } - jscal[i][1]/=100; - } - ui->howtoLabel->setText(tr("Map joystick channels to controls using comboboxes.\nPress next when done")); - step++; - foreach(QComboBox *cb, findChildren(QRegExp("jsmapCB_[0-9]+"))) { - cb->setEnabled(true); - } + started = true; + ui->howtoLabel->setText(tr("Move sticks and pots in every direction making full movement\nPress Next when finished")); + ui->nextButton->setText(tr("Next")); + ui->backButton->setDisabled(true); break; case 2: - ui->howtoLabel->setText(tr("Check inversion checkbox to get maximum at top-right position.\nPress next when done")); - step++; - foreach(QCheckBox *ib, findChildren(QRegExp("ChInv_[0-9]+"))) { - ib->setEnabled(true); - } + ui->howtoLabel->setText(tr("Place sticks and pots in middle position.\nPress Next when done")); + ui->backButton->setEnabled(true); + ui->nextButton->setEnabled(true); break; case 3: - ui->howtoLabel->setText(tr("Press ok to save configuration\nPress cancel to abort joystick calibration")); - ui->okButton->setEnabled(true); + for (int i=0; i < numAxes; i++) { + jscal[i][1]=0; + for (int j=0; j < 100; j++) { + jscal[i][1] += joystick->getAxisValue(i); + } + jscal[i][1] /= 100; + } + ui->howtoLabel->setText(tr("Map joystick channels to controls using comboboxes.\nPress Next when done.")); + ui->nextButton->setEnabled(true); + break; + case 4: + ui->howtoLabel->setText(tr("Check inversion checkbox to if necessary to reverse direction.\nPress Next when done.")); + ui->nextButton->setEnabled(true); + break; + case 5: + ui->howtoLabel->setText(tr("Press OK to save configuration\nPress Cancel to abort joystick calibration without saving.")); + ui->nextButton->setDisabled(true); } } -void joystickDialog::on_cancelButton_clicked() { +void joystickDialog::on_backButton_clicked() +{ + if (step) { + --step; + loadStep(); + } +} + +void joystickDialog::on_nextButton_clicked() +{ + if (step < 5){ + ++step; + loadStep(); + } +} + +void joystickDialog::on_cancelButton_clicked() +{ joystick->close(); - this->close(); + this->reject(); } void joystickDialog::on_okButton_clicked() { - int stickAssignments[MAX_JOYSTICKS] = {0}; - foreach(QComboBox *cb, findChildren(QRegExp("jsmapCB_[0-9]+"))) { - int mappedValue = cb->currentIndex() - 1; - // qDebug() << "cb->currentIndex()" << mappedValue; - if (mappedValue >= 0 && mappedValue < MAX_JOYSTICKS) { - stickAssignments[mappedValue]++; - } + int stick; + + g.jsSupport(ui->joystickChkB->isChecked()); + g.jsCtrl(ui->joystickCB->currentIndex()); + + if (!g.jsSupport() || g.jsCtrl() < 0) { + this->accept(); + return; } - for (int i=0; i 1) { - QMessageBox::critical(this, tr("Error"), tr("Duplicated stick assignment")); + if (started && step < 4) { + int resp = QMessageBox::warning(this, tr("Warning"), tr("Calibration not complete, save anyway?"), + QDialogButtonBox::Ok | QMessageBox::Default, QDialogButtonBox::Cancel | QMessageBox::Escape, QMessageBox::NoButton); + if (resp == QDialogButtonBox::Cancel) + return; + } + + int stickAssignments[MAX_JOYSTICKS] = {0}; + + for (int i = 0; i < MAX_JOYSTICKS; ++i) { + stick = sticks[i]->currentIndex() - 1; + if (stick >= 0 && stick < MAX_JOYSTICKS && ++stickAssignments[stick] > 1) { + QMessageBox::critical(this, tr("Error"), tr("Duplicated stick assignment!")); return; } } joystick->close(); - for (int i=0; iChInv_1, ui->ChInv_2, ui->ChInv_3, ui->ChInv_4, ui->ChInv_5, ui->ChInv_6, ui->ChInv_7, ui->ChInv_8}; - foreach(QComboBox *cb, findChildren(QRegExp("jsmapCB_[0-9]+"))) { - int axe=cb->objectName().mid(cb->objectName().lastIndexOf("_")+1).toInt()-1; - int stick=cb->currentIndex() - 1; - qDebug() << "joystick mapping " << cb->objectName() <<"axe:" << axe << "stick:" << stick; + + for (int i = 0; i < MAX_JOYSTICKS; ++i) { + stick = sticks[i]->currentIndex() - 1; + qDebug() << "joystick mapping " << sticks[i]->objectName() << "axe:" << i << "stick:" << stick; if (stick >= 0 && stick < MAX_JOYSTICKS) { - g.joystick[stick].stick_axe( axe ); - g.joystick[stick].stick_max( jscal[axe][2] ); - g.joystick[stick].stick_med( jscal[axe][1] ); - g.joystick[stick].stick_min( jscal[axe][0] ); - g.joystick[stick].stick_inv( ib[axe]->isChecked() ? 1 : 0 ); + g.joystick[stick].stick_axe( i ); + g.joystick[stick].stick_max( jscal[i][2] ); + g.joystick[stick].stick_med( jscal[i][1] ); + g.joystick[stick].stick_min( jscal[i][0] ); + g.joystick[stick].stick_inv( invert[i]->isChecked() ); } } - this->close(); + this->accept(); } diff --git a/companion/src/simulation/joystickdialog.h b/companion/src/simulation/joystickdialog.h index eedb2ba54..8c6fb5555 100644 --- a/companion/src/simulation/joystickdialog.h +++ b/companion/src/simulation/joystickdialog.h @@ -22,8 +22,13 @@ #define _JOYSTICKDIALOG_H_ #include +#include "appdata.h" #include "joystick.h" +class QCheckBox; +class QComboBox; +class QSlider; + namespace Ui { class joystickDialog; } @@ -32,25 +37,32 @@ class joystickDialog : public QDialog { Q_OBJECT -public: + public: explicit joystickDialog(QWidget *parent = 0, int stick=-1); ~joystickDialog(); Joystick *joystick; -public slots: - void onjoystickAxisValueChanged(int axis, int value); - -private: + private: Ui::joystickDialog *ui; - void joystickOpen(int stick); - int jscal[8][3]; + int jscal[MAX_JOYSTICKS][3]; + QCheckBox * invert[MAX_JOYSTICKS]; + QComboBox * sticks[MAX_JOYSTICKS]; + QSlider * sliders[MAX_JOYSTICKS]; int step; - -private slots: + int numAxes; + bool started; + + private slots: + void loadJoysticks(int stick = -1); + void joystickOpen(int stick); + void joystickSetEnabled(bool enable); + void onjoystickAxisValueChanged(int axis, int value); + void loadStep(); + void on_backButton_clicked(); void on_nextButton_clicked(); void on_cancelButton_clicked(); void on_okButton_clicked(); - + }; #endif // _JOYSTICKDIALOG_H_ diff --git a/companion/src/simulation/joystickdialog.ui b/companion/src/simulation/joystickdialog.ui index 2d18fdb0a..d52c6ceb5 100644 --- a/companion/src/simulation/joystickdialog.ui +++ b/companion/src/simulation/joystickdialog.ui @@ -6,8 +6,8 @@ 0 0 - 418 - 301 + 473 + 335 @@ -20,867 +20,7 @@ Configure Joystick - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - Ch2 - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - Ch1 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - Ch4 - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - Ch6 - - - - - - - Ch3 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - Ch5 - - - - - - - Ch7 - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - Ch8 - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - 0 - 0 - - - - - Not Assigned - - - - - Right Vertical - - - - - Right Horizontal - - - - - Left Vertical - - - - - Left Horizontal - - - - - P1 - - - - - P2 - - - - - P3/LS - - - - - RS - - - - - - - - -32767 - - - 32676 - - - Qt::Horizontal - - - false - - - false - - - QSlider::NoTicks - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 400 - 40 - - - - false - - - background:rgb(255, 255, 0); - - - QFrame::Box - - - Instructions - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - Cancel - - - - - - - Qt::Horizontal - - - - 198 - 20 - - - - - - - - - 0 - 0 - - - - Next - - - - - - - - 0 - 0 - - - - Ok - - - - - - - - + Qt::Horizontal @@ -893,7 +33,974 @@ - + + + + false + + + + 6 + + + + + + + Ch2 + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 2 + + + + + + + + 0 + 0 + + + + 1 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + Ch1 + + + + + + + + 0 + 0 + + + + 2 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 1 + + + + + + + + 0 + 0 + + + + 0 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + Ch4 + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 4 + + + + + + + Ch6 + + + + + + + Ch3 + + + + + + + + 0 + 0 + + + + 4 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 3 + + + + + + + + 0 + 0 + + + + 3 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 5 + + + + + + + + 0 + 0 + + + + 5 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + Ch5 + + + + + + + Ch7 + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 6 + + + + + + + + 0 + 0 + + + + 6 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + Ch8 + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 7 + + + + + + + + 0 + 0 + + + + 7 + + + + Not Assigned + + + + + Right Vertical + + + + + Right Horizontal + + + + + Left Vertical + + + + + Left Horizontal + + + + + P1 + + + + + P2 + + + + + P3/LS + + + + + RS + + + + + + + + -32767 + + + 32676 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + 0 + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 1 + + + + + + + + + + 2 + + + + + + + + + + 3 + + + + + + + + + + 4 + + + + + + + + + + 5 + + + + + + + + + + 6 + + + + + + + + + + 7 + + + + + + + + + + 0 + 0 + + + + + 400 + 40 + + + + false + + + background:rgb(255, 255, 0); + + + QFrame::Box + + + Instructions + + + Qt::AlignCenter + + + + + + + + + + + + + 0 + 0 + + + + + + + + Enable + + + + + + + + + + + + 0 + 0 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 198 + 20 + + + + + + + + false + + + + 0 + 0 + + + + Back + + + + + + + false + + + + 0 + 0 + + + + Start + + + + + + + + 0 + 0 + + + + Ok + + + + + + Qt::Vertical @@ -906,10 +1013,21 @@ + + + + Qt::Horizontal + + + + 0 + 20 + + + + - - - + diff --git a/companion/src/simulation/simulateduiwidget.h b/companion/src/simulation/simulateduiwidget.h index f9f267ca8..243784709 100644 --- a/companion/src/simulation/simulateduiwidget.h +++ b/companion/src/simulation/simulateduiwidget.h @@ -31,6 +31,8 @@ class SimulatorInterface; class LcdWidget; 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). diff --git a/companion/src/simulation/simulator.h b/companion/src/simulation/simulator.h new file mode 100644 index 000000000..62abba57a --- /dev/null +++ b/companion/src/simulation/simulator.h @@ -0,0 +1,89 @@ +/* + * 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 SIMULATOR_H +#define SIMULATOR_H + +#include +#include +#include +#include +#include + +#define SIMULATOR_OPTIONS_VERSION 1 + +namespace Simulator { + +typedef QPair keymapHelp_t; + +} + +struct SimulatorOptions +{ + enum StartupDataTypes { + START_WITH_FILE = 0, + START_WITH_FOLDER, + START_WITH_SDPATH, + START_WITH_RAWDATA + }; + + private: + quint16 _version = SIMULATOR_OPTIONS_VERSION; // structure definition version + + public: + // v1 fields + quint8 startupDataType = START_WITH_FILE; + QString firmwareId; + QString dataFile; + QString dataFolder; + QString sdPath; + QByteArray windowGeometry; + QList controlsState; // saved switch/pot/stick settings + QColor lcdColor; + + friend QDataStream & operator << (QDataStream &out, const SimulatorOptions & o) + { + out << o._version << o.startupDataType << o.firmwareId << o.dataFile << o.dataFolder + << o.sdPath << o.windowGeometry << o.controlsState << o.lcdColor; + return out; + } + + friend QDataStream & operator >> (QDataStream &in, SimulatorOptions & o) + { + if (o._version <= SIMULATOR_OPTIONS_VERSION) { + in >> o._version >> o.startupDataType >> o.firmwareId >> o.dataFile >> o.dataFolder + >> o.sdPath >> o.windowGeometry >> o.controlsState >> o.lcdColor; + } + return in; + } + + friend QDebug operator << (QDebug d, const SimulatorOptions & o) + { + QDebugStateSaver saver(d); + d.nospace() << "SimulatorOptions: firmwareId=" << o.firmwareId << "; dataFile=" << o.dataFile << "; dataFolder=" << o.dataFolder + << "; sdPath=" << o.sdPath << "; startupDataType=" << o.startupDataType; + return d; + } +}; + +Q_DECLARE_METATYPE(SimulatorOptions) + + +#endif // SIMULATOR_H diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index 0ea74a6fe..be04db15d 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -30,12 +30,13 @@ #include "sdcard.h" #include "simulateduiwidget.h" #include "simulatorinterface.h" -//#include "storage.h" +#include "storage.h" #include "telemetrysimu.h" #include "trainersimu.h" #include "virtualjoystickwidget.h" #ifdef JOYSTICKS #include "joystick.h" +#include "joystickdialog.h" #endif #include @@ -71,7 +72,9 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator lastPhase(-1), buttonPressed(0), trimPressed(TRIM_NONE), - eepromDataFromFile(false), + startupFromFile(false), + deleteTempRadioData(false), + saveTempRadioData(false), middleButtonPressed(false) { setWindowFlags(Qt::Window); @@ -80,6 +83,10 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator traceCallbackInstance = this; simulator->installTraceHook(traceCb); + // defaults + setRadioProfileId(radioProfileId); + setSdPath(g.profile[radioProfileId].sdPath()); + setupUi(); } @@ -98,8 +105,13 @@ SimulatorDialog::~SimulatorDialog() void SimulatorDialog::setRadioProfileId(int value) { radioProfileId = value; - simulator->setVolumeGain(g.profile[radioProfileId].volumeGain()); - setPaths(g.profile[radioProfileId].sdPath(), radioDataPath); + if (simulator) + simulator->setVolumeGain(g.profile[radioProfileId].volumeGain()); +} + +void SimulatorDialog::setSdPath(const QString & sdPath) +{ + setPaths(sdPath, radioDataPath); } void SimulatorDialog::setDataPath(const QString & dataPath) @@ -111,7 +123,8 @@ void SimulatorDialog::setPaths(const QString & sdPath, const QString & dataPath) { sdCardPath = sdPath; radioDataPath = dataPath; - simulator->setSdPath(sdPath, dataPath); + if (simulator) + simulator->setSdPath(sdPath, dataPath); } void SimulatorDialog::setRadioSettings(const GeneralSettings settings) @@ -119,35 +132,66 @@ void SimulatorDialog::setRadioSettings(const GeneralSettings settings) radioSettings = settings; } -void SimulatorDialog::setEepromData(const QByteArray & eeprom, bool fromFile) +/* + * This function can accept no parameters, a file name (QString is a QBA), or a data array. It will attempt to load radio settings data from one of + * several sources into a RadioData object, parse the data, and then pass it on as appropriate to the SimulatorInterface in start(). + * If given no/blank , and setDataPath() was already called, then it will check that directory for "Horus-style" data files. + * If given a file name, set the parameter to 'true'. This will attempt to load radio settings from said file + * and later start the simulator interface in start() using the same data. + * If is a byte array of data, attempts to load radio settings from there and will also start the simulator interface + * with the same data when start() is called. + * If you already have a valid RadioData structure, call setRadioData() instead. + */ +void SimulatorDialog::setStartupData(const QByteArray & dataSource, bool fromFile) { RadioData simuData; quint16 ret = 1; QString error; - if (eeprom.isEmpty() && !radioDataPath.isEmpty()) { - // FIXME : need Storage classes to return error code, not just a message. + // If is blank but we have a data path, use that for individual radio/model files. + if (dataSource.isEmpty() && !radioDataPath.isEmpty()) { + // If directory structure already exists, try to load data from there. + // FIXME : need Storage class to return formal error code, not just a boolean, because it would be better + // to avoid hard-coding paths like "RADIO" here. E.g. did it fail due to no data at all, or corrupt data, or...? if (QDir(QString(radioDataPath).append("/RADIO")).exists()) { SdcardFormat sdcard(radioDataPath); - ret = sdcard.load(simuData); - if (!ret) + if (!(ret = sdcard.load(simuData))) error = sdcard.error(); } } + // Supposedly we're being given a file name to use, try that out. else if (fromFile) { - // Storage store = Storage(QString(eeprom)); - // if (!(ret = store.load(simuData))) { - // error = store.error(); - // } - QFile file; - QByteArray ba; - file.setFileName(QString(eeprom)); - if (file.exists() && file.open(QIODevice::ReadOnly) && !(ba = file.readAll()).isEmpty()) { - ret = firmware->getEEpromInterface()->load(simuData, (uint8_t *)ba.constData(), getEEpromSize(m_board)); + Storage store = Storage(QString(dataSource)); + if ((ret = store.load(simuData))) { + if (IS_HORUS(m_board)) { + // save the data to a temp folder + if (!(ret = useTempDataPath(true, true))) // save data back to file on simulator close + error = tr("Error: Could not save data to temporary directory in '%1'").arg(QDir::tempPath()); + else + ret = saveRadioData(&simuData, radioDataPath, &error); + } + else if (QString(dataSource).endsWith(".otx", Qt::CaseInsensitive)) { + // FIXME : Right now there's no way to read data back into the .otx file after simulation finishes. + setRadioData(&simuData); + return; + } + else { + startupData = dataSource; // save the file name for start() + } + } + else { + error = store.error(); } } - else if (!eeprom.isEmpty()) { - ret = firmware->getEEpromInterface()->load(simuData, (uint8_t *)eeprom.constData(), getEEpromSize(m_board)); + // Assume a byte array of radio data was passed, load it. + else if (!dataSource.isEmpty()) { + ret = firmware->getEEpromInterface()->load(simuData, (uint8_t *)dataSource.constData(), getEEpromSize(m_board)); + startupData = dataSource; // save the data for start() + } + // we're :-( + else { + ret = 0; + error = tr("Could not determine startup data source."); } if (!ret) { @@ -158,8 +202,7 @@ void SimulatorDialog::setEepromData(const QByteArray & eeprom, bool fromFile) } radioSettings = simuData.generalSettings; - eepromData = eeprom; - eepromDataFromFile = fromFile; + startupFromFile = fromFile; } void SimulatorDialog::setRadioData(RadioData * radioData) @@ -167,15 +210,108 @@ void SimulatorDialog::setRadioData(RadioData * radioData) if (radioDataPath.isEmpty()) { QByteArray eeprom(getEEpromSize(m_board), 0); if (firmware->getEEpromInterface()->save((uint8_t *)eeprom.data(), *radioData, 0, firmware->getCapability(SimulatorVariant)) > 0) - setEepromData(eeprom, false); + setStartupData(eeprom, false); } else { - SdcardFormat sdcard(radioDataPath); - sdcard.write(*radioData); + saveRadioData(radioData, radioDataPath); radioSettings = radioData->generalSettings; } } +void SimulatorDialog::setOptions(SimulatorOptions & options, bool withSave) +{ + setSdPath(options.sdPath); + if (options.startupDataType == SimulatorOptions::START_WITH_FOLDER && !options.dataFolder.isEmpty()) { + setDataPath(options.dataFolder); + setStartupData(); + } + else if (options.startupDataType == SimulatorOptions::START_WITH_SDPATH && !options.sdPath.isEmpty()) { + setDataPath(options.sdPath); + setStartupData(); + } + else if (options.startupDataType == SimulatorOptions::START_WITH_FILE && !options.dataFile.isEmpty()) { + setStartupData(options.dataFile.toLocal8Bit(), true); + } + + if (withSave) + g.profile[radioProfileId].simulatorOptions(options); +} + +bool SimulatorDialog::saveRadioData(RadioData * radioData, const QString & path, QString * error) +{ + QString dir = path; + if (dir.isEmpty()) + dir = radioDataPath; + if (radioData && !dir.isEmpty()) { + SdcardFormat sdcard(dir); + bool ret = sdcard.write(*radioData); + if (!ret && error) + *error = sdcard.error(); + return ret; + } + return false; +} + +bool SimulatorDialog::useTempDataPath(bool deleteOnClose, bool saveOnClose) +{ + if (deleteTempRadioData) + deleteTempData(); + + QTemporaryDir tmpDir(QDir::tempPath() + "/otx-XXXXXX"); + if (tmpDir.isValid()) { + setDataPath(tmpDir.path()); + tmpDir.setAutoRemove(false); + deleteTempRadioData = deleteOnClose; + saveTempRadioData = saveOnClose; + qDebug() << __FILE__ << __LINE__ << "Created temporary settings directory" << radioDataPath + << "with delteOnClose:" << deleteOnClose << "with saveOnClose:" << saveOnClose; + return true; + } + else { + qDebug() << __FILE__ << __LINE__ << "ERROR : Failed to create temporary settings directory" << radioDataPath; + return false; + } +} + +// This will save radio data from temporary folder structure back into an .otx file, eg. for Horus. +bool SimulatorDialog::saveTempData() +{ + bool ret = false; + QString error; + QString file = g.profile[radioProfileId].simulatorOptions().dataFile; + + if (!saveTempRadioData || radioDataPath.isEmpty() || file.isEmpty()) + return ret; + + RadioData radioData; + SdcardFormat sdcard(radioDataPath); + if (!(ret = sdcard.load(radioData))) { + error = sdcard.error(); + } + else { + Storage store(file); + if (!(ret = store.write(radioData))) + error = store.error(); + else + qDebug() << __FILE__ << __LINE__ << "Saved radio data to file" << file; + } + + if (!ret) + QMessageBox::critical(this, tr("Data Save Error"), error); + + return ret; +} + +void SimulatorDialog::deleteTempData() +{ + if (!radioDataPath.isEmpty()) { + QDir tpath(radioDataPath); + qDebug() << __FILE__ << __LINE__ << "Deleting temporary settings directory" << tpath.absolutePath(); + tpath.removeRecursively(); + tpath.rmdir(radioDataPath); // for some reason this is necessary to remove the base folder + } +} + void SimulatorDialog::setUiAreaStyle(const QString & style) { ui->radioUiTab->setStyleSheet(style); @@ -192,6 +328,31 @@ void SimulatorDialog::traceCallback(const char * text) traceMutex.unlock(); } +/* + * Startup + */ + +void SimulatorDialog::start() +{ + setupRadioWidgets(); + setupJoysticks(); + setupOutputsDisplay(); + setupGVarsDisplay(); + restoreRadioWidgetsState(); + setTrims(); + + 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); + + getValues(); + setupTimer(); + startupData.clear(); // this is safe because simulator->start() makes copy of data/discards the file name +} + /* * Setup */ @@ -220,7 +381,6 @@ void SimulatorDialog::setupUi() break; } - // support for getKeymapHelp()) { keymapHelp.append(item); } @@ -235,47 +395,6 @@ void SimulatorDialog::setupUi() vJoyRight = new VirtualJoystickWidget(this, 'R'); ui->rightStickLayout->addWidget(vJoyRight); -#ifdef JOYSTICKS - if (g.jsSupport()) { - int count=0; - for (int j=0; j<8; j++){ - int axe = g.joystick[j].stick_axe(); - if (axe>=0 && axe<8) { - jsmap[axe]=j+1; - jscal[axe][0] = g.joystick[j].stick_min(); - jscal[axe][1] = g.joystick[j].stick_med(); - jscal[axe][2] = g.joystick[j].stick_max(); - jscal[axe][3] = g.joystick[j].stick_inv(); - count++; - } - } - if (count<3) { - QMessageBox::critical(this, tr("Warning"), tr("Joystick enabled but not configured correctly")); - } - if (g.jsCtrl()!=-1) { - joystick = new Joystick(this); - if (joystick) { - if (joystick->open(g.jsCtrl())) { - int numAxes=std::min(joystick->numAxes,8); - for (int j=0; jsensitivities[j] = 0; - joystick->deadzones[j]=0; - } - //mode 1,3 -> THR on right - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); - connect(joystick, SIGNAL(axisValueChanged(int, int)), this, SLOT(onjoystickAxisValueChanged(int, int))); - } - else { - QMessageBox::critical(this, tr("Warning"), tr("Cannot open joystick, joystick disabled")); - } - } - } - } -#endif - ui->tabWidget->setCurrentIndex(flags & SIMULATOR_FLAGS_NOTX); connect(vJoyLeft, SIGNAL(trimButtonPressed(int)), this, SLOT(onTrimPressed(int))); @@ -287,30 +406,43 @@ void SimulatorDialog::setupUi() connect(vJoyRight, SIGNAL(trimSliderMoved(int,int)), this, SLOT(onTrimSliderMoved(int,int))); connect(ui->btn_help, SIGNAL(released()), this, SLOT(showHelp())); + connect(ui->btn_joystickDialog, SIGNAL(released()), this, SLOT(openJoystickDialog())); connect(ui->btn_telemSim, SIGNAL(released()), this, SLOT(openTelemetrySimulator())); connect(ui->btn_trainerSim, SIGNAL(released()), this, SLOT(openTrainerSimulator())); connect(ui->btn_debugConsole, SIGNAL(released()), this, SLOT(openDebugOutput())); connect(ui->btn_luaReload, SIGNAL(released()), this, SLOT(luaReload())); connect(ui->btn_screenshot, SIGNAL(released()), radioUiWidget, SLOT(captureScreenshot())); + // Hide some main UI buttons based on board capabilities, and add keymap help texts. + keymapHelp.append(keymapHelp_t(ui->btn_help->shortcut().toString(QKeySequence::NativeText), ui->btn_help->statusTip())); - if (!firmware->getCapability(Capability(LuaInputsPerScript))) // hackish! but using "LuaScripts" checks for id "lua" in fw. - keymapHelp.append(keymapHelp_t(ui->btn_telemSim->shortcut().toString(QKeySequence::NativeText), ui->btn_telemSim->statusTip())); - else - ui->btn_luaReload->hide(); - keymapHelp.append(keymapHelp_t(ui->btn_trainerSim->shortcut().toString(QKeySequence::NativeText), ui->btn_trainerSim->statusTip())); - keymapHelp.append(keymapHelp_t(ui->btn_debugConsole->shortcut().toString(QKeySequence::NativeText), ui->btn_debugConsole->statusTip())); + +#ifdef JOYSTICKS + keymapHelp.append(keymapHelp_t(ui->btn_joystickDialog->shortcut().toString(QKeySequence::NativeText), ui->btn_joystickDialog->statusTip())); +#else + ui->btn_joystickDialog->hide(); +#endif + if (firmware->getCapability(Capability(SportTelemetry))) - keymapHelp.append(keymapHelp_t(ui->btn_luaReload->shortcut().toString(QKeySequence::NativeText), ui->btn_luaReload->statusTip())); + keymapHelp.append(keymapHelp_t(ui->btn_telemSim->shortcut().toString(QKeySequence::NativeText), ui->btn_luaReload->statusTip())); else ui->btn_telemSim->hide(); + + keymapHelp.append(keymapHelp_t(ui->btn_trainerSim->shortcut().toString(QKeySequence::NativeText), ui->btn_trainerSim->statusTip())); + keymapHelp.append(keymapHelp_t(ui->btn_debugConsole->shortcut().toString(QKeySequence::NativeText), ui->btn_debugConsole->statusTip())); + + if (!firmware->getCapability(Capability(LuaInputsPerScript))) // hackish! but using "LuaScripts" checks for id "lua" in fw. + keymapHelp.append(keymapHelp_t(ui->btn_luaReload->shortcut().toString(QKeySequence::NativeText), ui->btn_telemSim->statusTip())); + else + ui->btn_luaReload->hide(); + keymapHelp.append(keymapHelp_t(ui->btn_screenshot->shortcut().toString(QKeySequence::NativeText), ui->btn_screenshot->statusTip())); } void SimulatorDialog::setupRadioWidgets() { - int i, midpos, aIdx; + int i, midpos, aIdx, wval; QString wname; Board::Type board = firmware->getBoard(); @@ -335,16 +467,20 @@ void SimulatorDialog::setupRadioWidgets() // switches Board::SwitchInfo switchInfo; + Board::SwitchType swcfg; // FIXME : CPN_MAX_SWITCHES == 32 but GeneralSettings::switchConfig[18] !! for (i = 0; i < firmware->getCapability(Capability(Switches)) && i < 18 /*CPN_MAX_SWITCHES*/; ++i) { if (radioSettings.switchConfig[i] == Board::SWITCH_NOT_AVAILABLE) continue; + swcfg = Board::SwitchType(radioSettings.switchConfig[i]); + wval = (swcfg == Board::SWITCH_3POS ? -1 : 0); + if ((wname = QString(radioSettings.switchName[i])).isEmpty()) { switchInfo = getSwitchInfo(board, i); wname = QString(switchInfo.name); } - RadioSwitchWidget * sw = new RadioSwitchWidget(Board::SwitchType(radioSettings.switchConfig[i]), wname, 0, ui->radioWidgetsHT); + RadioSwitchWidget * sw = new RadioSwitchWidget(swcfg, wname, wval, ui->radioWidgetsHT); sw->setIndex(i); ui->radioWidgetsHTLayout->addWidget(sw); switches.append(sw); @@ -532,6 +668,56 @@ QFrame * SimulatorDialog::createLogicalSwitch(QWidget * parent, int switchNo, QV return swtch; } +void SimulatorDialog::setupJoysticks() +{ +#ifdef JOYSTICKS + static bool joysticksEnabled = false; + if (g.jsSupport() && g.jsCtrl() > -1) { + int count=0, axe; + for (int j=0; j < MAX_JOYSTICKS; j++){ + axe = g.joystick[j].stick_axe(); + if (axe >= 0 && axe < MAX_JOYSTICKS) { + jsmap[axe] = j + 1; + jscal[axe][0] = g.joystick[j].stick_min(); + jscal[axe][1] = g.joystick[j].stick_med(); + jscal[axe][2] = g.joystick[j].stick_max(); + jscal[axe][3] = g.joystick[j].stick_inv(); + count++; + } + } + if (count<3) { + QMessageBox::critical(this, tr("Warning"), tr("Joystick enabled but not configured correctly")); + return; + } + joystick = new Joystick(this); + if (joystick && joystick->open(g.jsCtrl())) { + int numAxes = std::min(joystick->numAxes, MAX_JOYSTICKS); + for (int j=0; jsensitivities[j] = 0; + joystick->deadzones[j] = 0; + } + //mode 1,3 -> THR on right + vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); + vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); + vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); + vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); + connect(joystick, SIGNAL(axisValueChanged(int, int)), this, SLOT(onjoystickAxisValueChanged(int, int))); + joysticksEnabled = true; + } + else { + QMessageBox::critical(this, tr("Warning"), tr("Cannot open joystick, joystick disabled")); + } + } + else if (joysticksEnabled && joystick) { + disconnect(joystick, 0, this, 0); + vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, false); + vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, false); + vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, false); + vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, false); + } +#endif +} + void SimulatorDialog::setupTimer() { timer = new QTimer(this); @@ -539,26 +725,44 @@ void SimulatorDialog::setupTimer() timer->start(10); } -/* - * Startup - */ - -void SimulatorDialog::start() +void SimulatorDialog::restoreRadioWidgetsState() { - setupRadioWidgets(); - setupOutputsDisplay(); - setupGVarsDisplay(); - setTrims(); + RadioWidget::RadioWidgetState state; + QMap switchesMap; + QMap analogsMap; + QList states = g.profile[radioProfileId].simulatorOptions().controlsState; - if (eepromData.isEmpty()) - simulator->start((const char *)0); - else if (eepromDataFromFile) - simulator->start(eepromData.constData()); - else - simulator->start(eepromData, (flags & SIMULATOR_FLAGS_NOTX) ? false : true); + 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); + } - setupTimer(); - eepromData.clear(); // this is safe because simulator->start() makes copy of data/discards the file name + 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())); + } +} + +QList SimulatorDialog::saveRadioWidgetsState() +{ + 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; } /* @@ -687,14 +891,21 @@ void SimulatorDialog::closeEvent(QCloseEvent *) { simulator->stop(); timer->stop(); - g.profile[radioProfileId].simuWinGeo(saveGeometry()); + SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions(); + opts.windowGeometry = saveGeometry(); + opts.controlsState = saveRadioWidgetsState(); + g.profile[radioProfileId].simulatorOptions(opts); + if (saveTempRadioData) + saveTempData(); + if (deleteTempRadioData) + deleteTempData(); } void SimulatorDialog::showEvent(QShowEvent *) { static bool firstShow = true; if (firstShow) { - restoreGeometry(g.profile[radioProfileId].simuWinGeo()); + restoreGeometry(g.profile[radioProfileId].simulatorOptions().windowGeometry); // The stick position needs to be set after the final show event, otherwise resizes during dialog creation will screw it up. if (radioSettings.stickMode & 1) { @@ -803,6 +1014,16 @@ void SimulatorDialog::openTrainerSimulator() } } +void SimulatorDialog::openJoystickDialog() +{ +#ifdef JOYSTICKS + joystickDialog * jd = new joystickDialog(this); + if (jd->exec() == QDialog::Accepted) + setupJoysticks(); + jd->deleteLater(); +#endif +} + void SimulatorDialog::openDebugOutput() { // allow only one instance, but install signal handler to catch dialog destruction just in case diff --git a/companion/src/simulation/simulatordialog.h b/companion/src/simulation/simulatordialog.h index af0ecc786..0d4bda80a 100644 --- a/companion/src/simulation/simulatordialog.h +++ b/companion/src/simulation/simulatordialog.h @@ -24,6 +24,7 @@ #include "constants.h" #include "helpers.h" #include "radiodata.h" +#include "simulator.h" #include #include @@ -39,8 +40,6 @@ #define CBEEP_ON "QLabel { background-color: #FF364E }" #define CBEEP_OFF "QLabel { }" -typedef QPair keymapHelp_t; - void traceCb(const char * text); class Firmware; @@ -66,6 +65,8 @@ namespace Ui { class SimulatorDialog; } +using namespace Simulator; + class SimulatorDialog : public QDialog { Q_OBJECT @@ -75,22 +76,34 @@ class SimulatorDialog : public QDialog virtual ~SimulatorDialog(); void setRadioProfileId(int value); + void setSdPath(const QString & sdPath); void setDataPath(const QString & dataPath); void setPaths(const QString & sdPath, const QString & dataPath); void setRadioSettings(const GeneralSettings settings); - void setEepromData(const QByteArray & eeprom = NULL, bool fromFile = false); + void setStartupData(const QByteArray & dataSource = NULL, bool fromFile = false); void setRadioData(RadioData * radioData); + void setOptions(SimulatorOptions & options, bool withSave = true); + bool saveRadioData(RadioData * radioData, const QString & path = "", QString * error = NULL); + bool useTempDataPath(bool deleteOnClose = true, bool saveOnClose = false); + bool saveTempData(); + void deleteTempData(); void setUiAreaStyle(const QString & style); void traceCallback(const char * text); void start(); + QString getSdPath() const { return sdCardPath; } + QString getDataPath() const { return radioDataPath; } + private: void setupUi(); void setupRadioWidgets(); void setupOutputsDisplay(); void setupGVarsDisplay(); + void setupJoysticks(); QFrame * createLogicalSwitch(QWidget * parent, int switchNo, QVector & labels); void setupTimer(); + void restoreRadioWidgetsState(); + QList saveRadioWidgetsState(); void setValues(); void getValues(); @@ -125,14 +138,16 @@ class SimulatorDialog : public QDialog QString sdCardPath; QString radioDataPath; - QByteArray eepromData; + QByteArray startupData; Board::Type m_board; quint8 flags; int radioProfileId; int lastPhase; int buttonPressed; int trimPressed; - bool eepromDataFromFile; + bool startupFromFile; + bool deleteTempRadioData; + bool saveTempRadioData; bool middleButtonPressed; #ifdef JOYSTICKS @@ -155,6 +170,7 @@ class SimulatorDialog : public QDialog void centerSticks(); void openTelemetrySimulator(); void openTrainerSimulator(); + void openJoystickDialog(); void openDebugOutput(); void updateDebugOutput(); void luaReload(); diff --git a/companion/src/simulation/simulatordialog.ui b/companion/src/simulation/simulatordialog.ui index bef8295b1..022cb5f48 100644 --- a/companion/src/simulation/simulatordialog.ui +++ b/companion/src/simulation/simulatordialog.ui @@ -246,13 +246,29 @@ Show Help/Keymap - Keymap + Keymap Help F1 + + + + Open the Joystick Settings window (F3) + + + Telemetry Simulator + + + Joystick Config. + + + F3 + + + @@ -262,7 +278,7 @@ Telemetry Simulator - Telemetry Simulator + Telemetry Sim. F4 @@ -278,7 +294,7 @@ Trainer Simulator - Trainer Simulator + Trainer Sim. F5 @@ -310,7 +326,7 @@ Reload Lua Scripts - Reload Lua Scripts + Lua Reload F7 diff --git a/companion/src/simulation/simulatorstartupdialog.cpp b/companion/src/simulation/simulatorstartupdialog.cpp new file mode 100644 index 000000000..98ad670c5 --- /dev/null +++ b/companion/src/simulation/simulatorstartupdialog.cpp @@ -0,0 +1,257 @@ +/* + * 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 "simulatorstartupdialog.h" +#include "ui_simulatorstartupdialog.h" + +#include "appdata.h" +#include "constants.h" +#include "eeprominterface.h" +#include "helpers.h" + +#include + +extern AppData g; +extern QMap registered_simulators; + +SimulatorStartupDialog::SimulatorStartupDialog(SimulatorOptions * options, int * profId, QWidget *parent) : + QDialog(parent), + ui(new Ui::SimulatorStartupDialog), + m_options(options), + m_profileId(profId) +{ + ui->setupUi(this); + + QMapIterator pi(g.getActiveProfiles()); + while (pi.hasNext()) { + pi.next(); + ui->radioProfile->addItem(pi.value(), pi.key()); + } + + ui->radioType->addItems(registered_simulators.keys()); + + ui->optGrp_dataSource->setId(ui->optFile, SimulatorOptions::START_WITH_FILE); + ui->optGrp_dataSource->setId(ui->optFolder, SimulatorOptions::START_WITH_FOLDER); + ui->optGrp_dataSource->setId(ui->optSdPath, SimulatorOptions::START_WITH_SDPATH); + + CompanionIcon icon("open.png"); + ui->btnSelectDataFile->setIcon(icon); + ui->btnSelectDataFolder->setIcon(icon); + ui->btnSelectSdPath->setIcon(icon); + + QObject::connect(ui->radioProfile, SIGNAL(currentIndexChanged(int)), this, SLOT(onRadioProfileChanged(int))); + QObject::connect(ui->radioType, SIGNAL(currentIndexChanged(int)), this, SLOT(onRadioTypeChanged(int))); + QObject::connect(ui->btnSelectDataFile, &QToolButton::clicked, this, &SimulatorStartupDialog::onDataFileSelect); + QObject::connect(ui->btnSelectDataFolder, &QToolButton::clicked, this, &SimulatorStartupDialog::onDataFolderSelect); + QObject::connect(ui->btnSelectSdPath, &QToolButton::clicked, this, &SimulatorStartupDialog::onSdPathSelect); + + loadRadioProfile(*m_profileId); +} + +SimulatorStartupDialog::~SimulatorStartupDialog() +{ + delete ui; +} + +void SimulatorStartupDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +// FIXME : need a better way to check for this +bool SimulatorStartupDialog::usesCategorizedStorage(const QString & name) +{ + return name.contains("horus", Qt::CaseInsensitive); +} + +bool SimulatorStartupDialog::usesCategorizedStorage() +{ + return usesCategorizedStorage(ui->radioType->currentText()); +} + +QString SimulatorStartupDialog::findRadioId(const QString & str) +{ + QString radioId(str); + int pos = str.indexOf("-"); + if (pos > 0) { + radioId = str.mid(pos+1); + pos = radioId.lastIndexOf("-"); + if (pos > 0) { + radioId = radioId.mid(0, pos); + } + } + return radioId; +} + +// TODO : this could be smarter and actually look for a matching file in the folder +QString SimulatorStartupDialog::radioEepromFileName(const QString & firmwareId, QString folder) +{ + QString eepromFileName = "", ext = "bin"; + + if (folder.isEmpty()) + folder = g.eepromDir(); + + QString radioId = findRadioId(firmwareId); + if (usesCategorizedStorage(radioId)) + ext = "otx"; + + eepromFileName = QString("eeprom-%1.%2").arg(radioId, ext); + eepromFileName = QDir(folder).filePath(eepromFileName.toLatin1()); + // qDebug() << "radioId" << radioId << "eepromFileName" << eepromFileName; + + return eepromFileName; +} + +void SimulatorStartupDialog::updateContainerTypes(void) +{ + static int oldstate = -1; + int state = usesCategorizedStorage(); + + if (state == oldstate) + return; + + oldstate = state; + + ui->wdgt_dataSource->setVisible(state); + ui->layout_options->labelForField(ui->wdgt_dataSource)->setVisible(state); + ui->wdgt_dataFolder->setVisible(state); + ui->layout_options->labelForField(ui->wdgt_dataFolder)->setVisible(state); + + if (!state || ui->optGrp_dataSource->checkedId() < 0) + ui->optFile->setChecked(true); + +} + +void SimulatorStartupDialog::loadRadioProfile(int id) +{ + QString tmpstr; + int i; + + if (id < 0 || !g.getActiveProfiles().contains(id)) + return; + + i = ui->radioProfile->findData(id); + if (i > -1 && ui->radioProfile->currentIndex() != i) { + ui->radioProfile->setCurrentIndex(i); + return; + } + + *m_options = g.profile[id].simulatorOptions(); + + tmpstr = m_options->firmwareId; + if (tmpstr.isEmpty() && !g.profile[id].fwType().isEmpty()) + tmpstr = g.profile[id].fwType(); + else if (getSimulatorFactory(tmpstr)) + tmpstr = getSimulatorFactory(tmpstr)->name(); + i = ui->radioType->findText(findRadioId(tmpstr), Qt::MatchFixedString | Qt::MatchContains); + if (i > -1) + ui->radioType->setCurrentIndex(i); + + tmpstr = m_options->dataFile; + if (!tmpstr.isEmpty()) + ui->dataFile->setText(tmpstr); + + tmpstr = m_options->dataFolder; + if (tmpstr.isEmpty()) + tmpstr = g.eepromDir(); + ui->dataFolder->setText(tmpstr); + + tmpstr = m_options->sdPath; + if (tmpstr.isEmpty()) + tmpstr = g.profile[id].sdPath(); + ui->sdPath->setText(tmpstr); + + foreach (QAbstractButton * btn, ui->optGrp_dataSource->buttons()) { + if (ui->optGrp_dataSource->id(btn) == m_options->startupDataType) { + btn->setChecked(true); + break; + } + }; + + updateContainerTypes(); +} + +void SimulatorStartupDialog::accept() +{ + *m_profileId = ui->radioProfile->currentData().toInt(); + m_options->firmwareId = ui->radioType->currentText(); + m_options->dataFile = ui->dataFile->text(); + m_options->dataFolder = ui->dataFolder->text(); + m_options->sdPath = ui->sdPath->text(); + m_options->startupDataType = ui->optGrp_dataSource->checkedId(); + + QDialog::accept(); +} + +void SimulatorStartupDialog::onRadioProfileChanged(int index) +{ + if (index < 0) + return; + + loadRadioProfile(ui->radioProfile->currentData().toInt()); +} + +void SimulatorStartupDialog::onRadioTypeChanged(int index) +{ + if (index < 0) + return; + ui->dataFile->setText(radioEepromFileName(ui->radioType->currentText())); + updateContainerTypes(); +} + +void SimulatorStartupDialog::onDataFileSelect(bool) +{ + QString filter = tr(EEPROM_FILES_FILTER) + tr("All files (*.*)"); + QString file = QFileDialog::getSaveFileName(this, QObject::tr("Select a data file"), ui->dataFile->text(), + filter, NULL, QFileDialog::DontConfirmOverwrite); + if (!file.isEmpty()) { + ui->dataFile->setText(file); + ui->optFile->setChecked(true); + } +} + +void SimulatorStartupDialog::onDataFolderSelect(bool) +{ + QString folder = QFileDialog::getExistingDirectory(this, QObject::tr("Select Data Directory"), + ui->dataFolder->text(), QFileDialog::DontUseNativeDialog); + if (!folder.isEmpty()) { + ui->dataFolder->setText(folder); + if (usesCategorizedStorage()) + ui->optFolder->setChecked(true); + } +} + +void SimulatorStartupDialog::onSdPathSelect(bool) +{ + QString folder = QFileDialog::getExistingDirectory(this, QObject::tr("Select SD Card Image Folder"), + ui->sdPath->text(), QFileDialog::DontUseNativeDialog); + if (!folder.isEmpty()) { + ui->sdPath->setText(folder); + if (usesCategorizedStorage()) + ui->optSdPath->setChecked(true); + } +} diff --git a/companion/src/simulation/simulatorstartupdialog.h b/companion/src/simulation/simulatorstartupdialog.h new file mode 100644 index 000000000..dc5abcbea --- /dev/null +++ b/companion/src/simulation/simulatorstartupdialog.h @@ -0,0 +1,69 @@ +/* + * 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 SIMULATORSTARTUPDIALOG_H +#define SIMULATORSTARTUPDIALOG_H + +#include "simulator.h" +#include + +namespace Ui { +class SimulatorStartupDialog; +} + +using namespace Simulator; + +class SimulatorStartupDialog : public QDialog +{ + Q_OBJECT + + public: + + explicit SimulatorStartupDialog(SimulatorOptions * options, int * profId, QWidget *parent = 0); + ~SimulatorStartupDialog(); + + static bool usesCategorizedStorage(const QString & name); + static QString findRadioId(const QString & str); + static QString radioEepromFileName(const QString & firmwareId, QString folder = ""); + + void updateContainerTypes(); + + public slots: + + virtual void accept(); + void loadRadioProfile(int id); + void onRadioProfileChanged(int index); + void onRadioTypeChanged(int index); + void onDataFileSelect(bool); + void onDataFolderSelect(bool); + void onSdPathSelect(bool); + + protected: + + bool usesCategorizedStorage(); + void changeEvent(QEvent *e); + + private: + Ui::SimulatorStartupDialog *ui; + SimulatorOptions * m_options; + int * m_profileId; +}; + +#endif // SIMULATORSTARTUPDIALOG_H diff --git a/companion/src/simulation/simulatorstartupdialog.ui b/companion/src/simulation/simulatorstartupdialog.ui new file mode 100644 index 000000000..0f342ebae --- /dev/null +++ b/companion/src/simulation/simulatorstartupdialog.ui @@ -0,0 +1,357 @@ + + + SimulatorStartupDialog + + + + 0 + 0 + 488 + 219 + + + + OpenTX Simulator - Startup Options + + + true + + + + 6 + + + + + Simulator Startup Options: + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5 + + + 4 + + + 9 + + + 7 + + + 9 + + + 5 + + + + + Radio Profile: + + + + + + + Existing radio profiles are shown here.<br /> +Create or edit profiles using the Companion application. + + + + + + + Radio Type: + + + + + + + Existing radio simulators are shown here.<br /> +The radio type specified in the selected profile is used by default. + + + + + + + Data Source: + + + + + + + Data File: + + + + + + + Data Folder: + + + + + + + SD Image Path: + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Radio data (.bin/.eeprom/.otx) image file to use. A new file with a default image will be created if necessary.<br /> +<b>NOTE</b>: any existing EEPROM data incompatible with the selected radio type may be overwritten! + + + + + + + Select data file... + + + ... + + + + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png + + + Qt::ToolButtonIconOnly + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Directory containing RADIO and MODELS folders to use.<br /> +New folder(s) with default radio/model will be created here if necessary. + + + + + + + Select data folder... + + + ... + + + + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png + + + Qt::ToolButtonIconOnly + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Directory containing the SD card image to use.<br/> +The default is configured in the chosen Radio Profile. + + + + + + + Select SD card image folder... + + + ... + + + + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png + + + Qt::ToolButtonIconOnly + + + + + + + + + + Select which of the data sources (File/Folder/SD Card) you would like to start the simulator with. + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + File + + + optGrp_dataSource + + + + + + + Folder + + + optGrp_dataSource + + + + + + + SD Path + + + optGrp_dataSource + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + groupBox + buttonBox + + + + + + + buttonBox + accepted() + SimulatorStartupDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SimulatorStartupDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + diff --git a/companion/src/simulation/widgets/radiofaderwidget.h b/companion/src/simulation/widgets/radiofaderwidget.h index 45537420f..fda81a7a4 100644 --- a/companion/src/simulation/widgets/radiofaderwidget.h +++ b/companion/src/simulation/widgets/radiofaderwidget.h @@ -30,12 +30,12 @@ class RadioFaderWidget : public RadioWidget public: - explicit RadioFaderWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : + explicit RadioFaderWidget(QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : RadioWidget(parent, f) { init(); } - explicit RadioFaderWidget(const QString &labelText, int value = 0, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : + explicit RadioFaderWidget(const QString & labelText, int value = 0, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : RadioWidget(labelText, value, parent, f) { init(); diff --git a/companion/src/simulation/widgets/radioknobwidget.h b/companion/src/simulation/widgets/radioknobwidget.h index 2343c8c9f..9c27d6b38 100644 --- a/companion/src/simulation/widgets/radioknobwidget.h +++ b/companion/src/simulation/widgets/radioknobwidget.h @@ -22,7 +22,7 @@ #define _RADIOKNOBWIDGET_H_ #include "radiowidget.h" -#include "radiodata.h" +#include "boards.h" #include #include @@ -68,7 +68,7 @@ class RadioKnobWidget : public RadioWidget class KnobWidget : public QDial { - friend class RadioKnobWidget; + friend class RadioKnobWidget; public: diff --git a/companion/src/simulation/widgets/radioswitchwidget.h b/companion/src/simulation/widgets/radioswitchwidget.h index 2aee3a435..2302cb8f3 100644 --- a/companion/src/simulation/widgets/radioswitchwidget.h +++ b/companion/src/simulation/widgets/radioswitchwidget.h @@ -22,7 +22,7 @@ #define _RADIOSWITCHWIDGET_H_ #include "radiowidget.h" -#include "radiodata.h" +#include "boards.h" #include #include @@ -33,15 +33,15 @@ class RadioSwitchWidget : public RadioWidget public: - explicit RadioSwitchWidget(Board::SwitchType type = Board::SWITCH_3POS, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : + explicit RadioSwitchWidget(Board::SwitchType type = Board::SWITCH_3POS, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : RadioWidget(parent, f), - switchConfig(type) + swType(type) { init(); } - explicit RadioSwitchWidget(Board::SwitchType type, const QString &labelText, int value = 0, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : + explicit RadioSwitchWidget(Board::SwitchType type, const QString & labelText, int value = 0, QWidget * parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : RadioWidget(labelText, value, parent, f), - switchConfig(type) + swType(type) { init(); } @@ -58,7 +58,7 @@ class RadioSwitchWidget : public RadioWidget sl->setInvertedControls(true); sl->setTickPosition(QSlider::TicksBothSides); sl->setPageStep(1); - sl->setMinimum((switchConfig == Board::SWITCH_3POS ? -1 : 0)); + sl->setMinimum((swType == Board::SWITCH_3POS ? -1 : 0)); sl->setMaximum(1); sl->setTickInterval(1); sl->setValue(m_value); @@ -70,7 +70,7 @@ class RadioSwitchWidget : public RadioWidget } private: - Board::SwitchType switchConfig; + Board::SwitchType swType; }; diff --git a/companion/src/simulation/widgets/radiowidget.cpp b/companion/src/simulation/widgets/radiowidget.cpp new file mode 100644 index 000000000..7885383df --- /dev/null +++ b/companion/src/simulation/widgets/radiowidget.cpp @@ -0,0 +1,201 @@ +/* + * 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 "radiowidget.h" + +RadioWidget::RadioWidget(QWidget * parent, Qt::WindowFlags f) : + QWidget(parent, f), + m_value(0), + m_flags(0), + m_invertValue(false), + m_showLabel(false), + m_labelText(""), + m_type(RADIO_WIDGET_NONE) +{ + init(); +} + +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) +{ + init(); +} + +void RadioWidget::setIndex(int index) +{ + m_index = index; +} + +void RadioWidget::setInvertValue(bool invertValue) +{ + m_invertValue = invertValue; +} + +void RadioWidget::setValue(int value) +{ + if (value != m_value) { + m_value = value; + emit valueChanged(m_value); + } +} + +void RadioWidget::setFlags(quint16 flags) +{ + if (flags != m_flags) { + m_flags = flags; + emit flagsChanged(flags); + } +} + +void RadioWidget::setShowLabel(bool show) +{ + if (show != m_showLabel) { + m_showLabel = show; + addLabel(); + } +} + +void RadioWidget::setLabelText(const QString & labelText, bool showLabel) +{ + m_labelText = labelText; + m_showLabel = showLabel; + addLabel(); +} + +void RadioWidget::setStateData(const QByteArray & data) +{ + RadioWidgetState state; + QDataStream stream(data); + stream >> state; + + //setIndex(state.index); + setValue(state.value); + setFlags(state.flags); +} + +void RadioWidget::changeVisibility(bool visible) +{ + static QSizePolicy oldSP = sizePolicy(); + setVisible(visible); + if (visible) { + setSizePolicy(oldSP); + } + else { + oldSP = sizePolicy(); + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + } +} + +int RadioWidget::getValue() const +{ + return m_value * (m_invertValue ? -1 : 1); +} + +int RadioWidget::getIndex() const +{ + return m_index; +} + +int RadioWidget::getType() const +{ + return m_type; +} + +QByteArray RadioWidget::getStateData() const +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << getState(); + return ba; +} + +RadioWidget::RadioWidgetState RadioWidget::getState() const +{ + return RadioWidgetState(quint8(m_type), qint8(m_index), qint16(m_value));; +} + +void RadioWidget::init() +{ + setIndex(0); + m_controlWidget = NULL; + m_nameLabel = NULL; + + m_gridLayout= new QGridLayout(this); + m_gridLayout->setContentsMargins(0, 0, 0, 0); + m_gridLayout->setVerticalSpacing(4); + m_gridLayout->setHorizontalSpacing(0); + + addLabel(); +} + +void RadioWidget::addLabel() +{ + if (m_nameLabel) { + m_gridLayout->removeWidget(m_nameLabel); + m_nameLabel->deleteLater(); + } + if (m_showLabel && !m_labelText.isEmpty()) { + m_nameLabel = new QLabel(m_labelText, this); + m_nameLabel->setAlignment(Qt::AlignCenter); + m_nameLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_gridLayout->addWidget(m_nameLabel, 1, 0, 1, 1, Qt::AlignTop); + m_gridLayout->setRowStretch(1, 0); + } +} + +void RadioWidget::setWidget(QWidget * widget) +{ + if (m_controlWidget) { + m_gridLayout->removeWidget(m_controlWidget); + m_controlWidget->deleteLater(); + } + m_controlWidget = widget; + if (widget) { + m_gridLayout->addWidget(widget, 0, 0, 1, 1, Qt::AlignHCenter); + m_gridLayout->setRowStretch(0, 1); + } +} + +QDataStream & operator << (QDataStream &out, const RadioWidget::RadioWidgetState & o) +{ + out << o._version << o.type << o.index << o.value << o.flags; + return out; +} + +QDataStream & operator >> (QDataStream &in, RadioWidget::RadioWidgetState & o) +{ + if (o._version <= RADIO_WIDGET_STATE_VERSION) + in >> o._version >> o.type >> o.index >> o.value >> o.flags; + return in; +} + +QDebug operator << (QDebug d, const RadioWidget::RadioWidgetState & o) +{ + QDebugStateSaver saver(d); + d << "RadioWidget::RadioWidgetState: type=" << o.type << "; index=" << o.index + << "; value=" << o.value << "; flags=0x" << hex << o.flags; + return d; +} diff --git a/companion/src/simulation/widgets/radiowidget.h b/companion/src/simulation/widgets/radiowidget.h index 458d69ff0..6c4d38858 100644 --- a/companion/src/simulation/widgets/radiowidget.h +++ b/companion/src/simulation/widgets/radiowidget.h @@ -21,146 +21,93 @@ #ifndef _RADIOWIDGET_H_ #define _RADIOWIDGET_H_ -#include -#include #include +#include +#include +#include + +#define RADIO_WIDGET_STATE_VERSION 1 class RadioWidget : public QWidget { Q_OBJECT public: - enum RadioWidgetType { RADIO_WIDGET_NONE, RADIO_WIDGET_SWITCH, RADIO_WIDGET_KNOB, RADIO_WIDGET_FADER }; - explicit RadioWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : - QWidget(parent, f), - m_value(0), - m_invertValue(false), - m_showLabel(false), - m_labelText(""), - m_type(RADIO_WIDGET_NONE) - { - init(); - } - explicit RadioWidget(const QString &labelText, int value = 0, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : - QWidget(parent, f), - m_value(value), - m_invertValue(false), - m_showLabel(true), - m_labelText(labelText), - m_type(RADIO_WIDGET_NONE) - { - init(); - } + // These are saved to persistent user settings in RadioWidgetState, + // so only append entires here, do not change index value if it can be avoided. + enum RadioWidgetType { + RADIO_WIDGET_NONE = 0, + RADIO_WIDGET_SWITCH, + RADIO_WIDGET_KNOB, + RADIO_WIDGET_FADER, + RADIO_WIDGET_STICK // actually one axis of a stick + }; - void setIndex(int index) { m_index = index; } - void setInvertValue(bool invertValue) { m_invertValue = invertValue; } + struct RadioWidgetState { + public: + RadioWidgetState(quint8 type = 0, qint8 index = 0, qint16 value = 0, quint16 flags = 0) : + type(type), index(index), value(value), flags(flags), + _version(RADIO_WIDGET_STATE_VERSION) + { } - void setValue(int value) - { - if (value != m_value) { - m_value = value; - emit valueChanged(m_value); - } - } + quint8 type; + qint8 index; + qint16 value; + quint16 flags; - void setShowLabel(bool show) - { - if (show != m_showLabel) { - m_showLabel = show; - addLabel(); - } - } + friend QDataStream & operator << (QDataStream &out, const RadioWidgetState & o); + friend QDataStream & operator >> (QDataStream &in, RadioWidgetState & o); + friend QDebug operator << (QDebug d, const RadioWidgetState & o); - void setLabelText(const QString &labelText, bool showLabel = true) - { - m_labelText = labelText; - m_showLabel = showLabel; - addLabel(); - } + private: + quint8 _version = RADIO_WIDGET_STATE_VERSION; // structure definition version + }; - void changeVisibility(bool visible) - { - static QSizePolicy oldSP = sizePolicy(); - setVisible(visible); - if (visible) { - setSizePolicy(oldSP); - } - else { - oldSP = sizePolicy(); - setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - } - } + 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()); - int getValue() const { return m_value * (m_invertValue ? -1 : 1); } - int getIndex() const { return m_index; } - int getType() const { return m_type; } + 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); + int getValue() const; + int getIndex() const; + int getType() const; + QByteArray getStateData() const; + RadioWidgetState getState() const; + protected: - void init() - { - setIndex(0); - m_controlWidget = NULL; - m_nameLabel = NULL; - m_gridLayout= new QGridLayout(this); - m_gridLayout->setContentsMargins(0, 0, 0, 0); - m_gridLayout->setVerticalSpacing(4); - m_gridLayout->setHorizontalSpacing(0); - - addLabel(); - } - - void addLabel() - { - if (m_nameLabel) { - m_gridLayout->removeWidget(m_nameLabel); - m_nameLabel->deleteLater(); - } - if (m_showLabel && !m_labelText.isEmpty()) { - m_nameLabel = new QLabel(m_labelText, this); - m_nameLabel->setAlignment(Qt::AlignCenter); - m_nameLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - m_gridLayout->addWidget(m_nameLabel, 1, 0, 1, 1, Qt::AlignTop); - m_gridLayout->setRowStretch(1, 0); - } - } - - void setWidget(QWidget * widget = NULL) - { - if (m_controlWidget) { - m_gridLayout->removeWidget(m_controlWidget); - m_controlWidget->deleteLater(); - } - m_controlWidget = widget; - if (widget) { - m_gridLayout->addWidget(widget, 0, 0, 1, 1, Qt::AlignHCenter); - m_gridLayout->setRowStretch(0, 1); - } - } + void init(); + void addLabel(); + void setWidget(QWidget * widget = NULL); int m_value; int m_index; + quint16 m_flags; bool m_invertValue; bool m_showLabel; QString m_labelText; RadioWidgetType m_type; private: + QGridLayout * m_gridLayout; QWidget * m_controlWidget; QLabel * m_nameLabel; signals: + void valueChanged(int m_value); + void flagsChanged(quint16 flags); }; #endif // _RADIOWIDGET_H_ - - - - - - diff --git a/companion/src/simulator.cpp b/companion/src/simulator.cpp index 5c10192f0..bc9d2bc9d 100644 --- a/companion/src/simulator.cpp +++ b/companion/src/simulator.cpp @@ -26,9 +26,6 @@ #include #include #include -#include -#include -#include #if defined(JOYSTICKS) || defined(SIMU_AUDIO) #include #undef main @@ -37,7 +34,9 @@ #include "appdata.h" #include "constants.h" #include "eeprominterface.h" +#include "simulator.h" #include "simulatordialog.h" +#include "simulatorstartupdialog.h" #include "storage.h" #include "qxtcommandoptions.h" @@ -66,17 +65,7 @@ class MyProxyStyle : public QProxyStyle }; #endif -typedef struct -{ - int profileId; - QString firmwareId; - QString eepromFileName; - QString dataFolder; - bool useFolder; -} simulatorOptions_t; - - -QDir g_eepromDirectory; +using namespace Simulator; int finish(int exitCode); @@ -88,202 +77,6 @@ void showMessage(const QString & message, enum QMessageBox::Icon icon = QMessage msgBox.exec(); } -bool usesCategorizedStorage(const QString &name) -{ - return name.contains("horus", Qt::CaseInsensitive); -} -QString radioEepromFileName(QString firmwareId) -{ - QString eepromFileName = "", ext = "bin"; - QString radioId = firmwareId; - int pos = firmwareId.indexOf("-"); - if (pos > 0) { - radioId = firmwareId.mid(pos+1); - pos = radioId.lastIndexOf("-"); - if (pos > 0) { - radioId = radioId.mid(0, pos); - } - } - if (usesCategorizedStorage(radioId)) - ext = "otx"; - - eepromFileName = QString("eeprom-%1.%2").arg(radioId, ext); - eepromFileName = g_eepromDirectory.filePath(eepromFileName.toLatin1()); - // qDebug() << "radioId" << radioId << "eepromFileName" << eepromFileName; - - return eepromFileName; -} - -bool startupOptionsDialog(simulatorOptions_t &opts) -{ - bool ret = false; - QString label; - - QDialog * dialog = new QDialog(); - dialog->setWindowFlags(dialog->windowFlags() & (~ Qt::WindowContextHelpButtonHint)); - - QFormLayout * form = new QFormLayout(dialog); - form->addRow(new QLabel(QObject::tr("Simulator Startup Options:"))); - - label = QObject::tr("Profile:"); - QComboBox * cbProf = new QComboBox(); - cbProf->setToolTip(QObject::tr("Existing radio profiles are shown here.
" \ - "Create or edit profiles using the Companion application.")); - QMapIterator pi(g.getActiveProfiles()); - while (pi.hasNext()) { - pi.next(); - cbProf->addItem(pi.value(), pi.key()); - if (pi.key() == opts.profileId) - cbProf->setCurrentIndex(cbProf->count() - 1); - } - form->addRow(label, cbProf); - - label = QObject::tr("Radio Type:"); - QComboBox * cbType = new QComboBox(); - cbType->setToolTip(QObject::tr("Existing radio simulators are shown here.
" \ - "The radio type specified in the selected profile is used by default.")); - cbType->addItems(registered_simulators.keys()); - cbType->setCurrentIndex(cbType->findText(opts.firmwareId)); - if (cbType->currentIndex() == -1 && cbProf->currentIndex() > -1){ - SimulatorFactory * sf = getSimulatorFactory(g.profile[cbProf->currentIndex()].fwType()); - if (sf) - cbType->setCurrentIndex(cbType->findText(sf->name())); - } - form->addRow(label, cbType); - - label = QObject::tr("Data File:"); - QLineEdit * fwFile = new QLineEdit(opts.eepromFileName, dialog); - fwFile->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - fwFile->setToolTip(QObject::tr("Radio data (.bin/.eeprom/.otx) image file to use. A new file with a default image will be created if necessary.
" \ - "NOTE: any existing EEPROM data incompatible with the selected radio type may be overwritten!")); - QToolButton * fwBtn = new QToolButton(dialog); - fwBtn->setText("..."); - fwBtn->setToolButtonStyle(Qt::ToolButtonTextOnly); - fwBtn->setToolTip(QObject::tr("Select radio data...")); - QWidget * fw = new QWidget(dialog); - QHBoxLayout * hl = new QHBoxLayout(fw); - hl->setContentsMargins(0, 0, 0, 0); - hl->setSpacing(2); - hl->addWidget(fwFile, 2); - hl->addWidget(fwBtn, 0); - form->addRow(label, fw); - - label = QObject::tr("Data Folder:"); - QLineEdit * fwFolder = new QLineEdit(opts.dataFolder, dialog); - fwFolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - fwFolder->setToolTip(QObject::tr("Directory containing RADIO and MODELS folders to use." \ - "
New folder(s) with default radio/model will be created here if necessary.")); - QToolButton * fwFolderBtn = new QToolButton(dialog); - fwFolderBtn->setText("..."); - fwFolderBtn->setToolButtonStyle(Qt::ToolButtonTextOnly); - fwFolderBtn->setToolTip(QObject::tr("Select data folder...")); - QWidget * fwf = new QWidget(dialog); - QHBoxLayout * hlf = new QHBoxLayout(fwf); - hlf->setContentsMargins(0, 0, 0, 0); - hlf->setSpacing(2); - hlf->addWidget(fwFolder, 2); - hlf->addWidget(fwFolderBtn, 0); - form->addRow(label, fwf); - - label = QObject::tr("Date Source:"); - QRadioButton * dsfileBtn = new QRadioButton(QObject::tr("File"), dialog); - QRadioButton * dsdirBtn = new QRadioButton(QObject::tr("Folder"), dialog); - dsfileBtn->setChecked(true); - QButtonGroup * btngrp = new QButtonGroup(dialog); - btngrp->setExclusive(true); - btngrp->addButton(dsfileBtn); - btngrp->addButton(dsdirBtn); - QWidget * btnwdgt = new QWidget(dialog); - QHBoxLayout * btnlo = new QHBoxLayout(btnwdgt); - btnlo->setContentsMargins(0, 0, 0, 0); - btnlo->setSpacing(9); - btnlo->addWidget(dsfileBtn); - btnlo->addWidget(dsdirBtn); - btnlo->addStretch(1); - form->addRow(label, btnwdgt); - - QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dialog); - form->addRow(&buttonBox); - - dialog->resize(400, dialog->sizeHint().height()); - - QObject::connect(&buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); - QObject::connect(&buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); - - auto changeContainerType = [cbType, btnwdgt, dsfileBtn, dsdirBtn, fw, fwf, form] (void) - { - bool show = usesCategorizedStorage(cbType->currentText()); - //btnwdgt->setVisible(show); - //form->labelForField(btnwdgt)->setVisible(show); - fwf->setVisible(show); - form->labelForField(fwf)->setVisible(show); - // TODO : remove below once .otx support works and un-comment 2 lines above - fw->setVisible(!show); - form->labelForField(fw)->setVisible(!show); - dsdirBtn->setChecked(show); - dsfileBtn->setChecked(!show); - btnwdgt->setVisible(false); - form->labelForField(btnwdgt)->setVisible(false); - }; - - changeContainerType(); - - // set new default radio type when profile choice changes - QObject::connect(cbProf, static_cast(&QComboBox::currentIndexChanged), [cbProf, cbType] (int index) { - if (index < 0) - return; - SimulatorFactory * sf = getSimulatorFactory(g.profile[index].fwType()); - if (sf) { - int i = cbType->findText(sf->name()); - if (i > -1) { - cbType->setCurrentIndex(i); - } - } - }); - - // set new default firmware file when radio type changes - QObject::connect(cbType, static_cast(&QComboBox::currentIndexChanged), [cbType, fwFile, changeContainerType] (int index) { - if (index < 0) - return; - fwFile->setText(radioEepromFileName(cbType->currentText())); - changeContainerType(); - }); - - // connect button to file selector dialog - QObject::connect(fwBtn, &QToolButton::clicked, [dialog, fwFile, cbType, opts, dsfileBtn] (bool) { - QString filter = QObject::tr((usesCategorizedStorage(cbType->currentText()) ? OTX_FILES_FILTER : EEPROM_FILES_FILTER)); - filter += QObject::tr("All files (*.*)"); - QString file = QFileDialog::getSaveFileName(dialog, QObject::tr("Select EEPROM image"), fwFile->text(), - filter, NULL, QFileDialog::DontConfirmOverwrite); - if (!file.isEmpty()) { - fwFile->setText(file); - dsfileBtn->setChecked(true); - } - }); - - // connect button to folder selector dialog - QObject::connect(fwFolderBtn, &QToolButton::clicked, [dialog, fwFolder, cbType, opts, dsdirBtn](bool) { - QString folder = QFileDialog::getExistingDirectory(dialog, QObject::tr("Select Data Directory"), fwFolder->text(), QFileDialog::DontUseNativeDialog); - if (!folder.isEmpty()) { - fwFolder->setText(folder); - dsdirBtn->setChecked(true); - } - }); - - // go - if (dialog->exec() == QDialog::Accepted) { - opts.profileId = cbProf->currentData().toInt(); - opts.firmwareId = cbType->currentText(); - opts.eepromFileName = fwFile->text(); - opts.dataFolder = fwFolder->text(); - opts.useFolder = dsdirBtn->isChecked(); - ret = true; - } - - dialog->deleteLater(); - return ret; -} - void sharedHelpText(QTextStream &stream) { // list all available profiles @@ -300,24 +93,36 @@ void sharedHelpText(QTextStream &stream) } } +QString findFirmwareId(int profileId) +{ + SimulatorFactory * sf = getSimulatorFactory(g.profile[profileId].fwType()); + if (sf) + return sf->name(); + else + return QString(); +} + int main(int argc, char *argv[]) { Q_INIT_RESOURCE(companion); + #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #if defined(WIN32) && defined(WIN_USE_CONSOLE_STDIO) - AllocConsole(); - SetConsoleTitle("Simulator Console"); - freopen("conin$", "r", stdin); - freopen("conout$", "w", stdout); - freopen("conout$", "w", stderr); + if (!GetConsoleWindow()) { + AllocConsole(); + freopen("conin$", "r", stdin); + freopen("conout$", "w", stdout); + freopen("conout$", "w", stderr); + } + SetConsoleTitle(APP_SIMULATOR " Console"); #endif QApplication app(argc, argv); - app.setApplicationName("OpenTX Simulator"); - app.setOrganizationName("OpenTX"); - app.setOrganizationDomain("open-tx.org"); + app.setApplicationName(APP_SIMULATOR); + app.setOrganizationName(COMPANY); + app.setOrganizationDomain(COMPANY_DOMAIN); g.init(); @@ -348,20 +153,14 @@ int main(int argc, char *argv[]) } #endif - simulatorOptions_t simOptions; QxtCommandOptions cliOptions; bool cliOptsFound = false; + int profileId = (g.simuLastProfId() > -1 ? g.simuLastProfId() : g.id()); + SimulatorOptions simOptions = g.profile[profileId].simulatorOptions(); - g_eepromDirectory = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); - if (!g_eepromDirectory.exists("OpenTX")) { - if (!g_eepromDirectory.mkpath("OpenTX")) { - showMessage(QObject::tr("WARNING: couldn't create directory for EEPROM:\n%1").arg(g_eepromDirectory.absoluteFilePath("OpenTX")), QMessageBox::Warning); - } - } - g_eepromDirectory.cd("OpenTX"); - - registerSimulators(); + registerStorageFactories(); registerOpenTxFirmwares(); + registerSimulators(); if (!registered_simulators.size()) { showMessage(QObject::tr("ERROR: No simulator libraries available."), QMessageBox::Critical); @@ -387,75 +186,71 @@ int main(int argc, char *argv[]) return finish(1); } - if (cliOptions.count("radio") == 1) { - simOptions.firmwareId = cliOptions.value("radio").toString(); - cliOptsFound = true; - } - else { - simOptions.firmwareId = g.lastSimulator(); - } + // TODO : defaults should be set in Profile::init() + if (simOptions.firmwareId.isEmpty()) + simOptions.firmwareId = findFirmwareId(profileId); + if (simOptions.dataFolder.isEmpty()) + simOptions.dataFolder = g.eepromDir(); + if (simOptions.sdPath.isEmpty()) + simOptions.sdPath = g.profile[profileId].sdPath(); if (cliOptions.count("profile") == 1) { bool chk; int pid = cliOptions.value("profile").toInt(&chk); - if (chk) { - simOptions.profileId = pid; - } - else { - simOptions.profileId = g.getActiveProfiles().key(cliOptions.value("profile").toString(), -1); - } - // load default radio for this profile if not already passed on command line - if (!cliOptsFound) { - SimulatorFactory * sf = getSimulatorFactory(g.profile[simOptions.profileId].fwType()); - if (sf) - simOptions.firmwareId = sf->name(); + if (chk) + profileId = pid; + else + profileId = g.getActiveProfiles().key(cliOptions.value("profile").toString(), -1); + + if (!g.getActiveProfiles().contains(profileId)) { + fprintf(stderr, "Error: Profile ID %d was not found.", profileId); + return finish(1); } + + simOptions = g.profile[profileId].simulatorOptions(); cliOptsFound = true; } - else if (g.simuLastProfId() != -1) { - simOptions.profileId = g.simuLastProfId(); - } - else { - simOptions.profileId = g.id(); + + if (cliOptions.count("radio") == 1) { + simOptions.firmwareId = cliOptions.value("radio").toString(); + cliOptsFound = true; } + if (cliOptsFound || (simOptions.dataFile.isEmpty() && !simOptions.firmwareId.isEmpty())) + simOptions.dataFile = SimulatorStartupDialog::radioEepromFileName(simOptions.firmwareId, g.eepromDir()); + if (!cliOptions.positional().isEmpty()) { if (QString(cliOptions.positional().at(0)).contains(QRegExp(".*\\.[\\w]{2,6}$"))) { - simOptions.eepromFileName = cliOptions.positional()[0]; + simOptions.dataFile = cliOptions.positional()[0]; + simOptions.startupDataType = SimulatorOptions::START_WITH_FILE; } else { simOptions.dataFolder = cliOptions.positional()[0]; - simOptions.useFolder = true; + simOptions.startupDataType = SimulatorOptions::START_WITH_FOLDER; } cliOptsFound = true; } - else if (cliOptsFound || g.simuLastEepe().isEmpty()) { - simOptions.eepromFileName = radioEepromFileName(simOptions.firmwareId); - simOptions.dataFolder = g_eepromDirectory.absolutePath(); - } - else { - simOptions.eepromFileName = g.simuLastEepe(); - simOptions.dataFolder = g.simuLastFolder(); - } - if (!cliOptsFound || simOptions.profileId == -1 || simOptions.firmwareId.isEmpty() || (simOptions.eepromFileName.isEmpty() && simOptions.dataFolder.isEmpty())) { - if (!startupOptionsDialog(simOptions)) { + if (!cliOptsFound || profileId == -1 || simOptions.firmwareId.isEmpty() || (simOptions.dataFile.isEmpty() && simOptions.dataFolder.isEmpty())) { + SimulatorStartupDialog * dlg = new SimulatorStartupDialog(&simOptions, &profileId); + int ret = dlg->exec(); + dlg->deleteLater(); + if (ret != QDialog::Accepted) { return finish(0); } } - qDebug() << "firmwareId" << simOptions.firmwareId << "profileId" << simOptions.profileId << "eepromFileName" \ - << simOptions.eepromFileName << "dataFolder" << simOptions.dataFolder << "useFolder" << simOptions.useFolder; + qDebug() << __FILE__ << __LINE__ << "profileId=" << profileId << simOptions; - if (simOptions.profileId < 0 || simOptions.firmwareId.isEmpty() || (simOptions.eepromFileName.isEmpty() && simOptions.dataFolder.isEmpty())) { - showMessage(QObject::tr("ERROR: Couldn't start simulator, missing radio/profile/data file/folder.\nProfile ID: [%1]; Radio ID: [%2];\nData File: [%3]") - .arg(simOptions.profileId).arg(simOptions.firmwareId).arg(simOptions.eepromFileName), QMessageBox::Critical); + if (profileId < 0 || simOptions.firmwareId.isEmpty() || (simOptions.dataFile.isEmpty() && simOptions.dataFolder.isEmpty())) { + showMessage(QObject::tr("ERROR: Couldn't start simulator, missing radio/profile/data file/folder.\n Profile ID: [%1]; Radio ID: [%2];\nData File: [%3]") + .arg(profileId).arg(simOptions.firmwareId, simOptions.dataFile), QMessageBox::Critical); return finish(1); } - if (!g.getActiveProfiles().contains(simOptions.profileId) || !registered_simulators.keys().contains(simOptions.firmwareId)) { + if (!g.getActiveProfiles().contains(profileId) || !registered_simulators.keys().contains(simOptions.firmwareId)) { QString msg; QTextStream stream(&msg); stream << QObject::tr("ERROR: Radio profile or simulator firmware not found.\nProfile ID: [%1]; Radio ID: [%2]") - .arg(simOptions.profileId).arg(simOptions.firmwareId); + .arg(profileId).arg(simOptions.firmwareId); sharedHelpText(stream); showMessage(msg, QMessageBox::Critical); return finish(2); @@ -468,24 +263,14 @@ int main(int argc, char *argv[]) } current_firmware_variant = getFirmware(simOptions.firmwareId); - registerStorageFactories(); int oldProfId = g.id(); - g.id(simOptions.profileId); - g.simuLastProfId(simOptions.profileId); - g.lastSimulator(simOptions.firmwareId); - g.simuLastEepe(simOptions.eepromFileName); - g.simuLastFolder(simOptions.dataFolder); + g.id(profileId); + g.simuLastProfId(profileId); SimulatorDialog * dialog = new SimulatorDialog(NULL, factory->create(), SIMULATOR_FLAGS_STANDALONE); - dialog->setRadioProfileId(simOptions.profileId); - if (simOptions.useFolder && !simOptions.dataFolder.isEmpty()) { - dialog->setDataPath(simOptions.dataFolder); - dialog->setEepromData(); - } - else { - dialog->setEepromData(simOptions.eepromFileName.toLocal8Bit(), true); - } + dialog->setRadioProfileId(profileId); + dialog->setOptions(simOptions, true); dialog->start(); dialog->show(); int result = app.exec(); diff --git a/companion/src/storage/appdata.cpp b/companion/src/storage/appdata.cpp index ffa8e80f7..ffe84efac 100644 --- a/companion/src/storage/appdata.cpp +++ b/companion/src/storage/appdata.cpp @@ -24,191 +24,93 @@ AppData g; // ** CompStoreObj class******************** -void CompStoreObj::clear (const QString tag1, const QString tag2, const QString tag3) +void CompStoreObj::clear (const QString &tag1, const QString &tag2, const QString &tag3) { - QSettings settings(COMPANY, PRODUCT); - if (tag2.isEmpty()) - { - settings.remove(tag1); - } - else if (tag3.isEmpty()) - { - settings.beginGroup(tag1); - settings.remove(tag2); - settings.endGroup(); - } - else - { - settings.beginGroup(tag1); - settings.beginGroup(tag2); - settings.remove(tag3); - settings.endGroup(); - settings.endGroup(); - } + QSettings settings(COMPANY, PRODUCT); + if (tag2.isEmpty()) + { + settings.remove(tag1); + } + else if (tag3.isEmpty()) + { + settings.beginGroup(tag1); + settings.remove(tag2); + settings.endGroup(); + } + else + { + settings.beginGroup(tag1); + settings.beginGroup(tag2); + settings.remove(tag3); + settings.endGroup(); + settings.endGroup(); + } } -void CompStoreObj::store(const QByteArray newArray, QByteArray &array, const QString tag, const QString group1, const QString group2 ) +template +void CompStoreObj::store(const OBJ_T newValue, OBJ_T & destValue, const TAG_T tag, const GP1_T group1, const GP2_T group2) { - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); + QSettings settings(COMPANY, PRODUCT); + QString g1 = group1; // these may come in as const char * or QString + QString g2 = group2; + if (!g1.isEmpty()){ + settings.beginGroup(g1); + if (!g2.isEmpty()) + settings.beginGroup(g2); + } - settings.setValue(tag, newArray); - array = newArray; + settings.setValue(tag, QVariant::fromValue(newValue)); + destValue = newValue; - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); + if (!g1.isEmpty()){ + settings.endGroup(); + if (!g2.isEmpty()) + settings.endGroup(); + } } -void CompStoreObj::store(const QStringList newSList, QStringList &stringList, const QString tag, const QString group1, const QString group2 ) +template +void CompStoreObj::retrieve(OBJ_T & destValue, const TAG_T tag, const DEF_T def, const GP1_T group1, const GP2_T group2) { - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); + QSettings settings(COMPANY, PRODUCT); + QString g1 = group1; // these may come in as const char * or QString + QString g2 = group2; + if (!g1.isEmpty()){ + settings.beginGroup(g1); + if (!g2.isEmpty()) + settings.beginGroup(g2); + } - settings.setValue(tag, newSList); - stringList = newSList; + QVariant val = settings.value(QString(tag), QVariant::fromValue(def)); + if (val.canConvert()) + destValue = val.value(); - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); + if (!g1.isEmpty()){; + settings.endGroup(); + if (!g2.isEmpty()) + settings.endGroup(); + } } -void CompStoreObj::store(const QString newString, QString &string, const QString tag, const QString group1, const QString group2 ) +template +void CompStoreObj::getset(OBJ_T & value, const TAG_T tag, const DEF_T def, const GP1_T group1, const GP2_T group2 ) { - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - settings.setValue(tag, newString); - string = newString; - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); + retrieve(value, tag, def, group1, group2); + store(value, value, tag, group1, group2); } -void CompStoreObj::store(const bool newTruth, bool &truth, const QString tag, const QString group1, const QString group2 ) +template +void CompStoreObj::getset(OBJ_T & value, const TAG_T tag, const char * def, const GP1_T group1, const GP2_T group2 ) { - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - settings.setValue(tag, newTruth); - truth = newTruth; - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); + getset(value, tag, QString(def), group1, group2); } -void CompStoreObj::store(const int newNumber, int &number, const QString tag, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - settings.setValue(tag, newNumber); - number = newNumber; - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -// Retrieval functions -void CompStoreObj::retrieve( QByteArray &array, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - array = settings.value(tag, def).toByteArray(); - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -void CompStoreObj::retrieve( QStringList &stringList, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - stringList = settings.value(tag, def).toStringList(); - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -void CompStoreObj::retrieve( QString &string, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - string = settings.value(tag, def).toString(); - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -void CompStoreObj::retrieve( bool &truth, const QString tag, const bool def, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - truth = settings.value(tag, def).toBool(); - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -void CompStoreObj::retrieve( int &number, const QString tag, const int def, const QString group1, const QString group2 ) -{ - QSettings settings(COMPANY, PRODUCT); - if (!group1.isEmpty()) settings.beginGroup(group1); - if (!group2.isEmpty()) settings.beginGroup(group2); - - number = settings.value(tag, def).toInt(); - - if (!group1.isEmpty()) settings.endGroup(); - if (!group2.isEmpty()) settings.endGroup(); -} - -// Retrieve and Store functions -void CompStoreObj::getset( QByteArray &array, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - retrieve( array, tag, def, group1, group2); - store(array, array, tag, group1, group2); -} - -void CompStoreObj::getset( QStringList &stringList, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - retrieve( stringList, tag, def, group1, group2); - store(stringList, stringList, tag, group1, group2); -} - -void CompStoreObj::getset( QString &string, const QString tag, const QString def, const QString group1, const QString group2 ) -{ - retrieve( string, tag, def, group1, group2); - store(string, string, tag, group1, group2); -} - -void CompStoreObj::getset( bool &truth, const QString tag, const bool def, const QString group1, const QString group2 ) -{ - retrieve( truth, tag, def, group1, group2); - store(truth, truth, tag, group1, group2); -} - -void CompStoreObj::getset( int &number, const QString tag, const int def, const QString group1, const QString group2 ) -{ - retrieve( number, tag, def, group1, group2); - store(number, number, tag, group1, group2); -} // ** FwRevision class******************** int FwRevision::get( const QString fwType ) { QString result; - retrieve( result, fwType, "", "FwRevisions" ); + retrieve( result, fwType, QString(""), "FwRevisions" ); return result.toInt(); } @@ -310,8 +212,6 @@ bool Profile::renameFwFiles() const { return _renameFwFiles; } int Profile::channelOrder() const { return _channelOrder; } int Profile::defaultMode() const { return _defaultMode; } -QByteArray Profile::simuWinGeo() const { return _simuWinGeo; } - QString Profile::beeper() const { return _beeper; } QString Profile::countryCode() const { return _countryCode; } QString Profile::display() const { return _display; } @@ -322,47 +222,48 @@ QString Profile::timeStamp() const { return _timeStamp; } QString Profile::trainerCalib() const { return _trainerCalib; } QString Profile::controlTypes() const { return _controlTypes; } QString Profile::controlNames() const { return _controlNames; } -int Profile::txCurrentCalibration() const { return _txCurrentCalibration; } int Profile::gsStickMode() const { return _gsStickMode; } int Profile::ppmMultiplier() const { return _ppmMultiplier; } -int Profile::txVoltageCalibration() const { return _txVoltageCalibration; } int Profile::vBatWarn() const { return _vBatWarn; } int Profile::vBatMin() const { return _vBatMin; } int Profile::vBatMax() const { return _vBatMax; } +int Profile::txCurrentCalibration() const { return _txCurrentCalibration; } +int Profile::txVoltageCalibration() const { return _txVoltageCalibration; } + +SimulatorOptions Profile::simulatorOptions() const { return _simulatorOptions; } // Set declarations -void Profile::name (const QString x) { store(x, _name, "Name" ,"Profiles", QString("profile%1").arg(index));} -void Profile::fwName (const QString x) { store(x, _fwName, "fwName" ,"Profiles", QString("profile%1").arg(index));} -void Profile::fwType (const QString x) { store(x, _fwType, "fwType" ,"Profiles", QString("profile%1").arg(index));} -void Profile::sdPath (const QString x) { store(x, _sdPath, "sdPath" ,"Profiles", QString("profile%1").arg(index));} -void Profile::volumeGain (const int x) { store(x, _volumeGain, "volumeGain" ,"Profiles", QString("profile%1").arg(index));} -void Profile::pBackupDir (const QString x) { store(x, _pBackupDir, "pBackupDir" ,"Profiles", QString("profile%1").arg(index));} -void Profile::splashFile (const QString x) { store(x, _splashFile, "SplashFileName" ,"Profiles", QString("profile%1").arg(index));} -void Profile::burnFirmware (const bool x) { store(x, _burnFirmware, "burnFirmware" ,"Profiles", QString("profile%1").arg(index));} -void Profile::renameFwFiles (const bool x) { store(x, _renameFwFiles, "rename_firmware_files" ,"Profiles", QString("profile%1").arg(index));} -void Profile::penableBackup (const bool x) { store(x, _penableBackup, "penableBackup" ,"Profiles", QString("profile%1").arg(index));} -void Profile::channelOrder (const int x) { store(x, _channelOrder, "default_channel_order" ,"Profiles", QString("profile%1").arg(index));} -void Profile::defaultMode (const int x) { store(x, _defaultMode, "default_mode" ,"Profiles", QString("profile%1").arg(index));} +void Profile::name (const QString x) { store(x, _name, "Name" ,"Profiles", groupId());} +void Profile::fwName (const QString x) { store(x, _fwName, "fwName" ,"Profiles", groupId());} +void Profile::fwType (const QString x) { store(x, _fwType, "fwType" ,"Profiles", groupId());} +void Profile::sdPath (const QString x) { store(x, _sdPath, "sdPath" ,"Profiles", groupId());} +void Profile::volumeGain (const int x) { store(x, _volumeGain, "volumeGain" ,"Profiles", groupId());} +void Profile::pBackupDir (const QString x) { store(x, _pBackupDir, "pBackupDir" ,"Profiles", groupId());} +void Profile::splashFile (const QString x) { store(x, _splashFile, "SplashFileName" ,"Profiles", groupId());} +void Profile::burnFirmware (const bool x) { store(x, _burnFirmware, "burnFirmware" ,"Profiles", groupId());} +void Profile::renameFwFiles (const bool x) { store(x, _renameFwFiles, "rename_firmware_files" ,"Profiles", groupId());} +void Profile::penableBackup (const bool x) { store(x, _penableBackup, "penableBackup" ,"Profiles", groupId());} +void Profile::channelOrder (const int x) { store(x, _channelOrder, "default_channel_order" ,"Profiles", groupId());} +void Profile::defaultMode (const int x) { store(x, _defaultMode, "default_mode" ,"Profiles", groupId());} +void Profile::beeper (const QString x) { store(x, _beeper, "Beeper" ,"Profiles", groupId());} +void Profile::countryCode (const QString x) { store(x, _countryCode, "countryCode" ,"Profiles", groupId());} +void Profile::display (const QString x) { store(x, _display, "Display" ,"Profiles", groupId());} +void Profile::haptic (const QString x) { store(x, _haptic, "Haptic" ,"Profiles", groupId());} +void Profile::speaker (const QString x) { store(x, _speaker, "Speaker" ,"Profiles", groupId());} +void Profile::stickPotCalib (const QString x) { store(x, _stickPotCalib, "StickPotCalib" ,"Profiles", groupId());} +void Profile::timeStamp (const QString x) { store(x, _timeStamp, "TimeStamp" ,"Profiles", groupId());} +void Profile::trainerCalib (const QString x) { store(x, _trainerCalib, "TrainerCalib" ,"Profiles", groupId());} +void Profile::controlTypes (const QString x) { store(x, _controlTypes, "ControlTypes" ,"Profiles", groupId());} +void Profile::controlNames (const QString x) { store(x, _controlNames, "ControlNames" ,"Profiles", groupId());} +void Profile::gsStickMode (const int x) { store(x, _gsStickMode, "GSStickMode" ,"Profiles", groupId());} +void Profile::ppmMultiplier (const int x) { store(x, _ppmMultiplier, "PPM_Multiplier" ,"Profiles", groupId());} +void Profile::vBatWarn (const int x) { store(x, _vBatWarn, "vBatWarn" ,"Profiles", groupId());} +void Profile::vBatMin (const int x) { store(x, _vBatMin, "VbatMin" ,"Profiles", groupId());} +void Profile::vBatMax (const int x) { store(x, _vBatMax, "VbatMax" ,"Profiles", groupId());} +void Profile::txCurrentCalibration (const int x) { store(x, _txCurrentCalibration, "currentCalib","Profiles", groupId());} +void Profile::txVoltageCalibration (const int x) { store(x, _txVoltageCalibration, "VbatCalib" ,"Profiles", groupId());} -void Profile::simuWinGeo (const QByteArray x) { store(x, _simuWinGeo, "simuWindowGeometry" ,"Profiles", QString("profile%1").arg(index));} - -void Profile::beeper (const QString x) { store(x, _beeper, "Beeper" ,"Profiles", QString("profile%1").arg(index));} -void Profile::countryCode (const QString x) { store(x, _countryCode, "countryCode" ,"Profiles", QString("profile%1").arg(index));} -void Profile::display (const QString x) { store(x, _display, "Display" ,"Profiles", QString("profile%1").arg(index));} -void Profile::haptic (const QString x) { store(x, _haptic, "Haptic" ,"Profiles", QString("profile%1").arg(index));} -void Profile::speaker (const QString x) { store(x, _speaker, "Speaker" ,"Profiles", QString("profile%1").arg(index));} -void Profile::stickPotCalib (const QString x) { store(x, _stickPotCalib, "StickPotCalib" ,"Profiles", QString("profile%1").arg(index));} -void Profile::timeStamp (const QString x) { store(x, _timeStamp, "TimeStamp" ,"Profiles", QString("profile%1").arg(index));} -void Profile::trainerCalib (const QString x) { store(x, _trainerCalib, "TrainerCalib" ,"Profiles", QString("profile%1").arg(index));} -void Profile::controlTypes (const QString x) { store(x, _controlTypes, "ControlTypes" ,"Profiles", QString("profile%1").arg(index));} -void Profile::controlNames (const QString x) { store(x, _controlNames, "ControlNames" ,"Profiles", QString("profile%1").arg(index));} -void Profile::txCurrentCalibration (const int x) { store(x, _txCurrentCalibration, "currentCalib","Profiles", QString("profile%1").arg(index));} -void Profile::gsStickMode (const int x) { store(x, _gsStickMode, "GSStickMode" ,"Profiles", QString("profile%1").arg(index));} -void Profile::ppmMultiplier (const int x) { store(x, _ppmMultiplier, "PPM_Multiplier" ,"Profiles", QString("profile%1").arg(index));} -void Profile::txVoltageCalibration (const int x) { store(x, _txVoltageCalibration, "VbatCalib","Profiles", QString("profile%1").arg(index));} -void Profile::vBatWarn (const int x) { store(x, _vBatWarn, "vBatWarn" ,"Profiles", QString("profile%1").arg(index));} -void Profile::vBatMin (const int x) { store(x, _vBatMin, "VbatMin" ,"Profiles", QString("profile%1").arg(index));} -void Profile::vBatMax (const int x) { store(x, _vBatMax, "VbatMax" ,"Profiles", QString("profile%1").arg(index));} +void Profile::simulatorOptions(const SimulatorOptions & x) { store(x, _simulatorOptions, "simulatorOptions" ,"Profiles", groupId()); } // Constructor Profile::Profile() @@ -394,13 +295,14 @@ Profile& Profile::operator=(const Profile& rhs) trainerCalib ( rhs.trainerCalib() ); controlTypes ( rhs.controlTypes() ); controlNames ( rhs.controlNames() ); - txCurrentCalibration ( rhs.txCurrentCalibration() ); gsStickMode ( rhs.gsStickMode() ); ppmMultiplier( rhs.ppmMultiplier() ); - txVoltageCalibration ( rhs.txVoltageCalibration() ); vBatWarn ( rhs.vBatWarn() ); vBatMin ( rhs.vBatMin() ); vBatMax ( rhs.vBatMax() ); + txCurrentCalibration ( rhs.txCurrentCalibration() ); + txVoltageCalibration ( rhs.txVoltageCalibration() ); + simulatorOptions ( rhs.simulatorOptions() ); return *this; } @@ -410,7 +312,7 @@ void Profile::remove() // Remove all profile values from settings file QSettings settings(COMPANY, PRODUCT); settings.beginGroup("Profiles"); - settings.remove(QString("profile%1").arg(index)); + settings.remove(groupId()); settings.endGroup(); // Reset all profile variables to initial values @@ -421,7 +323,7 @@ bool Profile::existsOnDisk() { QSettings settings(COMPANY, PRODUCT); settings.beginGroup("Profiles"); - settings.beginGroup(QString("profile%1").arg(index)); + settings.beginGroup(groupId()); QStringList keyList = settings.allKeys(); settings.endGroup(); settings.endGroup(); @@ -468,7 +370,7 @@ void Profile::init(int newIndex) _channelOrder = 0; _defaultMode = 1; - _simuWinGeo = QByteArray(); + _simulatorOptions = SimulatorOptions(); initFwVariables(); @@ -482,38 +384,44 @@ void Profile::init(int newIndex) void Profile::flush() { // Load and store all variables. Use default values if setting values are missing - getset( _fwName, "fwName" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _fwType, "fwType" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _name, "Name" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _sdPath, "sdPath" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _volumeGain, "volumeGain" ,10 ,"Profiles", QString("profile%1").arg(index)); - getset( _pBackupDir, "pBackupDir" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _splashFile, "SplashFileName" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _burnFirmware, "burnFirmware" ,false ,"Profiles", QString("profile%1").arg(index)); - getset( _penableBackup, "penableBackup" ,false ,"Profiles", QString("profile%1").arg(index)); - getset( _renameFwFiles, "rename_firmware_files" ,false ,"Profiles", QString("profile%1").arg(index)); - getset( _channelOrder, "default_channel_order" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _defaultMode, "default_mode" ,1 ,"Profiles", QString("profile%1").arg(index)); + getset( _fwName, "fwName" ,"" ,"Profiles", groupId()); + getset( _fwType, "fwType" ,"" ,"Profiles", groupId()); + getset( _name, "Name" ,"" ,"Profiles", groupId()); + getset( _sdPath, "sdPath" ,"" ,"Profiles", groupId()); + getset( _volumeGain, "volumeGain" ,10 ,"Profiles", groupId()); + getset( _pBackupDir, "pBackupDir" ,"" ,"Profiles", groupId()); + getset( _splashFile, "SplashFileName" ,"" ,"Profiles", groupId()); + getset( _burnFirmware, "burnFirmware" ,false ,"Profiles", groupId()); + getset( _penableBackup, "penableBackup" ,false ,"Profiles", groupId()); + getset( _renameFwFiles, "rename_firmware_files" ,false ,"Profiles", groupId()); + getset( _channelOrder, "default_channel_order" ,0 ,"Profiles", groupId()); + getset( _defaultMode, "default_mode" ,1 ,"Profiles", groupId()); - getset( _simuWinGeo, "simuWindowGeometry" ,"" ,"Profiles", QString("profile%1").arg(index)); + getset( _beeper, "Beeper" ,"" ,"Profiles", groupId()); + getset( _countryCode, "countryCode" ,"" ,"Profiles", groupId()); + getset( _display, "Display" ,"" ,"Profiles", groupId()); + getset( _haptic, "Haptic" ,"" ,"Profiles", groupId()); + getset( _speaker, "Speaker" ,"" ,"Profiles", groupId()); + getset( _stickPotCalib, "StickPotCalib" ,"" ,"Profiles", groupId()); + getset( _timeStamp, "TimeStamp" ,"" ,"Profiles", groupId()); + getset( _trainerCalib, "TrainerCalib" ,"" ,"Profiles", groupId()); + getset( _controlTypes, "ControlTypes" ,"" ,"Profiles", groupId()); + getset( _controlNames, "ControlNames" ,"" ,"Profiles", groupId()); + getset( _gsStickMode, "GSStickMode" ,0 ,"Profiles", groupId()); + getset( _ppmMultiplier, "PPM_Multiplier" ,0 ,"Profiles", groupId()); + getset( _vBatWarn, "vBatWarn" ,0 ,"Profiles", groupId()); + getset( _vBatMin, "VbatMin" ,0 ,"Profiles", groupId()); + getset( _vBatMax, "VbatMax" ,0 ,"Profiles", groupId()); + getset( _txCurrentCalibration, "currentCalib" ,0 ,"Profiles", groupId()); + getset( _txVoltageCalibration, "VbatCalib" ,0 ,"Profiles", groupId()); - getset( _beeper, "Beeper" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _countryCode, "countryCode" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _display, "Display" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _haptic, "Haptic" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _speaker, "Speaker" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _stickPotCalib, "StickPotCalib" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _timeStamp, "TimeStamp" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _trainerCalib, "TrainerCalib" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _controlTypes, "ControlTypes" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _controlNames, "ControlNames" ,"" ,"Profiles", QString("profile%1").arg(index)); - getset( _txCurrentCalibration, "currentCalib" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _gsStickMode, "GSStickMode" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _ppmMultiplier, "PPM_Multiplier" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _txVoltageCalibration, "VbatCalib" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _vBatWarn, "vBatWarn" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _vBatMin, "VbatMin" ,0 ,"Profiles", QString("profile%1").arg(index)); - getset( _vBatMax, "VbatMax" ,0 ,"Profiles", QString("profile%1").arg(index)); + getset( _simulatorOptions, "simulatorOptions", SimulatorOptions(), "Profiles", groupId()); + +} + +QString Profile::groupId() +{ + return QString("profile%1").arg(index); } @@ -536,9 +444,6 @@ QString AppData::mcu() { return _mcu; } QString AppData::programmer() { return _programmer; } QString AppData::sambaLocation() { return _sambaLocation; } QString AppData::sambaPort() { return _sambaPort; } -QString AppData::lastSimulator() { return _lastSimulator; } -QString AppData::simuLastEepe() { return _simuLastEepe; } -QString AppData::simuLastFolder() { return _simuLastFolder; } QString AppData::backupDir() { return _backupDir; } QString AppData::gePath() { return _gePath; } @@ -588,9 +493,6 @@ void AppData::mcu (const QString x) { store(x, _mcu, void AppData::programmer (const QString x) { store(x, _programmer, "programmer" );} void AppData::sambaLocation (const QString x) { store(x, _sambaLocation, "samba_location" );} void AppData::sambaPort (const QString x) { store(x, _sambaPort, "samba_port" );} -void AppData::lastSimulator (const QString x) { store(x, _lastSimulator, "last_simulator" );} -void AppData::simuLastEepe (const QString x) { store(x, _simuLastEepe, "simuLastEepe" );} -void AppData::simuLastFolder (const QString x) { store(x, _simuLastFolder, "simuLastFolder" );} void AppData::backupDir (const QString x) { store(x, _backupDir, "backupPath" );} void AppData::gePath (const QString x) { store(x, _gePath, "gePath" );} @@ -630,6 +532,8 @@ AppData::AppData() void AppData::init() { + qRegisterMetaTypeStreamOperators("SimulatorOptions"); + //Initialize the profiles for (int i=0; i AppData::getActiveProfiles() diff --git a/companion/src/storage/appdata.h b/companion/src/storage/appdata.h index 6ac08d5ed..4328a4aa1 100644 --- a/companion/src/storage/appdata.h +++ b/companion/src/storage/appdata.h @@ -30,8 +30,13 @@ #include #include -#define COMPANY "OpenTX" -#define PRODUCT "Companion 2.2" +#include "simulator.h" + +#define COMPANY "OpenTX" +#define COMPANY_DOMAIN "open-tx.org" +#define PRODUCT "Companion 2.2" +#define APP_COMPANION "OpenTX Companion" +#define APP_SIMULATOR "OpenTX Simulator" #define MAX_PROFILES 15 #define MAX_JOYSTICKS 8 @@ -39,26 +44,24 @@ class CompStoreObj { public: - void clear (const QString tag1="", const QString tag2="", const QString tag3=""); - void store(const QByteArray newArray, QByteArray &array, const QString tag, const QString group1="", const QString group2="" ); - void store(const QStringList newSList, QStringList &stringList, const QString tag, const QString group1="", const QString group2="" ); - void store(const QString newString, QString &string, const QString tag, const QString group1="", const QString group2="" ); - void store(const bool newTruth, bool &truth, const QString tag, const QString group1="", const QString group2="" ); - void store(const int newNumber, int &number, const QString tag, const QString group1="", const QString group2="" ); + void clear (const QString & tag1 = "", const QString &tag2 = "", const QString &tag3 = ""); - // Retrieval functions - void retrieve( QByteArray &array, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void retrieve( QStringList &stringList, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void retrieve( QString &string, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void retrieve( bool &truth, const QString tag, const bool def, const QString group1="", const QString group2="" ); - void retrieve( int &number, const QString tag, const int def, const QString group1="", const QString group2="" ); + // NOTE: OBJ_T can only be a standard or registered Qt type. To use custom types, + // either Q_DECLARE_METATYPE() them or add additional logic in retrieve() to handle QMetaType::User. + + template + void store(const OBJ_T newValue, OBJ_T & destValue, const TAG_T tag, const GP1_T group1 = "", const GP2_T group2 = ""); + + template + void retrieve(OBJ_T & destValue, const TAG_T tag, const DEF_T def, const GP1_T group1 = "", const GP2_T group2 = ""); + + template + void getset(OBJ_T & value, const TAG_T tag, const DEF_T def, const GP1_T group1 = "", const GP2_T group2 = "" ); + + // this specialization is needed because QVariant::fromValue(const char *) fails + template + void getset(OBJ_T & value, const TAG_T tag, const char * def, const GP1_T group1 = "", const GP2_T group2 = "" ); - // Retrieve and Store functions - void getset( QByteArray &array, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void getset( QStringList &stringList, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void getset( QString &string, const QString tag, const QString def, const QString group1="", const QString group2="" ); - void getset( bool &truth, const QString tag, const bool def, const QString group1="", const QString group2="" ); - void getset( int &number, const QString tag, const int def, const QString group1="", const QString group2="" ); }; class FwRevision: protected CompStoreObj @@ -122,9 +125,6 @@ class Profile: protected CompStoreObj int _channelOrder; int _defaultMode; - // Simulator variables - QByteArray _simuWinGeo; - // Firmware Variables QString _beeper; QString _countryCode; @@ -136,13 +136,16 @@ class Profile: protected CompStoreObj QString _trainerCalib; QString _controlTypes; QString _controlNames; - int _txCurrentCalibration; int _gsStickMode; int _ppmMultiplier; - int _txVoltageCalibration; int _vBatWarn; int _vBatMin; int _vBatMax; + int _txCurrentCalibration; + int _txVoltageCalibration; + + // Simulator variables + SimulatorOptions _simulatorOptions; public: // All the get definitions @@ -159,8 +162,6 @@ class Profile: protected CompStoreObj int channelOrder() const; int defaultMode() const; - QByteArray simuWinGeo() const; - QString beeper() const; QString countryCode() const; QString display() const; @@ -179,6 +180,8 @@ class Profile: protected CompStoreObj int vBatMin() const; int vBatMax() const; + SimulatorOptions simulatorOptions() const; + // All the set definitions void name (const QString); void fwName (const QString); @@ -193,8 +196,6 @@ class Profile: protected CompStoreObj void channelOrder (const int); void defaultMode (const int); - void simuWinGeo (const QByteArray); - void beeper (const QString); void countryCode (const QString); void display (const QString); @@ -213,6 +214,8 @@ class Profile: protected CompStoreObj void vBatMin (const int); void vBatMax (const int); + void simulatorOptions (const SimulatorOptions &); + Profile(); Profile& operator=(const Profile&); void remove(); @@ -220,6 +223,7 @@ class Profile: protected CompStoreObj void init(int newIndex); void initFwVariables(); void flush(); + QString groupId(); }; #define BOOL_PROPERTY(name, dflt) \ @@ -231,12 +235,12 @@ class Profile: protected CompStoreObj class AppData: protected CompStoreObj { - BOOL_PROPERTY(enableBackup, false) - BOOL_PROPERTY(outputDisplayDetails, false) - BOOL_PROPERTY(backupOnFlash, true) + BOOL_PROPERTY(enableBackup, false) + BOOL_PROPERTY(backupOnFlash, true) + BOOL_PROPERTY(outputDisplayDetails, false) BOOL_PROPERTY(checkHardwareCompatibility, true) - BOOL_PROPERTY(useCompanionNightlyBuilds, false) - BOOL_PROPERTY(useFirmwareNightlyBuilds, false) + BOOL_PROPERTY(useCompanionNightlyBuilds, false) + BOOL_PROPERTY(useFirmwareNightlyBuilds, false) // All the global variables public: @@ -261,9 +265,6 @@ class AppData: protected CompStoreObj QString _programmer; QString _sambaLocation; QString _sambaPort; - QString _lastSimulator; - QString _simuLastEepe; - QString _simuLastFolder; QString _backupDir; QString _gePath; @@ -315,9 +316,6 @@ class AppData: protected CompStoreObj QString programmer(); QString sambaLocation(); QString sambaPort(); - QString lastSimulator(); - QString simuLastEepe(); - QString simuLastFolder(); QString backupDir(); QString gePath(); @@ -368,9 +366,6 @@ class AppData: protected CompStoreObj void programmer (const QString); void sambaLocation (const QString); void sambaPort (const QString); - void lastSimulator (const QString); - void simuLastEepe (const QString); - void simuLastFolder (const QString); void backupDir (const QString); void gePath (const QString);