From 2f9d9c1a99e1270c881d1d790e93220a4d20270d Mon Sep 17 00:00:00 2001 From: Max Paperno Date: Sat, 4 Feb 2017 05:59:42 -0500 Subject: [PATCH] [Simulator] New main UI window, debug console, separate outputs window, more. (#4385) * [package] Add QtSvg support DLLs for Windows. * [Simulator] Add images/css resources for new version; Create SimulatorIcon and SimulatorStyle classes; Build with SVG support, remove unused bitmaps. * [Simulator] Improve and simplify VirtualJoystickWidget resize event (sticks maintain X/Y position) & improve values layout w/out trims. * [Simulator] Refactor Trainer simulator to use VirtualJoystickWidget, gains lock/hold buttons and X/Y position readout. * [Simulator] More compact layout for Telemetry simulator, reduces min. width and adds scrolling columns to allow smaller window sizes. * [Simulator] Add new SimulatorMainWindow and RadioOutputsWidget UI classes. * [storage] Add non-persistent sessionId() to AppData for tracking currently active radio profile ID. * [Simulator] Major UI updates: * Use a MainWindow interface with dockable/floatable windows, proper toolbar & menu; * Persistent size/layout state of all windows/docks/etc between uses, per radio profile; * Completely separate radio Outputs window with configurable layout (also persistent between uses); * Translations now enabled in standalone Simulator, follows main Companion setting (they need a lot of updates); * New custom icons throughout, toolbar icon sizes adhere to user preference in Companion. * [Simulator] Debug Output console updates: * New filter feature with optional full RegEx support, can work in inclusive or exclusive modes (full help text is included in UI); * Up to 50 user-defined filters are saved to permanent settings, also a few predefined filters are provided; * Saves/restores the last filter used and if it was en/disabled; * Added word wrap and clear screen features; * Configurable scroll-back buffer size; * Increased efficiency & performance of the buffering/printing process. * [Simulator][OSX] Visual UI tweaks for OS X. * [Simulator] Fix some debug console settings not being restored properly. * [Simulator] Add ability to hide/show menu bar; Persist view state of menu bar and radio title bar; Reduce font size on OS X; Other small visual tweaks and improvements. * [build] Add QtSvg to Linux build scripts (a guess for Dockerfile). * [build] Fix for Qt 5.3. * Add OpenTx headers to new files. * [Companion][storage] Update GeneralSettings() initializer to use current sessionId for profile data retrieval, removes workaround for standalone Simulator being started with arbitrary profile; AppData: always update sessionId when global id() changes. * [X10] Fixes for new simulator --- .travis.yml | 2 +- companion/src/CMakeLists.txt | 8 +- companion/src/companion.qrc | 24 +- companion/src/eeprominterface.cpp | 28 +- companion/src/helpers.cpp | 17 +- .../images/simulator/icons/8/lock-locked.png | Bin 152 -> 0 bytes .../simulator/icons/8/lock-unlocked.png | Bin 154 -> 0 bytes .../simulator/icons/svg/camera-active.svg | 1 + .../src/images/simulator/icons/svg/camera.svg | 1 + .../images/simulator/icons/svg/console.svg | 10 + .../simulator/icons/svg/eraser-active.svg | 12 + .../src/images/simulator/icons/svg/eraser.svg | 22 + .../simulator/icons/svg/info-active.svg | 15 + .../src/images/simulator/icons/svg/info.svg | 23 + .../icons/svg/joystick_settings-active.svg | 18 + .../simulator/icons/svg/joystick_settings.svg | 18 + .../simulator/icons/svg/radio_outputs.svg | 33 + .../icons/svg/reload_script-active.svg | 13 + .../simulator/icons/svg/reload_script.svg | 21 + .../simulator/icons/svg/restart-active.svg | 13 + .../images/simulator/icons/svg/restart.svg | 21 + .../images/simulator/icons/svg/telemetry.svg | 22 + .../simulator/icons/svg/toggle_lock-on.svg | 3 + .../simulator/icons/svg/toggle_lock.svg | 3 + .../images/simulator/icons/svg/trainer.svg | 35 + .../simulator/icons/svg/word_wrap-active.svg | 11 + .../images/simulator/icons/svg/word_wrap.svg | 20 + companion/src/simulation/CMakeLists.txt | 10 +- companion/src/simulation/debugoutput.cpp | 357 +- companion/src/simulation/debugoutput.h | 60 +- companion/src/simulation/debugoutput.ui | 310 +- .../src/simulation/radiooutputswidget.cpp | 365 ++ companion/src/simulation/radiooutputswidget.h | 87 + .../src/simulation/radiooutputswidget.ui | 440 ++ companion/src/simulation/radiouiaction.h | 5 +- .../src/simulation/simulateduiwidget.cpp | 12 +- companion/src/simulation/simulateduiwidget.h | 3 +- companion/src/simulation/simulator.h | 87 +- companion/src/simulation/simulatordialog.cpp | 561 +- companion/src/simulation/simulatordialog.h | 49 +- companion/src/simulation/simulatordialog.ui | 571 +- .../src/simulation/simulatormainwindow.cpp | 350 ++ .../src/simulation/simulatormainwindow.h | 99 + .../src/simulation/simulatormainwindow.ui | 183 + .../src/simulation/simulatorstartupdialog.ui | 71 +- companion/src/simulation/telemetrysimu.cpp | 25 +- companion/src/simulation/telemetrysimu.h | 250 +- companion/src/simulation/telemetrysimu.ui | 4600 +++++++++++------ companion/src/simulation/trainersimu.cpp | 113 +- companion/src/simulation/trainersimu.h | 26 +- companion/src/simulation/trainersimu.ui | 124 +- .../src/simulation/widgets/radiofaderwidget.h | 2 +- .../simulation/widgets/radioswitchwidget.h | 10 +- .../widgets/virtualjoystickwidget.cpp | 75 +- .../widgets/virtualjoystickwidget.h | 21 +- companion/src/simulator.cpp | 51 +- companion/src/storage/appdata.cpp | 20 +- companion/src/storage/appdata.h | 10 + companion/src/themes/default/style-osx.css | 31 + companion/src/themes/default/style.css | 77 + companion/targets/windows/companion-vs.nsi.in | 2 + radio/util/Dockerfile | 1 + 62 files changed, 6357 insertions(+), 3095 deletions(-) delete mode 100644 companion/src/images/simulator/icons/8/lock-locked.png delete mode 100644 companion/src/images/simulator/icons/8/lock-unlocked.png create mode 100644 companion/src/images/simulator/icons/svg/camera-active.svg create mode 100644 companion/src/images/simulator/icons/svg/camera.svg create mode 100644 companion/src/images/simulator/icons/svg/console.svg create mode 100644 companion/src/images/simulator/icons/svg/eraser-active.svg create mode 100644 companion/src/images/simulator/icons/svg/eraser.svg create mode 100644 companion/src/images/simulator/icons/svg/info-active.svg create mode 100644 companion/src/images/simulator/icons/svg/info.svg create mode 100644 companion/src/images/simulator/icons/svg/joystick_settings-active.svg create mode 100644 companion/src/images/simulator/icons/svg/joystick_settings.svg create mode 100644 companion/src/images/simulator/icons/svg/radio_outputs.svg create mode 100644 companion/src/images/simulator/icons/svg/reload_script-active.svg create mode 100644 companion/src/images/simulator/icons/svg/reload_script.svg create mode 100644 companion/src/images/simulator/icons/svg/restart-active.svg create mode 100644 companion/src/images/simulator/icons/svg/restart.svg create mode 100644 companion/src/images/simulator/icons/svg/telemetry.svg create mode 100644 companion/src/images/simulator/icons/svg/toggle_lock-on.svg create mode 100644 companion/src/images/simulator/icons/svg/toggle_lock.svg create mode 100644 companion/src/images/simulator/icons/svg/trainer.svg create mode 100644 companion/src/images/simulator/icons/svg/word_wrap-active.svg create mode 100644 companion/src/images/simulator/icons/svg/word_wrap.svg create mode 100644 companion/src/simulation/radiooutputswidget.cpp create mode 100644 companion/src/simulation/radiooutputswidget.h create mode 100644 companion/src/simulation/radiooutputswidget.ui create mode 100644 companion/src/simulation/simulatormainwindow.cpp create mode 100644 companion/src/simulation/simulatormainwindow.h create mode 100644 companion/src/simulation/simulatormainwindow.ui create mode 100644 companion/src/themes/default/style-osx.css create mode 100644 companion/src/themes/default/style.css diff --git a/.travis.yml b/.travis.yml index f36cb420d..b733d57fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ install: # - tar xjf gcc-arm-none-eabi-4_7-2013q3-20130916-linux.tar.bz2 # - mv gcc-arm-none-eabi-4_7-2013q3 /opt/gcc-arm-none-eabi fi - - sudo apt-get install -qq qt${QT_BASE}base qt${QT_BASE}multimedia qt${QT_BASE}tools; source /opt/qt${QT_BASE}/bin/qt${QT_BASE}-env.sh + - sudo apt-get install -qq qt${QT_BASE}base qt${QT_BASE}multimedia qt${QT_BASE}svg qt${QT_BASE}tools; source /opt/qt${QT_BASE}/bin/qt${QT_BASE}-env.sh script: - ./tools/commit-tests.sh diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index f4a1a8297..ebc6ed137 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -49,8 +49,6 @@ endif() if(SDL_FOUND) include_directories(${SDL_INCLUDE_DIR}) add_definitions(-DJOYSTICKS) -else() - remove_definitions(-DSIMU_AUDIO) endif() add_definitions(-DSIMU) @@ -250,8 +248,6 @@ qt5_add_translation(companion_QM ${companion_TS}) qt5_add_resources(companion_RCC ${companion_RESOURCES}) add_custom_target(gen_qrc DEPENDS ${companion_RCC}) -add_definitions(-DQT_TRANSLATIONS_DIR="${QT_TRANSLATIONS_DIR}") - add_executable(${COMPANION_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE} ${companion_SRCS} ${companion_RCC} ${companion_QM}) add_dependencies(${COMPANION_NAME} gen_qrc) @@ -282,9 +278,7 @@ if(WIN32) endif() 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) target_link_libraries(${SIMULATOR_NAME} PRIVATE simulation common shared storage qxtcommandoptions ${PTHREAD_LIBRARY} ${SDL_LIBRARY} ${WIN_LINK_LIBRARIES}) @@ -343,7 +337,7 @@ elseif(WIN32) get_target_property(QtCore_LOCATION Qt5::Core LOCATION) get_filename_component(QT_DLL_DIR ${QtCore_LOCATION} PATH) - set(INSTALL_TEMP_QTDLL_FILES Qt5Core Qt5Gui Qt5Widgets Qt5Xml Qt5Network Qt5PrintSupport Qt5Multimedia) + set(INSTALL_TEMP_QTDLL_FILES Qt5Core Qt5Gui Qt5Widgets Qt5Xml Qt5Network Qt5PrintSupport Qt5Multimedia Qt5Svg) set(INSTALL_TEMP_ICUDLL_FILES icudt54.dll icuin54.dll icuuc54.dll) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(INSTALL_TEMP_QTDLL_SFX "d") diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index 79e040620..848007feb 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -12,8 +12,26 @@ images/track.png images/track0.png images/taranison.png - images/simulator/icons/8/lock-locked.png - images/simulator/icons/8/lock-unlocked.png + images/simulator/icons/svg/camera.svg + images/simulator/icons/svg/camera-active.svg + images/simulator/icons/svg/console.svg + images/simulator/icons/svg/eraser.svg + images/simulator/icons/svg/eraser-active.svg + images/simulator/icons/svg/info.svg + images/simulator/icons/svg/info-active.svg + images/simulator/icons/svg/joystick_settings.svg + images/simulator/icons/svg/joystick_settings-active.svg + images/simulator/icons/svg/toggle_lock.svg + images/simulator/icons/svg/toggle_lock-on.svg + images/simulator/icons/svg/radio_outputs.svg + images/simulator/icons/svg/reload_script.svg + images/simulator/icons/svg/reload_script-active.svg + images/simulator/icons/svg/restart.svg + images/simulator/icons/svg/restart-active.svg + images/simulator/icons/svg/telemetry.svg + images/simulator/icons/svg/trainer.svg + images/simulator/icons/svg/word_wrap.svg + images/simulator/icons/svg/word_wrap-active.svg images/simulator/Horus/middle.png images/simulator/Horus/left.png images/simulator/Horus/right.png @@ -180,6 +198,8 @@ images/library/10702.png images/library/10801.png images/library/10802.png + themes/default/style.css + themes/default/style-osx.css themes/monochrome/16/paintbrush.png themes/monochrome/16/open.png themes/monochrome/16/edit.png diff --git a/companion/src/eeprominterface.cpp b/companion/src/eeprominterface.cpp index 71328b4f3..2addd91d4 100644 --- a/companion/src/eeprominterface.cpp +++ b/companion/src/eeprominterface.cpp @@ -1120,23 +1120,23 @@ GeneralSettings::GeneralSettings() strcpy(bluetoothName, "Taranis"); } - templateSetup = g.profile[g.id()].channelOrder(); - stickMode = g.profile[g.id()].defaultMode(); + templateSetup = g.profile[g.sessionId()].channelOrder(); + stickMode = g.profile[g.sessionId()].defaultMode(); - QString t_calib = g.profile[g.id()].stickPotCalib(); + QString t_calib = g.profile[g.sessionId()].stickPotCalib(); int potsnum = getCurrentFirmware()->getCapability(Pots); if (!t_calib.isEmpty()) { - QString t_trainercalib=g.profile[g.id()].trainerCalib(); - int8_t t_txVoltageCalibration=(int8_t)g.profile[g.id()].txVoltageCalibration(); - int8_t t_txCurrentCalibration=(int8_t)g.profile[g.id()].txCurrentCalibration(); - int8_t t_PPM_Multiplier=(int8_t)g.profile[g.id()].ppmMultiplier(); - uint8_t t_stickMode=(uint8_t)g.profile[g.id()].gsStickMode(); - uint8_t t_vBatWarn=(uint8_t)g.profile[g.id()].vBatWarn(); - QString t_DisplaySet=g.profile[g.id()].display(); - QString t_BeeperSet=g.profile[g.id()].beeper(); - QString t_HapticSet=g.profile[g.id()].haptic(); - QString t_SpeakerSet=g.profile[g.id()].speaker(); - QString t_CountrySet=g.profile[g.id()].countryCode(); + QString t_trainercalib=g.profile[g.sessionId()].trainerCalib(); + int8_t t_txVoltageCalibration=(int8_t)g.profile[g.sessionId()].txVoltageCalibration(); + int8_t t_txCurrentCalibration=(int8_t)g.profile[g.sessionId()].txCurrentCalibration(); + int8_t t_PPM_Multiplier=(int8_t)g.profile[g.sessionId()].ppmMultiplier(); + uint8_t t_stickMode=(uint8_t)g.profile[g.sessionId()].gsStickMode(); + uint8_t t_vBatWarn=(uint8_t)g.profile[g.sessionId()].vBatWarn(); + QString t_DisplaySet=g.profile[g.sessionId()].display(); + QString t_BeeperSet=g.profile[g.sessionId()].beeper(); + QString t_HapticSet=g.profile[g.sessionId()].haptic(); + QString t_SpeakerSet=g.profile[g.sessionId()].speaker(); + QString t_CountrySet=g.profile[g.sessionId()].countryCode(); if ((t_calib.length()==(CPN_MAX_STICKS+potsnum)*12) && (t_trainercalib.length()==16)) { QString Byte; diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index ca616a221..82495c51f 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -32,7 +32,7 @@ #include "appdata.h" #include "helpers.h" #include "modeledit/modeledit.h" -#include "simulatordialog.h" +#include "simulatormainwindow.h" #include "storage/sdcard.h" Stopwatch gStopwatch("global"); @@ -823,7 +823,7 @@ void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) simuData->setCurrentModel(modelIdx); } - SimulatorDialog * dialog = new SimulatorDialog(parent, simulator, flags); + SimulatorMainWindow * dialog = new SimulatorMainWindow(parent, simulator, flags); 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())); delete dialog; @@ -831,12 +831,15 @@ void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) return; } dialog->setRadioData(simuData); - qDebug() << __FILE__ << __LINE__ << "Starting" << getCurrentFirmware()->getName() << "simulation with SD path" << dialog->getSdPath() << "and models/settings path" << dialog->getDataPath(); + dialog->setWindowModality(Qt::ApplicationModal); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->start(); - dialog->exec(); - delete dialog; - // 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 + dialog->show(); + + QObject::connect(dialog, &SimulatorMainWindow::destroyed, [simuData] (void) { + // TODO simuData and Horus tmp directory is deleted on simulator close OR we could use it to get back data from the simulation + delete simuData; + }); } else { QMessageBox::warning(NULL, diff --git a/companion/src/images/simulator/icons/8/lock-locked.png b/companion/src/images/simulator/icons/8/lock-locked.png deleted file mode 100644 index fef8198a36b42c41d05ce65c4106a57a0194549e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ za0`PlBg3pY54nJ za0`PlBg3pY5 \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/camera.svg b/companion/src/images/simulator/icons/svg/camera.svg new file mode 100644 index 000000000..24e159004 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/console.svg b/companion/src/images/simulator/icons/svg/console.svg new file mode 100644 index 000000000..9ce925a6e --- /dev/null +++ b/companion/src/images/simulator/icons/svg/console.svg @@ -0,0 +1,10 @@ + + + +image/svg+xml + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/eraser-active.svg b/companion/src/images/simulator/icons/svg/eraser-active.svg new file mode 100644 index 000000000..8b529097c --- /dev/null +++ b/companion/src/images/simulator/icons/svg/eraser-active.svg @@ -0,0 +1,12 @@ + + + +image/svg+xml + + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/eraser.svg b/companion/src/images/simulator/icons/svg/eraser.svg new file mode 100644 index 000000000..f7fe40f63 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/eraser.svg @@ -0,0 +1,22 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/info-active.svg b/companion/src/images/simulator/icons/svg/info-active.svg new file mode 100644 index 000000000..142f05adf --- /dev/null +++ b/companion/src/images/simulator/icons/svg/info-active.svg @@ -0,0 +1,15 @@ + + + +image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/info.svg b/companion/src/images/simulator/icons/svg/info.svg new file mode 100644 index 000000000..85f9f6e73 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/info.svg @@ -0,0 +1,23 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/joystick_settings-active.svg b/companion/src/images/simulator/icons/svg/joystick_settings-active.svg new file mode 100644 index 000000000..504b8d0a8 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/joystick_settings-active.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/joystick_settings.svg b/companion/src/images/simulator/icons/svg/joystick_settings.svg new file mode 100644 index 000000000..709113b4f --- /dev/null +++ b/companion/src/images/simulator/icons/svg/joystick_settings.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/radio_outputs.svg b/companion/src/images/simulator/icons/svg/radio_outputs.svg new file mode 100644 index 000000000..bba407aea --- /dev/null +++ b/companion/src/images/simulator/icons/svg/radio_outputs.svg @@ -0,0 +1,33 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/reload_script-active.svg b/companion/src/images/simulator/icons/svg/reload_script-active.svg new file mode 100644 index 000000000..cc8576d64 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/reload_script-active.svg @@ -0,0 +1,13 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/reload_script.svg b/companion/src/images/simulator/icons/svg/reload_script.svg new file mode 100644 index 000000000..c561fc4b0 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/reload_script.svg @@ -0,0 +1,21 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/restart-active.svg b/companion/src/images/simulator/icons/svg/restart-active.svg new file mode 100644 index 000000000..031008c58 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/restart-active.svg @@ -0,0 +1,13 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/restart.svg b/companion/src/images/simulator/icons/svg/restart.svg new file mode 100644 index 000000000..95ee5b5ca --- /dev/null +++ b/companion/src/images/simulator/icons/svg/restart.svg @@ -0,0 +1,21 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/telemetry.svg b/companion/src/images/simulator/icons/svg/telemetry.svg new file mode 100644 index 000000000..6adfbeb07 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/telemetry.svg @@ -0,0 +1,22 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/toggle_lock-on.svg b/companion/src/images/simulator/icons/svg/toggle_lock-on.svg new file mode 100644 index 000000000..ef54f3aa2 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/toggle_lock-on.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/toggle_lock.svg b/companion/src/images/simulator/icons/svg/toggle_lock.svg new file mode 100644 index 000000000..7f78a6f38 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/toggle_lock.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/trainer.svg b/companion/src/images/simulator/icons/svg/trainer.svg new file mode 100644 index 000000000..3bb8e4d70 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/trainer.svg @@ -0,0 +1,35 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/companion/src/images/simulator/icons/svg/word_wrap-active.svg b/companion/src/images/simulator/icons/svg/word_wrap-active.svg new file mode 100644 index 000000000..4a5fffbe2 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/word_wrap-active.svg @@ -0,0 +1,11 @@ + + + +image/svg+xml + + + + + + + \ No newline at end of file diff --git a/companion/src/images/simulator/icons/svg/word_wrap.svg b/companion/src/images/simulator/icons/svg/word_wrap.svg new file mode 100644 index 000000000..a6abd2061 --- /dev/null +++ b/companion/src/images/simulator/icons/svg/word_wrap.svg @@ -0,0 +1,20 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index 6514b6283..eddba8ea6 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -1,12 +1,14 @@ set(simulation_SRCS debugoutput.cpp + radiooutputswidget.cpp simulatordialog.cpp - simulatorinterface.cpp simulateduiwidget.cpp simulateduiwidget9X.cpp simulateduiwidgetX7.cpp simulateduiwidgetX9.cpp simulateduiwidgetX12.cpp + simulatorinterface.cpp + simulatormainwindow.cpp simulatorstartupdialog.cpp telemetrysimu.cpp trainersimu.cpp @@ -16,11 +18,13 @@ set(simulation_SRCS set(simulation_UIS debugoutput.ui + radiooutputswidget.ui simulatordialog.ui simulateduiwidget9X.ui simulateduiwidgetX7.ui simulateduiwidgetX9.ui simulateduiwidgetX12.ui + simulatormainwindow.ui simulatorstartupdialog.ui telemetrysimu.ui trainersimu.ui @@ -28,10 +32,12 @@ set(simulation_UIS set(simulation_HDRS debugoutput.h + radiooutputswidget.h radiouiaction.h simulateduiwidget.h # simulator.h simulatordialog.h + simulatormainwindow.h simulatorstartupdialog.h telemetrysimu.h trainersimu.h @@ -77,4 +83,4 @@ qt5_wrap_ui(simulation_SRCS ${simulation_UIS}) qt5_wrap_cpp(simulation_SRCS ${simulation_HDRS}) add_library(simulation ${simulation_SRCS} ${simulation_HDRS}) -qt5_use_modules(simulation Widgets Xml) +qt5_use_modules(simulation Core Widgets Svg) diff --git a/companion/src/simulation/debugoutput.cpp b/companion/src/simulation/debugoutput.cpp index 8cfb3d52a..f894767df 100644 --- a/companion/src/simulation/debugoutput.cpp +++ b/companion/src/simulation/debugoutput.cpp @@ -21,40 +21,361 @@ #include "debugoutput.h" #include "ui_debugoutput.h" -DebugOutput::DebugOutput(QWidget * parent): - QDialog(parent), - ui(new Ui::DebugOutput) +#include "appdata.h" + +#include +#include +#include + +#define DEBUG_OUTPUT_STATE_VERSION 1 + +extern AppData g; // ensure what "g" means + +DebugOutput * traceCallbackInstance = 0; +const int DebugOutput::m_dataBufferMaxSize = 100; // lines of text (this is not the display buffer) +const int DebugOutput::m_dataPrintFreqDefault = 10; // ms +const quint16 DebugOutput::m_savedViewStateVersion = 1; + +void traceCb(const char * text) +{ + // divert C callback into simulator instance + if (traceCallbackInstance) { + traceCallbackInstance->traceCallback(text); + } +} + +DebugOutput::DebugOutput(QWidget * parent, SimulatorInterface *simulator): + QWidget(parent), + ui(new Ui::DebugOutput), + m_simulator(simulator), + m_tmrDataPrint(new QTimer()), + m_dataBuffer(QByteArray()), + m_radioProfileId(g.sessionId()), + m_dataPrintFreq(m_dataPrintFreqDefault), + m_running(false), + m_filterExclude(true) { ui->setupUi(this); #ifdef __APPLE__ QFont newFont("Courier", 13); - ui->Output->setFont(newFont); - ui->Output->setAttribute(Qt::WA_MacNormalSize); -#endif -#if defined WIN32 || !defined __GNUC__ - QFont newFont("Courier", 9); - ui->Output->setFont(newFont); + ui->console->setFont(newFont); #endif + + // TODO : allow selecting multiple filters, but needs to be efficient at output stage + + QStringList stockFilters; + stockFilters << "^lua[A-Z].*"; + stockFilters << "/(error|warning|-(E|W)-)/i"; + stockFilters << "!^(GC Use|(play|load|write|find(True)?)File|convert(To|From)Simu|\\tfound( in map)?:|eeprom |f_[a-z]+\\(|(push|(p|P)op(up)?|chain)? ?Menu( .+ display)?|RamBackup).+$"; + + foreach (const QString & fltr, stockFilters) + ui->filterText->addItem(fltr, "no_delete"); + + ui->filterText->setValidator(new DebugOutputFilterValidator(ui->filterText)); + ui->filterText->installEventFilter(new DeleteComboBoxItemEventFilter()); + + ui->actionShowFilterHelp->setIcon(SimulatorIcon("info")); + ui->actionWordWrap->setIcon(SimulatorIcon("word_wrap")); + ui->actionClearScr->setIcon(SimulatorIcon("eraser")); + + ui->btnShowFilterHelp->setDefaultAction(ui->actionShowFilterHelp); + ui->btnWordWrap->setDefaultAction(ui->actionWordWrap); + ui->btnClearScr->setDefaultAction(ui->actionClearScr); + + restoreState(); + + ui->bufferSize->setValue(ui->console->maximumBlockCount()); + + // install simulator TRACE hook + traceCallbackInstance = this; + m_simulator->installTraceHook(traceCb); + + m_tmrDataPrint->setInterval(m_dataPrintFreq); + + connect(ui->filterText, &QComboBox::currentTextChanged, this, &DebugOutput::onFilterTextChanged); + connect(m_tmrDataPrint, &QTimer::timeout, this, &DebugOutput::processBytesReceived); } DebugOutput::~DebugOutput() { + traceCallbackInstance = 0; + stop(); + saveState(); + if (m_tmrDataPrint) + delete m_tmrDataPrint; delete ui; } -void DebugOutput::traceCallback(const QString & text) +void DebugOutput::start() { - // ui->Output->appendPlainText(text); - QTextCursor cursor(ui->Output->textCursor()); + m_tmrDataPrint->start(); + m_running = true; +} - // is the scrollbar at the end? - bool atEnd = (ui->Output->verticalScrollBar()->value() == ui->Output->verticalScrollBar()->maximum()); +void DebugOutput::stop() +{ + m_tmrDataPrint->stop(); + m_running = false; +} - cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor, 1); - cursor.insertText(text); +void DebugOutput::saveState() +{ + QStringList filters; + for (int i = 0; i < ui->filterText->count(); ++i) { + if (!ui->filterText->itemText(i).isEmpty() && ui->filterText->itemData(i).toString() != "no_delete") + filters << ui->filterText->itemText(i); + } + g.simuDbgFilters(filters); - if (atEnd) { - ui->Output->verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum); + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << m_savedViewStateVersion + << (qint16)ui->filterText->currentIndex() << (qint32)ui->console->maximumBlockCount() + << ui->btnFilter->isChecked() << ui->actionWordWrap->isChecked(); + + SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions(); + opts.dbgConsoleState = state; + g.profile[m_radioProfileId].simulatorOptions(opts); +} + +void DebugOutput::restoreState() +{ + quint16 ver = 0; + qint16 fci = -1; + qint32 mbc = 10000; + bool flten = false, wwen = false; + QByteArray state = g.profile[m_radioProfileId].simulatorOptions().dbgConsoleState; + QDataStream stream(state); + + stream >> ver; + if (ver && ver <= m_savedViewStateVersion) + stream >> fci >> mbc >> flten >> wwen; + + ui->filterText->insertItems(0, g.simuDbgFilters()); + ui->filterText->setCurrentIndex(fci); + ui->btnFilter->setChecked(flten); + ui->console->setMaximumBlockCount(mbc); + ui->actionWordWrap->setChecked(wwen); + onFilterTextEdited(); +} + +void DebugOutput::traceCallback(const char * text) +{ + const static QRegExp blank("^[\\r\\n]+$"); + bool isBlank; + + if (!m_running) + return; + + QString line(text); + isBlank = line.contains(blank); + + m_mtxDataBuffer.lock(); + if (isBlank && m_dataBuffer.size()) + m_dataBuffer[m_dataBuffer.size()-1] += line; + else + m_dataBuffer.append(text); + if (m_dataBuffer.size() > m_dataBufferMaxSize) { + m_dataBuffer.removeFirst(); + qDebug() << __FILE__ << __LINE__ << "Line buffer overflow! size >" << m_dataBufferMaxSize; + } + m_mtxDataBuffer.unlock(); +} + +void DebugOutput::processBytesReceived() +{ + QString text; + bool fltMatch; + const QTextCursor savedCursor(ui->console->textCursor()); + const int sbValue = ui->console->verticalScrollBar()->value(); + const bool sbAtBottom = (sbValue == ui->console->verticalScrollBar()->maximum()); + + m_tmrDataPrint->stop(); + while (m_dataBuffer.size() > 1) { + m_mtxDataBuffer.lock(); + text = m_dataBuffer.takeFirst(); + m_mtxDataBuffer.unlock(); + // filter + if (ui->btnFilter->isChecked()) { + fltMatch = text.contains(m_filterRegEx); + if ((m_filterExclude && fltMatch) || (!m_filterExclude && !fltMatch)) { + continue; + } + } + ui->console->moveCursor(QTextCursor::End); + ui->console->textCursor().insertText(text); + if (sbAtBottom) { + ui->console->moveCursor(QTextCursor::End); + ui->console->verticalScrollBar()->setValue(ui->console->verticalScrollBar()->maximum()); + } + else { + ui->console->setTextCursor(savedCursor); + ui->console->verticalScrollBar()->setValue(sbValue); + } + QCoreApplication::processEvents(); + } + m_tmrDataPrint->start(); +} + +/* + * UI handlers + */ + +void DebugOutput::onFilterTextEdited() +{ + const QString fText = ui->filterText->currentText(); + if (fText.isEmpty()) { + ui->btnFilter->setChecked(false); + m_filterRegEx = QRegularExpression(); + return; + } + + m_filterRegEx = makeRegEx(fText, &m_filterExclude); + + if (m_filterRegEx.isValid()) { + //ui->btnFilter->setChecked(true); + ui->filterText->setStyleSheet(""); + } + else { + ui->btnFilter->setChecked(false); + m_filterRegEx = QRegularExpression(); + ui->filterText->setStyleSheet("background-color: rgba(255, 205, 185, 200);"); } } + +void DebugOutput::onFilterTextChanged(const QString &) +{ + onFilterTextEdited(); +} + +void DebugOutput::on_bufferSize_editingFinished() +{ + ui->console->setMaximumBlockCount(ui->bufferSize->value()); +} + +void DebugOutput::on_actionWordWrap_toggled(bool checked) +{ + ui->console->setLineWrapMode(checked ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap); +} + +void DebugOutput::on_actionClearScr_triggered() +{ + ui->console->clear(); +} + +void DebugOutput::on_actionShowFilterHelp_triggered() +{ + // TODO : find some place better for this. + QString help = \ + "" + "

The filter supports two syntax types: basic matching with common wildcards and well as full Perl-style (pcre) Regular Expressions.

" + "

By default a filter will only show lines which match (inclusive). To make an exclusive filter which removes matching lines, " + "prefix the filter expression with a ! (exclamation mark).

" + "

To use Regular Expressions (RegEx), prefix the filter text with a / (slash) or ^ (up caret). " + "

    " + "
  • Put the / or ^ after the exclusive ! indicator if you're using one.
  • " + "
  • By default the match is case-sensitive. To make it insensitive, add the typical /i (slash i) operator at the end of your RegEx.
  • " + "
  • If you use a caret (^) to denote a RegEx, it will become part of the Reg. Ex. (that is, matches from start of line).
  • " + "
  • If the RegEx is invalid, the filter edit field should show a red border and you will not be able to enable the filter.
  • " + "
  • A useful resource for testing REs (with a full reference) can be found at http://www.regexr.com/
  • " + "

" + "

To use basic matching just type any text. Wildcards * (asterisk) matches any text and ? (question mark) matches any single character." + "

    " + "
  • The match is always case-insensitive.
  • " + "
  • The match always starts from the beginning of a log line. To ignore characters at the start, use a leading * wildcard.
  • " + "
  • A trailing * is always implied (that is, matches anything to the end of the log line). To avoid this, use a RegEx.
  • " + "
  • You can match literal wildcard characters by prefixing them with a \\ (backslash) character (eg. \"foo\\*bar\" matches \"foo*bar\").
  • " + "

" + "

After editing text, press ENTER or TAB key (or click anywhere outside the box) to update the filter.

" + "

To remove an entry from the filter selector list, first choose it, and while in the line editor press Shift-Delete (or Shift-Backspace) key combination. " + "The default filters cannot be removed. Up to 50 filters are stored.

" + "" + ; + QMessageBox * msgbox = new QMessageBox(QMessageBox::NoIcon, tr("Debug Console Filter Help"), help, QMessageBox::Ok, this); + msgbox->exec(); +} + + +// static +QRegularExpression DebugOutput::makeRegEx(const QString & input, bool * isExlusive) +{ + QString output(input); + QRegularExpression re; + QRegularExpression::PatternOptions reFlags = QRegularExpression::DontCaptureOption; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) + reFlags |= QRegularExpression::OptimizeOnFirstUsageOption; +#endif + + if (input.left(1) == "!") { + output.remove(0, 1); + if (isExlusive) + *isExlusive = true; + } + else if (isExlusive) { + *isExlusive = false; + } + // regex? + if (output.left(1) == "/" || output.left(1) == "^") { + if (output.left(1) == "/") + output.remove(0, 1); + // check for case-insensitive flag at end ("/.../i") + if (output.endsWith("/i")) { + output.chop(2); + reFlags |= QRegularExpression::CaseInsensitiveOption; + } + else if (output.endsWith("/")) { + output.chop(1); + } + re.setPattern(output); + } + // no, convert arbitrary string to regex + else { + output.replace(QRegExp("^\\\\/"), "/"); // remove escape before fwd-slash ("\/...") + // escape all special chars except * and ? + output.replace(QRegExp("(\\\\|\\.|\\+|\\^|\\$|\\||\\)|\\(|\\]|\\[|\\}|\\{)"), "\\\\1"); + output.replace("\\*", "\x30").replace("\\?", "\x31"); // save escaped wildcard chars + output.replace("*", ".*").replace("?", "."); // convert common wildcards + output.replace("\x30", "\\\\*").replace("\x31", "\\\\?"); // replace escaped wildcard chars + output.prepend("^"); // match from start of line; .append("$"); // match whole line + reFlags |= QRegularExpression::CaseInsensitiveOption; + re.setPattern(output); + } + // TODO : user option? + re.setPatternOptions(reFlags); + return re; +} + + +/* + * Filter input validator for RegEx syntax. + */ + +QValidator::State DebugOutputFilterValidator::validate(QString & input, int &) const +{ + QRegularExpression re = DebugOutput::makeRegEx(input); + if (re.isValid()) + return QValidator::Acceptable; + else + return QValidator::Intermediate; +} + +/* + * Event filter for editable QComboBox to allow deleting items with Shift-Delete/Backspace + */ + +bool DeleteComboBoxItemEventFilter::eventFilter(QObject *obj, QEvent *event) +{ + QComboBox * cb = dynamic_cast(obj); + if (cb && cb->isEditable() && cb->currentIndex() > -1 && cb->currentData().toString() != "no_delete") { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if ((keyEvent->key() == Qt::Key::Key_Delete || keyEvent->key() == Qt::Key::Key_Backspace) && keyEvent->modifiers() == Qt::ShiftModifier) { + cb->removeItem(cb->currentIndex()); + cb->setCurrentIndex(-1); + return true; + } + } + } + return QObject::eventFilter(obj, event); +} diff --git a/companion/src/simulation/debugoutput.h b/companion/src/simulation/debugoutput.h index ec9745162..5e60c9ac4 100644 --- a/companion/src/simulation/debugoutput.h +++ b/companion/src/simulation/debugoutput.h @@ -21,30 +21,76 @@ #ifndef _DEBUGOUTPUT_H_ #define _DEBUGOUTPUT_H_ -#include +#include "simulator.h" #include "simulatorinterface.h" +#include +#include +#include +#include + namespace Ui { class DebugOutput; } +class QAbstractButton; -class DebugOutput : public QDialog +using namespace Simulator; + +class DebugOutput : public QWidget { Q_OBJECT public: - explicit DebugOutput(QWidget * parent); + explicit DebugOutput(QWidget * parent, SimulatorInterface * simulator); virtual ~DebugOutput(); - void traceCallback(const QString & text); + void start(); + void stop(); + void traceCallback(const char * text); + + static QRegularExpression makeRegEx(const QString & input, bool * isExlusive = NULL); + + protected slots: + void saveState(); + void restoreState(); + void processBytesReceived(); + void onFilterTextEdited(); + void onFilterTextChanged(const QString &); + void on_bufferSize_editingFinished(); + void on_actionWordWrap_toggled(bool checked); + void on_actionClearScr_triggered(); + void on_actionShowFilterHelp_triggered(); protected: - - private: Ui::DebugOutput * ui; + SimulatorInterface * m_simulator; + QTimer * m_tmrDataPrint; + QStringList m_dataBuffer; + QMutex m_mtxDataBuffer; + QRegularExpression m_filterRegEx; - private slots: + int m_radioProfileId; + int m_dataPrintFreq; + bool m_running; + bool m_filterExclude; + const static int m_dataBufferMaxSize; + const static int m_dataPrintFreqDefault; + const static quint16 m_savedViewStateVersion; +}; + +class DebugOutputFilterValidator : public QValidator +{ + public: + DebugOutputFilterValidator(QObject *parent = Q_NULLPTR) : QValidator(parent) { } + virtual State validate(QString & input, int &) const; +}; + +class DeleteComboBoxItemEventFilter : public QObject +{ + Q_OBJECT + protected: + bool eventFilter(QObject *obj, QEvent *event); }; #endif // _DEBUGOUTPUT_H_ diff --git a/companion/src/simulation/debugoutput.ui b/companion/src/simulation/debugoutput.ui index f14929b02..9a1fce02e 100644 --- a/companion/src/simulation/debugoutput.ui +++ b/companion/src/simulation/debugoutput.ui @@ -1,15 +1,21 @@ DebugOutput - + 0 0 - 711 - 470 + 555 + 733 + + + 0 + 0 + + Debug Output @@ -18,30 +24,286 @@ :/icon.png:/icon.png + + 7 + + + 7 + + + 7 + + + 7 + + + 6 + - - - - - - Courier New - 10 - - - - Qt::ScrollBarAlwaysOn - - - QPlainTextEdit::NoWrap - - - true - - - - + + + + 7 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 2 + + + + + <html><head/><body><p>Enable or disable the filter. If the button won't stay enabled, it is likely there is a syntax error in the Regular Expression entered.</p></body></html> + + + padding: 0.15em + + + Filter: + + + + 18 + 18 + + + + true + + + Qt::ToolButtonTextOnly + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + <html><head/><body><p>Enter filter text here. Click the help/info button for details about using the filter. </p><p> +To <b>remove a remembered entry</b> from the filter list, first choose it, and then press <code>Shift-Delete</code> (or <code>Shift-Backspace</code>) key combination.</p></body></html> + + + true + + + + + + -1 + + + 50 + + + QComboBox::InsertAtTop + + + QComboBox::AdjustToMinimumContentsLength + + + 1 + + + + + + + H + + + true + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 10 + 20 + + + + + + + + + + Buffer: + + + + + + + + 0 + 0 + + + + Number of lines to keep in display. + + + + + + 100 + + + 999999 + + + 100 + + + 10000 + + + + + + + + + WW + + + + 16 + 16 + + + + true + + + true + + + + + + + C + + + + 16 + 16 + + + + true + + + + + + + + + + + 0 + 0 + + + + + Courier New + 10 + + + + Qt::ScrollBarAlwaysOn + + + false + + + QPlainTextEdit::NoWrap + + + true + + + 10000 + + + + + + :/images/simulator/icons/svg/info.svg:/images/simulator/icons/svg/info.svg + + + Filter &Help + + + Show information about using the filter. + + + + + true + + + + :/images/simulator/icons/svg/word_wrap.svg:/images/simulator/icons/svg/word_wrap.svg + + + Word &Wrap + + + Toggle word wrapping on/off. + + + + + + :/images/simulator/icons/svg/eraser.svg:/images/simulator/icons/svg/eraser.svg + + + &Clear + + + Clear the output window of all text. + + diff --git a/companion/src/simulation/radiooutputswidget.cpp b/companion/src/simulation/radiooutputswidget.cpp new file mode 100644 index 000000000..86162fc71 --- /dev/null +++ b/companion/src/simulation/radiooutputswidget.cpp @@ -0,0 +1,365 @@ +/* + * 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 "radiooutputswidget.h" +#include "ui_radiooutputswidget.h" + +#include "appdata.h" +#include "constants.h" +#include "eeprominterface.h" +#include "radiodata.h" +#include "simulator.h" +#include "simulatorinterface.h" + +extern AppData g; // ensure what "g" means + +const int RadioOutputsWidget::m_dataUpdateFreqDefault = 10; // ms +const quint16 RadioOutputsWidget::m_savedViewStateVersion = 1; + +RadioOutputsWidget::RadioOutputsWidget(SimulatorInterface * simulator, Firmware * firmware, QWidget *parent) : + QWidget(parent), + m_simulator(simulator), + m_firmware(firmware), + m_tmrUpdateData(new QTimer), + m_radioProfileId(g.sessionId()), + m_started(false), + ui(new Ui::RadioOutputsWidget) +{ + ui->setupUi(this); + + restoreState(); + + m_dataUpdateFreq = m_dataUpdateFreqDefault; + m_tmrUpdateData->setInterval(m_dataUpdateFreq); + + connect(m_tmrUpdateData, &QTimer::timeout, this, &RadioOutputsWidget::setValues); +} + +RadioOutputsWidget::~RadioOutputsWidget() +{ + stop(); + saveState(); + if (m_tmrUpdateData) + delete m_tmrUpdateData; + delete ui; +} + +void RadioOutputsWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void RadioOutputsWidget::showEvent(QShowEvent * event) +{ + if (m_started) + m_tmrUpdateData->start(); +} + +void RadioOutputsWidget::hideEvent(QHideEvent * event) +{ + m_tmrUpdateData->stop(); +} + +void RadioOutputsWidget::start() +{ + setupChannelsDisplay(); + setupGVarsDisplay(); + setupLsDisplay(); + m_tmrUpdateData->start(); + m_started = true; +} + +void RadioOutputsWidget::stop() +{ + m_tmrUpdateData->stop(); + m_started = false; +} + +void RadioOutputsWidget::restart() +{ + stop(); + start(); +} + +void RadioOutputsWidget::saveState() +{ + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << m_savedViewStateVersion + << ui->btnLogiSw->isChecked() << ui->btnGlobalVars->isChecked() << ui->btnChannels->isChecked() + << ui->splitter->saveState(); + + SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions(); + opts.radioOutputsState = state; + g.profile[m_radioProfileId].simulatorOptions(opts); +} + +void RadioOutputsWidget::restoreState() +{ + quint16 ver = 0; + QByteArray splitterState; + bool ls = true, gv = true, ch = true; + QByteArray state = g.profile[m_radioProfileId].simulatorOptions().radioOutputsState; + QDataStream stream(state); + + stream >> ver; + if (ver && ver <= m_savedViewStateVersion) + stream >> ls >> gv >> ch >> splitterState; + + ui->btnLogiSw->setChecked(ls); + ui->btnGlobalVars->setChecked(gv); + ui->btnChannels->setChecked(ch); + if (!splitterState.isEmpty()) + ui->splitter->restoreState(splitterState); +} + + +void RadioOutputsWidget::setupChannelsDisplay() +{ + int outputs = std::min(32, m_firmware->getCapability(Capability(Outputs))); + + // delete old widgets if already exist + m_channelsMap.clear(); + + QWidget * oldChanW = ui->channelsScroll->takeWidget(); + if (oldChanW) + oldChanW->deleteLater(); + + if (!outputs) + return; + + QWidget * channelsWidget = new QWidget(); + QGridLayout * channelsLayout = new QGridLayout(channelsWidget); + channelsLayout->setHorizontalSpacing(4); + channelsLayout->setVerticalSpacing(3); + channelsLayout->setContentsMargins(5, 5, 5, 5); + + ui->channelsScroll->setWidget(channelsWidget); + + // populate outputs + int column = 0; + for (int i=0; i < outputs; i++) { + QLabel * label = new QLabel(channelsWidget); + label->setText(" " + RawSource(SOURCE_TYPE_CH, i).toString() + " "); + label->setAlignment(Qt::AlignCenter); + label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + channelsLayout->addWidget(label, 0, column, 1, 1); + + QSlider * slider = new QSlider(channelsWidget); + slider->setEnabled(false); + slider->setMinimum(-1024); + slider->setMaximum(1024); + slider->setPageStep(128); + slider->setTracking(false); + slider->setOrientation(Qt::Vertical); + slider->setInvertedAppearance(false); + slider->setTickPosition(QSlider::TicksRight); + slider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + channelsLayout->addWidget(slider, 2, column, 1, 1); + channelsLayout->setAlignment(slider, Qt::AlignHCenter); + + QLabel * value = new QLabel(channelsWidget); + value->setMinimumSize(QSize(value->fontMetrics().size(Qt::TextSingleLine, "-100.0").width(), 0)); + value->setAlignment(Qt::AlignCenter); + value->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + channelsLayout->addWidget(value, 1, column, 1, 1); + + ++column; + + m_channelsMap.insert(i, QPair(value, slider)); + } +} + +void RadioOutputsWidget::setupGVarsDisplay() +{ + int gvars = m_firmware->getCapability(Capability(Gvars)); + int fmodes = m_firmware->getCapability(Capability(FlightModes)); + + // delete old widgets if already exist + m_globalVarsMap.clear(); + + QWidget * oldGv = ui->globalVarsScroll->takeWidget(); + if (oldGv) + oldGv->deleteLater(); + + if (!gvars) + return; + + QWidget * gvarsWidget = new QWidget(); + QGridLayout * gvarsLayout = new QGridLayout(gvarsWidget); + gvarsLayout->setContentsMargins(5, 5, 5, 5); + gvarsLayout->setHorizontalSpacing(6); + gvarsLayout->setVerticalSpacing(3); + ui->globalVarsScroll->setWidget(gvarsWidget); + + QPalette::ColorRole bgrole = QPalette::AlternateBase; + for (int fm=0; fm < fmodes; fm++) { + QLabel * label = new QLabel(gvarsWidget); + label->setText(QString("FM%1").arg(fm)); + label->setAlignment(Qt::AlignCenter); + label->setBackgroundRole(bgrole); + gvarsLayout->addWidget(label, 0, fm+1); + } + QHash fmMap; + for (int gv=0; gv < gvars; gv++) { + bgrole = ((gv % 2) ? QPalette::Background : QPalette::AlternateBase); + QLabel * label = new QLabel(gvarsWidget); + label->setText(QString("GV%1").arg(gv+1)); + label->setAutoFillBackground(true); + label->setBackgroundRole(bgrole); + gvarsLayout->addWidget(label, gv+1, 0); + for (int fm=0; fm < fmodes; fm++) { + QLabel * value = new QLabel(gvarsWidget); + value->setAutoFillBackground(true); + value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + value->setBackgroundRole(bgrole); + value->setText("0"); + value->setStyleSheet("padding-right: .06em;"); + gvarsLayout->addWidget(value, gv+1, fm+1); + + fmMap.insert(fm, value); + } + m_globalVarsMap.insert(gv, fmMap); + } +} + +void RadioOutputsWidget::setupLsDisplay() +{ + int switches = m_firmware->getCapability(LogicalSwitches); + + // delete old widgets if already exist + m_logicSwitchMap.clear(); + + QWidget * oldLsW = ui->logicalSwitchesScroll->takeWidget(); + if (oldLsW) + oldLsW->deleteLater(); + + if (!switches) + return; + + QWidget * logicalSwitches = new QWidget(); + logicalSwitches->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + QGridLayout * logicalSwitchesLayout = new QGridLayout(logicalSwitches); + logicalSwitchesLayout->setHorizontalSpacing(3); + logicalSwitchesLayout->setVerticalSpacing(2); + logicalSwitchesLayout->setContentsMargins(5, 5, 5, 5); + ui->logicalSwitchesScroll->setWidget(logicalSwitches); + + // populate logical switches + int rows = switches / (switches > 16 ? 4 : 2); + for (int i=0; i < switches; i++) { + QLabel * lsLbl = new QLabel; + logicalSwitchesLayout->addWidget(createLogicalSwitch(logicalSwitches, i, lsLbl), i / rows, i % rows, 1, 1); + m_logicSwitchMap.insert(i, lsLbl); + } +} + +QWidget * RadioOutputsWidget::createLogicalSwitch(QWidget * parent, int switchNo, QLabel * label) +{ + QFrame * swtch = new QFrame(parent); + swtch->setAutoFillBackground(true); + swtch->setFrameShape(QFrame::Panel); + swtch->setFrameShadow(QFrame::Raised); + swtch->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + swtch->setMaximumHeight(18); + QVBoxLayout * layout = new QVBoxLayout(swtch); + layout->setContentsMargins(2, 0, 2, 0); + if (label) { + QFont font; + font.setPointSize(8); + label->setParent(swtch); + label->setFont(font); + label->setText(RawSwitch(SWITCH_TYPE_VIRTUAL, switchNo+1).toString()); + label->setAlignment(Qt::AlignCenter); + label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + label->setAutoFillBackground(true); + layout->addWidget(label); + } + return swtch; +} + +// Read various values from firmware simulator and populate values in this UI +void RadioOutputsWidget::setValues() +{ + static int lastPhase = 0; + static TxOutputs prevOutputs = TxOutputs(); + int currentPhase; + TxOutputs outputs; + + m_simulator->getValues(outputs); + currentPhase = m_simulator->getPhase(); + + if (ui->channelsWidget->isVisible()) { + QHash >::const_iterator ch; + for (ch = m_channelsMap.constBegin(); ch != m_channelsMap.constEnd(); ++ch) { + if (ch.key() >= CPN_MAX_CHNOUT) + continue; + ch.value().first->setText(QString("%1%").arg(outputs.chans[ch.key()] * 100 / 1024)); + ch.value().second->setValue(qMin(1024, qMax(-1024, outputs.chans[ch.key()]))); + } + } + + if (ui->logicalSwitchesWidget->isVisible()) { + QHash::const_iterator ls; + for (ls = m_logicSwitchMap.constBegin(); ls != m_logicSwitchMap.constEnd(); ++ls) { + if (ls.key() >= CPN_MAX_CSW || prevOutputs.vsw[ls.key()] == outputs.vsw[ls.key()]) + continue; + ls.value()->setBackgroundRole(outputs.vsw[ls.key()] ? QPalette::Highlight : QPalette::Background); + prevOutputs.vsw[ls.key()] = outputs.vsw[ls.key()]; + } + } + + if (ui->globalVarsWidget->isVisible()) { + QFont font; + QPalette::ColorRole bgrole; + QHash >::const_iterator gv; + QHash::const_iterator fm; + for (gv = m_globalVarsMap.constBegin(); gv != m_globalVarsMap.constEnd(); ++gv) { + if (gv.key() >= CPN_MAX_GVARS) + continue; + for (fm = gv.value().constBegin(); fm != gv.value().constEnd(); ++fm) { + if (fm.key() >= CPN_MAX_FLIGHT_MODES) + continue; + if (currentPhase != lastPhase || prevOutputs.gvars[fm.key()][gv.key()] != outputs.gvars[fm.key()][gv.key()]) { + font = fm.value()->font(); + bgrole = ((gv.key() % 2) ? QPalette::Background : QPalette::AlternateBase); + if (fm.key() == lastPhase) { + font.setBold(true); + bgrole = QPalette::Highlight; + } + fm.value()->setText(QString::number(outputs.gvars[fm.key()][gv.key()])); + fm.value()->setFont(font); + fm.value()->setBackgroundRole(bgrole); + prevOutputs.gvars[fm.key()][gv.key()] = outputs.gvars[fm.key()][gv.key()]; + } + } + } + } + + if (currentPhase != lastPhase) + lastPhase = currentPhase; +} diff --git a/companion/src/simulation/radiooutputswidget.h b/companion/src/simulation/radiooutputswidget.h new file mode 100644 index 000000000..972577cb5 --- /dev/null +++ b/companion/src/simulation/radiooutputswidget.h @@ -0,0 +1,87 @@ +/* + * 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 RADIOOUTPUTSWIDGET_H +#define RADIOOUTPUTSWIDGET_H + +#include "simulator.h" + +#include +#include + +namespace Ui { +class RadioOutputsWidget; +} + +class Firmware; +class SimulatorInterface; + +class QFrame; +class QLabel; +class QSlider; + +using namespace Simulator; + +class RadioOutputsWidget : public QWidget +{ + Q_OBJECT + + public: + explicit RadioOutputsWidget(SimulatorInterface * simulator, Firmware * firmware, QWidget * parent = 0); + ~RadioOutputsWidget(); + + void start(); + void stop(); + void restart(); + + protected slots: + void saveState(); + void restoreState(); + void setValues(); + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); + + protected: + void changeEvent(QEvent *e); + void setupChannelsDisplay(); + void setupLsDisplay(); + void setupGVarsDisplay(); + QWidget * createLogicalSwitch(QWidget * parent, int switchNo, QLabel * label); + + SimulatorInterface * m_simulator; + Firmware * m_firmware; + QTimer * m_tmrUpdateData; + + QHash > m_channelsMap; // m_channelsMap[chanIndex] = {QLabel*, QSlider*} + QHash m_logicSwitchMap; // m_logicSwitchMap[lsIndex] = QLabel* + QHash > m_globalVarsMap; // m_globalVarsMap[gvarIndex][fmodeIndex] = QLabel* + + int m_radioProfileId; + int m_dataUpdateFreq; + bool m_started; + + const static int m_dataUpdateFreqDefault; + const static quint16 m_savedViewStateVersion; + + private: + Ui::RadioOutputsWidget * ui; +}; + +#endif // RADIOOUTPUTSWIDGET_H diff --git a/companion/src/simulation/radiooutputswidget.ui b/companion/src/simulation/radiooutputswidget.ui new file mode 100644 index 000000000..94888173c --- /dev/null +++ b/companion/src/simulation/radiooutputswidget.ui @@ -0,0 +1,440 @@ + + + RadioOutputsWidget + + + + 0 + 0 + 762 + 436 + + + + Form + + + + + + + 5 + + + 7 + + + 8 + + + 7 + + + 7 + + + + + + 0 + 0 + + + + + 6 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + View: + + + + + + + Logical Switches + + + true + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Global Variables + + + true + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Channel Outputs + + + true + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + false + + + + + 0 + 0 + + + + + + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + 3 + + + L +o +g +i +c + + + Qt::AlignCenter + + + true + + + true + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 740 + 85 + + + + + + + + + + + 0 + 2 + + + + + + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + 3 + + + G +l +o +b +a +l + + + Qt::AlignCenter + + + true + + + true + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 739 + 101 + + + + + + + + + + + 0 + 3 + + + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + 3 + + + C +h +a +n +n +e +l +s + + + Qt::AlignCenter + + + true + + + true + + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 739 + 194 + + + + + + + + + + + + + + + btnChannels + toggled(bool) + channelsWidget + setVisible(bool) + + + 236 + 19 + + + 346 + 435 + + + + + btnGlobalVars + toggled(bool) + globalVarsWidget + setVisible(bool) + + + 157 + 19 + + + 346 + 275 + + + + + btnLogiSw + toggled(bool) + logicalSwitchesWidget + setVisible(bool) + + + 62 + 19 + + + 346 + 114 + + + + + diff --git a/companion/src/simulation/radiouiaction.h b/companion/src/simulation/radiouiaction.h index 61bfb0ec0..cc3a4b5e1 100644 --- a/companion/src/simulation/radiouiaction.h +++ b/companion/src/simulation/radiouiaction.h @@ -127,8 +127,10 @@ class RadioUiAction : public QObject { if (toggle(active)) { emit triggered(m_hwIndex, active); - if (active) + if (active) { emit pushed(m_hwIndex); + emit pushed(); + } } } @@ -146,6 +148,7 @@ class RadioUiAction : public QObject void toggled(int index, bool active); // on programmatic or user interaction change void triggered(int index, bool active); // on user interaction change only void pushed(int index); // only emitted on user interaction && when 'active' is true + void pushed(); }; #endif // RADIOUIACTION_H diff --git a/companion/src/simulation/simulateduiwidget.cpp b/companion/src/simulation/simulateduiwidget.cpp index 58f89a660..17cd1bd59 100644 --- a/companion/src/simulation/simulateduiwidget.cpp +++ b/companion/src/simulation/simulateduiwidget.cpp @@ -40,7 +40,7 @@ SimulatedUIWidget::SimulatedUIWidget(SimulatorInterface * simulator, SimulatorDi { m_rotEncClickAction = addRadioUiAction(-1, 0, tr("Rotary encoder click")); m_screenshotAction = addRadioUiAction(-1, Qt::Key_Print, tr("Take Screenshot")); - connect(m_screenshotAction, &RadioUiAction::pushed, this, &SimulatedUIWidget::saveScreenshot); + connect(m_screenshotAction, static_cast(&RadioUiAction::pushed), this, &SimulatedUIWidget::captureScreenshot); } SimulatedUIWidget::~SimulatedUIWidget() @@ -114,9 +114,8 @@ void SimulatedUIWidget::updateUi() } */ } -void SimulatedUIWidget::saveScreenshot(int idx) +void SimulatedUIWidget::captureScreenshot() { - Q_UNUSED(idx) QString fileName = ""; if (!g.snapToClpbrd()) { QString path = g.snapshotDir(); @@ -124,7 +123,8 @@ void SimulatedUIWidget::saveScreenshot(int idx) path = "./"; QDir dir(path); if (!dir.exists() || !dir.isReadable()) { - m_simuDialog->traceCallback("SIMULATOR ERROR - Cannot open screenshot folder, check your settings.\n"); +// m_simulator->traceCallback("SIMULATOR ERROR - Cannot open screenshot folder, check your settings.\n"); + qDebug() << "SIMULATOR ERROR - Cannot open screenshot folder, check your settings."; return; } fileName += QString("%1/screenshot_%2.png").arg(dir.absolutePath(), QDateTime::currentDateTime().toString("yy-MM-dd_HH-mm-ss")); @@ -180,12 +180,12 @@ void SimulatedUIWidget::setLcd(LcdWidget * lcd) void SimulatedUIWidget::connectScrollActions() { - connect(m_scrollUpAction, &RadioUiAction::pushed, [this](void) { + connect(m_scrollUpAction, static_cast(&RadioUiAction::pushed), [this](void) { this->simulatorWheelEvent(-1); m_scrollUpAction->toggle(false); }); - connect(m_scrollDnAction, &RadioUiAction::pushed, [this](void) { + connect(m_scrollDnAction, static_cast(&RadioUiAction::pushed), [this](void) { simulatorWheelEvent(1); m_scrollDnAction->toggle(false); }); diff --git a/companion/src/simulation/simulateduiwidget.h b/companion/src/simulation/simulateduiwidget.h index e60d8674b..49e97f8a4 100644 --- a/companion/src/simulation/simulateduiwidget.h +++ b/companion/src/simulation/simulateduiwidget.h @@ -64,8 +64,7 @@ class SimulatedUIWidget : public QWidget public slots: void updateUi(); - void saveScreenshot(int idx = -1); - void captureScreenshot() { saveScreenshot(); } + void captureScreenshot(); void simulatorWheelEvent(qint8 steps); void wheelEvent(QWheelEvent *event); diff --git a/companion/src/simulation/simulator.h b/companion/src/simulation/simulator.h index b4ac07868..c756b7d1a 100644 --- a/companion/src/simulation/simulator.h +++ b/companion/src/simulation/simulator.h @@ -24,16 +24,85 @@ #include #include #include +#include +#include #include #include -#define SIMULATOR_OPTIONS_VERSION 1 +#define SIMULATOR_FLAGS_NOTX 0x01 // simulating a single model from Companion +#define SIMULATOR_FLAGS_STANDALONE 0x02 // started from stanalone simulator -namespace Simulator { +#define SIMULATOR_OPTIONS_VERSION 2 -typedef QPair keymapHelp_t; +namespace Simulator +{ -} + typedef QPair keymapHelp_t; + + class SimulatorStyle + { + public: + SimulatorStyle() { } + static QString const basePath() { return ":/themes/default"; } + static QString const styleSheet() + { + QString css; + QFile fh(QString("%1/style.css").arg(basePath())); + if (fh.open(QFile::ReadOnly | QFile::Text)) { + css = fh.readAll(); + fh.close(); + } +#ifdef __APPLE__ + fh.setFileName(QString("%1/style-osx.css").arg(basePath())); + if (fh.open(QFile::ReadOnly | QFile::Text)) { + css.append(fh.readAll()); + fh.close(); + } +#endif + return css; + } + }; // SimulatorStyle + + class SimulatorIcon : public QIcon + { + public: + SimulatorIcon(const QString & baseImage) + { + QString baseFile = QString("%1/%2").arg(basePath(), baseImage); + addFile(QString("%1.svg").arg(baseFile)); + + QString addfile = QString("%1-on.svg").arg(baseFile); + if (QFile(addfile).exists()) + addFile(addfile, QSize(), QIcon::Normal, QIcon::On); + + addfile = QString("%1-active.svg").arg(baseFile); + if (QFile(addfile).exists()) + addFile(addfile, QSize(), QIcon::Active, QIcon::Off); + + addfile = QString("%1-disabled.svg").arg(baseFile); + if (QFile(addfile).exists()) + addFile(addfile, QSize(), QIcon::Disabled, QIcon::Off); + } + + static QString const basePath() { return ":/images/simulator/icons/svg"; } + + static QSize const toolbarIconSize(int setting) + { + switch(setting) { + case 0: + return QSize(16, 16); + case 2: + return QSize(32, 32); + case 3: + return QSize(48, 48); + case 1: + default: + return QSize(24, 24); + } + } + }; // class SimulatorIcon + +} // namespace Simulator struct SimulatorOptions { @@ -54,14 +123,18 @@ struct SimulatorOptions QString dataFile; QString dataFolder; QString sdPath; - QByteArray windowGeometry; + QByteArray windowGeometry; // SimulatorMainWindow geometry QList controlsState; // saved switch/pot/stick settings QColor lcdColor; + // added in v2 + QByteArray windowState; // SimulatorMainWindow dock/toolbar/options UI state + QByteArray dbgConsoleState; // DebugOutput UI state + QByteArray radioOutputsState; // RadioOutputsWidget UI state friend QDataStream & operator << (QDataStream &out, const SimulatorOptions & o) { out << quint16(SIMULATOR_OPTIONS_VERSION) << o.startupDataType << o.firmwareId << o.dataFile << o.dataFolder - << o.sdPath << o.windowGeometry << o.controlsState << o.lcdColor; + << o.sdPath << o.windowGeometry << o.controlsState << o.lcdColor << o.windowState << o.dbgConsoleState << o.radioOutputsState; return out; } @@ -70,6 +143,8 @@ struct SimulatorOptions 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; + if (o._version >= 2) + in >> o.windowState >> o.dbgConsoleState >> o.radioOutputsState; } return in; } diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index b8101690d..36ea286e9 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -22,7 +22,6 @@ #include "ui_simulatordialog.h" #include "appdata.h" -#include "debugoutput.h" #include "radiofaderwidget.h" #include "radioknobwidget.h" #include "radioswitchwidget.h" @@ -31,8 +30,6 @@ #include "simulateduiwidget.h" #include "simulatorinterface.h" #include "storage.h" -#include "telemetrysimu.h" -#include "trainersimu.h" #include "virtualjoystickwidget.h" #ifdef JOYSTICKS #include "joystick.h" @@ -43,18 +40,8 @@ #include #include -SimulatorDialog * traceCallbackInstance = 0; - -void traceCb(const char * text) -{ - // divert C callback into simulator instance - if (traceCallbackInstance) { - traceCallbackInstance->traceCallback(text); - } -} - SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator, quint8 flags): - QDialog(parent), + QWidget(parent), ui(new Ui::SimulatorDialog), simulator(simulator), firmware(getCurrentFirmware()), @@ -63,12 +50,8 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator radioUiWidget(NULL), vJoyLeft(NULL), vJoyRight(NULL), - TelemetrySimu(NULL), - TrainerSimu(NULL), - DebugOut(NULL), m_board(getCurrentBoard()), flags(flags), - radioProfileId(g.id()), lastPhase(-1), buttonPressed(0), trimPressed(TRIM_NONE), @@ -78,40 +61,74 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator middleButtonPressed(false), firstShow(true) { - setWindowFlags(Qt::Window); - - // install simulator TRACE hook - traceCallbackInstance = this; - simulator->installTraceHook(traceCb); #ifdef JOYSTICKS joystick = NULL; #endif // defaults - setRadioProfileId(radioProfileId); + setRadioProfileId(g.sessionId()); setSdPath(g.profile[radioProfileId].sdPath()); - setupUi(); + ui->setupUi(this); + + windowName = tr("Radio Simulator (%1)").arg(firmware->getName()); + setWindowTitle(windowName); + + switch(m_board) { + case Board::BOARD_TARANIS_X7 : + radioUiWidget = new SimulatedUIWidgetX7(simulator, this); + break; + case Board::BOARD_TARANIS_X9D : + case Board::BOARD_TARANIS_X9DP : + case Board::BOARD_TARANIS_X9E : + radioUiWidget = new SimulatedUIWidgetX9(simulator, this); + break; + case Board::BOARD_X12S: + case Board::BOARD_X10: + radioUiWidget = new SimulatedUIWidgetX12(simulator, this); + break; + default: + radioUiWidget = new SimulatedUIWidget9X(simulator, this); + break; + } + + foreach (keymapHelp_t item, *radioUiWidget->getKeymapHelp()) + keymapHelp.append(item); + + ui->radioUiWidget->layout()->removeItem(ui->radioUiTempSpacer); + ui->radioUiWidget->layout()->addWidget(radioUiWidget); + + vJoyLeft = new VirtualJoystickWidget(this, 'L'); + ui->leftStickLayout->addWidget(vJoyLeft); + + vJoyRight = new VirtualJoystickWidget(this, 'R'); + ui->rightStickLayout->addWidget(vJoyRight); + + connect(vJoyLeft, SIGNAL(trimButtonPressed(int)), this, SLOT(onTrimPressed(int))); + connect(vJoyLeft, SIGNAL(trimButtonReleased()), this, SLOT(onTrimReleased())); + connect(vJoyLeft, SIGNAL(trimSliderMoved(int,int)), this, SLOT(onTrimSliderMoved(int,int))); + + connect(vJoyRight, SIGNAL(trimButtonPressed(int)), this, SLOT(onTrimPressed(int))); + connect(vJoyRight, SIGNAL(trimButtonReleased()), this, SLOT(onTrimReleased())); + connect(vJoyRight, SIGNAL(trimSliderMoved(int,int)), this, SLOT(onTrimSliderMoved(int,int))); } SimulatorDialog::~SimulatorDialog() { - traceCallbackInstance = 0; + shutdown(); - if (timer) { - timer->stop(); + if (timer) timer->deleteLater(); - } if (radioUiWidget) - radioUiWidget->deleteLater(); + delete radioUiWidget; if (vJoyLeft) - vJoyLeft->deleteLater(); + delete vJoyLeft; if (vJoyRight) - vJoyRight->deleteLater(); + delete vJoyRight; #ifdef JOYSTICKS if (joystick) - joystick->deleteLater(); + delete joystick; #endif firmware = NULL; // Not sure we should delete this but at least release our pointer. @@ -125,13 +142,6 @@ SimulatorDialog::~SimulatorDialog() * Public slots/setters */ -void SimulatorDialog::setRadioProfileId(int value) -{ - radioProfileId = value; - if (simulator) - simulator->setVolumeGain(g.profile[radioProfileId].volumeGain()); -} - void SimulatorDialog::setSdPath(const QString & sdPath) { setPaths(sdPath, radioDataPath); @@ -355,25 +365,20 @@ void SimulatorDialog::deleteTempData() void SimulatorDialog::saveState() { SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions(); - opts.windowGeometry = saveGeometry(); + //opts.windowGeometry = saveGeometry(); opts.controlsState = saveRadioWidgetsState(); g.profile[radioProfileId].simulatorOptions(opts); } void SimulatorDialog::setUiAreaStyle(const QString & style) { - ui->radioUiTab->setStyleSheet(style); + ui->radioUiWidget->setStyleSheet(style); } -void SimulatorDialog::traceCallback(const char * text) +void SimulatorDialog::captureScreenshot(bool) { - // this function is called from other threads - traceMutex.lock(); - // limit the size of list - if (traceList.size() < 1000) { - traceList.append(QString(text)); - } - traceMutex.unlock(); + if (radioUiWidget) + radioUiWidget->captureScreenshot(); } /* @@ -384,8 +389,6 @@ void SimulatorDialog::start() { setupRadioWidgets(); setupJoysticks(); - setupOutputsDisplay(); - setupGVarsDisplay(); restoreRadioWidgetsState(); if (startupData.isEmpty()) @@ -414,94 +417,30 @@ void SimulatorDialog::restart() start(); } +void SimulatorDialog::shutdown() +{ + stop(); + saveState(); + if (saveTempRadioData) + saveTempData(); + if (deleteTempRadioData) + deleteTempData(); +} + /* * Setup */ -void SimulatorDialog::setupUi() +void SimulatorDialog::setRadioProfileId(int value) { - ui->setupUi(this); - - windowName = tr("Simulating Radio (%1)").arg(firmware->getName()); - setWindowTitle(windowName); - - switch(m_board) { - case Board::BOARD_TARANIS_X7: - radioUiWidget = new SimulatedUIWidgetX7(simulator, this); - break; - case Board::BOARD_TARANIS_X9D: - case Board::BOARD_TARANIS_X9DP: - case Board::BOARD_TARANIS_X9E: - radioUiWidget = new SimulatedUIWidgetX9(simulator, this); - break; - case Board::BOARD_X12S: - case Board::BOARD_X10: - radioUiWidget = new SimulatedUIWidgetX12(simulator, this); - break; - default: - radioUiWidget = new SimulatedUIWidget9X(simulator, this); - break; - } - - foreach (keymapHelp_t item, *radioUiWidget->getKeymapHelp()) { - keymapHelp.append(item); - } - - ui->radioUiTab->layout()->addWidget(radioUiWidget); - ui->tabWidget->setTabText(0, firmware->getName()); - ui->tabWidget->setFixedHeight(radioUiWidget->height() + ui->tabWidget->tabBar()->height() - 8); - - vJoyLeft = new VirtualJoystickWidget(this, 'L'); - ui->leftStickLayout->addWidget(vJoyLeft); - - vJoyRight = new VirtualJoystickWidget(this, 'R'); - ui->rightStickLayout->addWidget(vJoyRight); - - ui->tabWidget->setCurrentIndex(flags & SIMULATOR_FLAGS_NOTX); - - connect(vJoyLeft, SIGNAL(trimButtonPressed(int)), this, SLOT(onTrimPressed(int))); - connect(vJoyLeft, SIGNAL(trimButtonReleased()), this, SLOT(onTrimReleased())); - connect(vJoyLeft, SIGNAL(trimSliderMoved(int,int)), this, SLOT(onTrimSliderMoved(int,int))); - - connect(vJoyRight, SIGNAL(trimButtonPressed(int)), this, SLOT(onTrimPressed(int))); - connect(vJoyRight, SIGNAL(trimButtonReleased()), this, SLOT(onTrimReleased())); - 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())); - connect(ui->btn_reloadSimu, SIGNAL(released()), this, SLOT(restart())); - -#ifdef JOYSTICKS - bool showJoystick = true; -#else - bool showJoystick = false; -#endif - - // Hide some main UI buttons based on board capabilities, and add keymap help texts. - QString role; - foreach (QPushButton * btn, ui->buttonBox->findChildren()) { - role = btn->property("role").toString(); - if ((role == "joystick" && !showJoystick) || - (role == "telemetry" && !firmware->getCapability(Capability(SportTelemetry))) || - (role == "reloadLua" && !firmware->getCapability(Capability(LuaInputsPerScript))) ) - { - btn->hide(); - continue; - } - if (!btn->shortcut().isEmpty()) - keymapHelp.append(keymapHelp_t(btn->shortcut().toString(QKeySequence::NativeText), btn->statusTip())); - } - + radioProfileId = value; + if (simulator) + simulator->setVolumeGain(g.profile[radioProfileId].volumeGain()); } void SimulatorDialog::setupRadioWidgets() { - int i, midpos, aIdx, wval; + int i, midpos, aIdx; QString wname; Board::Type board = firmware->getBoard(); @@ -535,13 +474,12 @@ void SimulatorDialog::setupRadioWidgets() 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(swcfg, wname, wval, ui->radioWidgetsHT); + RadioSwitchWidget * sw = new RadioSwitchWidget(swcfg, wname, -1, ui->radioWidgetsHT); sw->setIndex(i); ui->radioWidgetsHTLayout->addWidget(sw); switches.append(sw); @@ -593,172 +531,6 @@ void SimulatorDialog::setupRadioWidgets() } } -void SimulatorDialog::setupOutputsDisplay() -{ - // setup Outputs tab - QWidget * outputsWidget; - - // delete old widget if already exists - if (ui->tabWidget->count() > 1 && ui->tabWidget->widget(1)->objectName() == "RadioOutputsWidget") { - outputsWidget = ui->tabWidget->widget(1); - ui->tabWidget->removeTab(1); - outputsWidget->deleteLater(); - outputsWidget = NULL; - } - - channelValues.clear(); - channelSliders.clear(); - logicalSwitchLabels.clear(); - - outputsWidget = new QWidget(); - outputsWidget->setObjectName("RadioOutputsWidget"); - QGridLayout * gridLayout = new QGridLayout(outputsWidget); - gridLayout->setHorizontalSpacing(0); - gridLayout->setVerticalSpacing(3); - gridLayout->setContentsMargins(5, 3, 5, 3); - // logical switches area - QWidget * logicalSwitches = new QWidget(outputsWidget); - logicalSwitches->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - QGridLayout * logicalSwitchesLayout = new QGridLayout(logicalSwitches); - logicalSwitchesLayout->setHorizontalSpacing(3); - logicalSwitchesLayout->setVerticalSpacing(2); - logicalSwitchesLayout->setContentsMargins(0, 0, 0, 0); - gridLayout->addWidget(logicalSwitches, 0, 0, 1, 1); - // channels area - QScrollArea * scrollArea = new QScrollArea(outputsWidget); - QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Preferred); - sp.setHorizontalStretch(0); - sp.setVerticalStretch(0); - scrollArea->setSizePolicy(sp); - scrollArea->setWidgetResizable(true); - QWidget * channelsWidget = new QWidget(); - QGridLayout * channelsLayout = new QGridLayout(channelsWidget); - channelsLayout->setHorizontalSpacing(4); - channelsLayout->setVerticalSpacing(3); - channelsLayout->setContentsMargins(0, 0, 0, 3); - scrollArea->setWidget(channelsWidget); - gridLayout->addWidget(scrollArea, 1, 0, 1, 1); - - ui->tabWidget->insertTab(1, outputsWidget, QString(tr("Outputs"))); - - // populate outputs - int outputs = std::min(32, firmware->getCapability(Outputs)); - int column = 0; - for (int i=0; isetText(" " + RawSource(SOURCE_TYPE_CH, i).toString() + " "); - label->setAlignment(Qt::AlignCenter); - label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - channelsLayout->addWidget(label, 0, column, 1, 1); - - QSlider * slider = new QSlider(outputsWidget); - slider->setEnabled(false); - slider->setMinimum(-1024); - slider->setMaximum(1024); - slider->setPageStep(128); - slider->setTracking(false); - slider->setOrientation(Qt::Vertical); - slider->setInvertedAppearance(false); - slider->setTickPosition(QSlider::TicksRight); - slider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - - QLabel * value = new QLabel(outputsWidget); - value->setMinimumSize(QSize(value->fontMetrics().size(Qt::TextSingleLine, "-100.0").width(), 0)); - value->setAlignment(Qt::AlignCenter); - value->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - channelValues << value; - channelsLayout->addWidget(value, 1, column, 1, 1); - - channelSliders << slider; - channelsLayout->addWidget(slider, 2, column++, 1, 1); - channelsLayout->setAlignment(slider, Qt::AlignHCenter); - } - - // populate logical switches - int switches = firmware->getCapability(LogicalSwitches); - int rows = switches / (switches > 16 ? 4 : 2); - for (int i=0; iaddWidget(swtch, i / rows, i % rows, 1, 1); - } -} - -void SimulatorDialog::setupGVarsDisplay() -{ - // setup GVars tab - - int gvars = firmware->getCapability(Gvars); - int fmodes = firmware->getCapability(FlightModes); - - QWidget * gvarsWidget; - // delete old widget if already exists - if (ui->tabWidget->count() > 2 && ui->tabWidget->widget(2)->objectName() == "RadioGVOutputsWidget") { - gvarsWidget = ui->tabWidget->widget(2); - ui->tabWidget->removeTab(2); - gvarsWidget->deleteLater(); - gvarsWidget = NULL; - } - - gvarValues.clear(); - - if (!gvars) - return; - - gvarsWidget = new QWidget(); - gvarsWidget->setObjectName("RadioGVOutputsWidget"); - QGridLayout * gvarsLayout = new QGridLayout(gvarsWidget); - ui->tabWidget->insertTab(2, gvarsWidget, QString(tr("GVars"))); - - for (int fm=0; fmsetText(QString("FM%1").arg(fm)); - label->setAlignment(Qt::AlignCenter); - gvarsLayout->addWidget(label, 0, fm+1); - } - for (int i=0; isetText(QString("GV%1").arg(i+1)); - label->setAutoFillBackground(true); - if ((i % 2) ==0 ) { - label->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }"); - } - gvarsLayout->addWidget(label, i+1, 0); - for (int fm=0; fmsetAutoFillBackground(true); - value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - if ((i % 2) ==0 ) { - value->setStyleSheet("QLabel { background-color: rgb(220, 220, 220) }"); - } - value->setText("0"); - gvarValues << value; - gvarsLayout->addWidget(value, i+1, fm+1); - } - } -} - -QFrame * SimulatorDialog::createLogicalSwitch(QWidget * parent, int switchNo, QVector & labels) -{ - QFrame * swtch = new QFrame(parent); - swtch->setAutoFillBackground(true); - swtch->setFrameShape(QFrame::Panel); - swtch->setFrameShadow(QFrame::Raised); - swtch->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - swtch->setMaximumHeight(18); - QVBoxLayout * layout = new QVBoxLayout(swtch); - layout->setContentsMargins(2, 0, 2, 0); - QFont font; - font.setPointSize(8); - QLabel * label = new QLabel(swtch); - label->setFont(font); - label->setText(RawSwitch(SWITCH_TYPE_VIRTUAL, switchNo+1).toString()); - label->setAlignment(Qt::AlignCenter); - label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - labels << label; - layout->addWidget(label); - return swtch; -} - void SimulatorDialog::setupJoysticks() { #ifdef JOYSTICKS @@ -855,6 +627,16 @@ void SimulatorDialog::restoreRadioWidgetsState() if (switchesMap.contains(switches[i]->getIndex())) switches[i]->setStateData(switchesMap.value(switches[i]->getIndex())); } + + // Set throttle stick down and locked, side depends on mode + if (radioSettings.stickMode & 1) { + vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); + vJoyLeft->setStickY(1); + } + else { + vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); + vJoyRight->setStickY(1); + } } QList SimulatorDialog::saveRadioWidgetsState() @@ -877,44 +659,8 @@ QList SimulatorDialog::saveRadioWidgetsState() // Read various values from firmware simulator and populate values in this UI void SimulatorDialog::setValues() { - static const int numGvars = firmware->getCapability(Gvars); - static const int numFlightModes = firmware->getCapability(FlightModes); - static TxOutputs prevOutputs; - - TxOutputs outputs; - simulator->getValues(outputs); - int currentPhase = simulator->getPhase(); - // Outputs tab visible? - if (ui->tabWidget->currentIndex() == 1) { - for (int i = 0; i < channelSliders.size() && i < CPN_MAX_CHNOUT; i++) { - channelSliders[i]->setValue(qMin(1024, qMax(-1024, outputs.chans[i]))); - channelValues[i]->setText(QString("%1").arg((qreal)outputs.chans[i]*100/1024, 0, 'f', 1)); - } - - for (int i = 0; i < logicalSwitchLabels.size() && i < CPN_MAX_CSW; i++) { - // setStyleSheet() is very CPU time consuming, do it only if the actual switch state changes - if (prevOutputs.vsw[i] != outputs.vsw[i]) { - logicalSwitchLabels[i]->setStyleSheet(outputs.vsw[i] ? CSWITCH_ON : CSWITCH_OFF); - prevOutputs.vsw[i] = outputs.vsw[i]; - } - } - } - - // GVars tab visible? - if (ui->tabWidget->currentIndex() == 2) { - for (int gv = 0; gv < numGvars; gv++) { - for (int fm = 0; fm < numFlightModes; fm++) { - // same trick for GVARS, but this has far less effect on CPU usage as setStyleSheet() - if (currentPhase != lastPhase || prevOutputs.gvars[fm][gv] != outputs.gvars[fm][gv]) { - gvarValues[gv*numFlightModes+fm]->setText(QString((fm == lastPhase) ? "%1" : "%1").arg(outputs.gvars[fm][gv])); - prevOutputs.gvars[fm][gv] = outputs.gvars[fm][gv]; - } - } - } - } - // display current flight mode in window title if (currentPhase != lastPhase) { lastPhase = currentPhase; @@ -923,7 +669,6 @@ void SimulatorDialog::setValues() phase_name = QString::number(currentPhase); setWindowTitle(windowName + QString(" - Flight Mode %1").arg(phase_name)); } - } // "get" values from this UI and send them to the firmware simulator. @@ -995,34 +740,16 @@ void SimulatorDialog::setTrims() * Event handlers/private slots */ -void SimulatorDialog::closeEvent(QCloseEvent *) -{ - stop(); - saveState(); - if (saveTempRadioData) - saveTempData(); - if (deleteTempRadioData) - deleteTempData(); -} +//void SimulatorDialog::showEvent(QShowEvent *) +//{ +// if (firstShow && isVisible()) { +// firstShow = false; +// } +//} -void SimulatorDialog::showEvent(QShowEvent *) -{ - if (firstShow) { - 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) { - vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyLeft->setStickY(1); - } - else { - vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); - vJoyRight->setStickY(1); - } - - firstShow = false; - } -} +//void SimulatorDialog::closeEvent(QCloseEvent *) +//{ +//} void SimulatorDialog::mousePressEvent(QMouseEvent *event) { @@ -1059,8 +786,6 @@ void SimulatorDialog::onTimerEvent() setTrims(); centerSticks(); } - - updateDebugOutput(); } void SimulatorDialog::onTrimPressed(int which) @@ -1087,111 +812,9 @@ void SimulatorDialog::centerSticks() vJoyRight->centerStick(); } -void SimulatorDialog::openTelemetrySimulator() -{ - // allow only one instance - if (TelemetrySimu == 0) { - TelemetrySimu = new TelemetrySimulator(this, simulator); - TelemetrySimu->setAttribute(Qt::WA_DeleteOnClose); - TelemetrySimu->show(); - connect(TelemetrySimu, &TelemetrySimulator::destroyed, [this](QObject *) { - this->TelemetrySimu = NULL; - }); - } - else if (!TelemetrySimu->isVisible()) { - TelemetrySimu->show(); - } -} - -void SimulatorDialog::openTrainerSimulator() -{ - // allow only one instance - if (TrainerSimu == 0) { - TrainerSimu = new TrainerSimulator(this, simulator); - TrainerSimu->setAttribute(Qt::WA_DeleteOnClose); - TrainerSimu->show(); - connect(TrainerSimu, &TrainerSimulator::destroyed, [this](QObject *) { - this->TrainerSimu = NULL; - }); - } - else if (!TrainerSimu->isVisible()) { - TrainerSimu->show(); - } -} - -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 - if (!DebugOut) { - DebugOut = new DebugOutput(this); - DebugOut->traceCallback(traceBuffer); - DebugOut->setAttribute(Qt::WA_DeleteOnClose); - DebugOut->show(); - connect(DebugOut, &DebugOutput::destroyed, [this](QObject *) { - this->DebugOut = NULL; - }); - } - else if (!DebugOut->isVisible()) { - DebugOut->show(); - } -} - -void SimulatorDialog::updateDebugOutput() -{ - traceMutex.lock(); - while (!traceList.isEmpty()) { - QString text = traceList.takeFirst(); - traceBuffer.append(text); - // limit the size of traceBuffer - if (traceBuffer.size() > 10*1024) { - traceBuffer.remove(0, 1*1024); - } - if (DebugOut) { - DebugOut->traceCallback(QString(text)); - } - } - traceMutex.unlock(); -} - -void SimulatorDialog::luaReload() -{ - // force a reload of the lua environment - simulator->setLuaStateReloadPermanentScripts(); -} - -void SimulatorDialog::showHelp() -{ - QString helpText = tr("Simulator Controls:"); - helpText += ""; - helpText += tr(""); - QString keyTemplate = ""; - foreach (keymapHelp_t pair, keymapHelp) - helpText += keyTemplate.arg(pair.first, pair.second); - helpText += "
Key/MouseAction
%1
%2
"; - - QMessageBox * msgBox = new QMessageBox(this); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint); - msgBox->setStandardButtons( QMessageBox::Ok ); - msgBox->setWindowTitle(tr("Simulator Help")); - msgBox->setText(helpText); - msgBox->setModal(false); - msgBox->show(); -} - -#ifdef JOYSTICKS void SimulatorDialog::onjoystickAxisValueChanged(int axis, int value) { +#ifdef JOYSTICKS int stick; if (axis>=0 && axis<=8) { stick=jsmap[axis]; @@ -1225,5 +848,5 @@ void SimulatorDialog::onjoystickAxisValueChanged(int axis, int value) analogs[stick-5]->setValue(stickval); } } -} #endif +} diff --git a/companion/src/simulation/simulatordialog.h b/companion/src/simulation/simulatordialog.h index 9a2bca1a9..e2dc55cef 100644 --- a/companion/src/simulation/simulatordialog.h +++ b/companion/src/simulation/simulatordialog.h @@ -26,17 +26,11 @@ #include "radiodata.h" #include "simulator.h" -#include +#include #include -#include - -#define SIMULATOR_FLAGS_NOTX 0x01 // simulating a single model from Companion -#define SIMULATOR_FLAGS_STANDALONE 0x02 // started from stanalone simulator #define FLASH_DURATION 10 -#define CSWITCH_ON "QLabel { background-color: #4CC417 }" -#define CSWITCH_OFF "QLabel { }" #define CBEEP_ON "QLabel { background-color: #FF364E }" #define CBEEP_OFF "QLabel { }" @@ -45,9 +39,6 @@ void traceCb(const char * text); class Firmware; class SimulatorInterface; class SimulatedUIWidget; -class TelemetrySimulator; -class TrainerSimulator; -class DebugOutput; class RadioWidget; class RadioSwitchWidget; class VirtualJoystickWidget; @@ -57,7 +48,6 @@ class Joystick; class QWidget; class QSlider; -class QDial; class QLabel; class QFrame; @@ -67,7 +57,7 @@ namespace Ui { using namespace Simulator; -class SimulatorDialog : public QDialog +class SimulatorDialog : public QWidget { Q_OBJECT @@ -75,7 +65,6 @@ class SimulatorDialog : public QDialog explicit SimulatorDialog(QWidget * parent, SimulatorInterface *simulator, quint8 flags=0); virtual ~SimulatorDialog(); - void setRadioProfileId(int value); void setSdPath(const QString & sdPath); void setDataPath(const QString & dataPath); void setPaths(const QString & sdPath, const QString & dataPath); @@ -89,23 +78,22 @@ class SimulatorDialog : public QDialog void deleteTempData(); void saveState(); void setUiAreaStyle(const QString & style); - void traceCallback(const char * text); + void captureScreenshot(bool); + void setupJoysticks(); QString getSdPath() const { return sdCardPath; } QString getDataPath() const { return radioDataPath; } + QVector * getKeymapHelp() { return &keymapHelp; } public slots: void start(); void stop(); void restart(); + void shutdown(); private: - void setupUi(); + void setRadioProfileId(int value); void setupRadioWidgets(); - void setupOutputsDisplay(); - void setupGVarsDisplay(); - void setupJoysticks(); - QFrame * createLogicalSwitch(QWidget * parent, int switchNo, QVector & labels); void setupTimer(); void restoreRadioWidgetsState(); QList saveRadioWidgetsState(); @@ -122,24 +110,14 @@ class SimulatorDialog : public QDialog QTimer * timer; QString windowName; - QString traceBuffer; - QMutex traceMutex; - QList traceList; QVector keymapHelp; SimulatedUIWidget * radioUiWidget; VirtualJoystickWidget * vJoyLeft; VirtualJoystickWidget * vJoyRight; - TelemetrySimulator * TelemetrySimu; - TrainerSimulator * TrainerSimu; - DebugOutput * DebugOut; QVector switches; QVector analogs; - QVector logicalSwitchLabels; - QVector channelSliders; - QVector channelValues; - QVector gvarValues; QString sdCardPath; QString radioDataPath; @@ -163,8 +141,8 @@ class SimulatorDialog : public QDialog #endif private slots: - virtual void closeEvent(QCloseEvent *); - virtual void showEvent(QShowEvent *); + //virtual void showEvent(QShowEvent *); + //virtual void closeEvent(QCloseEvent *); virtual void mousePressEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void wheelEvent(QWheelEvent *event); @@ -174,16 +152,7 @@ class SimulatorDialog : public QDialog void onTrimReleased(); void onTrimSliderMoved(int which, int value); void centerSticks(); - void openTelemetrySimulator(); - void openTrainerSimulator(); - void openJoystickDialog(); - void openDebugOutput(); - void updateDebugOutput(); - void luaReload(); - void showHelp(); -#ifdef JOYSTICKS void onjoystickAxisValueChanged(int axis, int value); -#endif }; diff --git a/companion/src/simulation/simulatordialog.ui b/companion/src/simulation/simulatordialog.ui index 62bd4c713..181f4da1d 100644 --- a/companion/src/simulation/simulatordialog.ui +++ b/companion/src/simulation/simulatordialog.ui @@ -1,17 +1,17 @@ SimulatorDialog - + 0 0 - 651 - 528 + 448 + 302 - + 0 0 @@ -23,29 +23,26 @@ :/icon.png:/icon.png - - true + + - - - 5 - - - 5 - - - 5 - - - 5 - - + + 0 - - 3 + + 0 - + + 0 + + + 0 + + + 0 + + @@ -54,7 +51,7 @@ - QFrame::Panel + QFrame::StyledPanel QFrame::Raised @@ -62,414 +59,248 @@ 2 - + 5 - 7 + 0 5 - 6 + 7 - 6 + 0 - 5 + 10 - - + + - + 0 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - - - - - - 0 - 0 - + + QFrame::Panel - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - - - - - - 0 - 0 - + + QFrame::Raised - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 16 - - - 9 - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 0 - 20 - - - - - - - - - - - - 0 - 0 - + + 0 - 11 + 10 6 - 6 + 9 6 - 6 + 9 + + + + + 0 + 0 + + + + + 0 + + + 7 + + + 0 + + + 0 + + + 12 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 12 + + + 0 + + + 0 + + + + + 16 + + + 9 + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 0 + 30 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + + + leftStickWidget + rightStickWidget + radioWidgetsVC + + - - - - true - + + - + 0 0 - - - 0 - 200 - - - QTabWidget::pane, -QStackedWidget { - padding: 0px; - margin: 0px; -} -QTabWidget::tab-bar{ - margin-bottom: 0px; - padding-bottom: 0px; -} + - - 0 - - - false - - - - background-color: qlineargradient(spread:reflect, x1:0.22, y1:0, x2:0.55065, y2:0.54, stop:0 rgba(7, 7, 7, 250), stop:0.864407 rgba(66, 66, 66, 255)); -margin: 0; -padding: 0; - - - Radio Simulator - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - 0 - 0 - - - + - 3 + 0 - 1 + 0 - 1 + 0 - 1 + 0 - 1 + 0 - - - Show Help/Keymap (F1) + + + Qt::Vertical - - Show Help/Keymap + + QSizePolicy::Maximum - - Keymap + + + 20 + 40 + - - F1 - - - help - - - - - - - Open the Joystick Settings window (F3) - - - Joystick Configuration - - - Joystick Config. - - - F3 - - - joystick - - - - - - - Open Telemetry Simulator window (F4) - - - Telemetry Simulator - - - Telemetry Sim. - - - F4 - - - telemetry - - - - - - - Open Trainer Simulator window (F5) - - - Trainer Simulator - - - Trainer Sim. - - - F5 - - - trainer - - - - - - - Open Debug Console window (F6) - - - Debug Console - - - Debug Console - - - F6 - - - debug - - - - - - - Reload Lua Scripts (F7) - - - Reload Lua Scripts - - - Reload Lua - - - F7 - - - reloadLua - - - - - - - Save a screenshot of the radio LCD screen (F8) - - - LCD Screenshot - - - Screenshot - - - F8 - - - lcdScreenshot - - - - - - - Reload all radio data (F9) - - - Reload Radio Data - - - Reload Radio - - - F9 - - - reloadradio - - + diff --git a/companion/src/simulation/simulatormainwindow.cpp b/companion/src/simulation/simulatormainwindow.cpp new file mode 100644 index 000000000..b57a02479 --- /dev/null +++ b/companion/src/simulation/simulatormainwindow.cpp @@ -0,0 +1,350 @@ +/* + * 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 "simulatormainwindow.h" +#include "ui_simulatormainwindow.h" + +#include "appdata.h" +#include "debugoutput.h" +#include "radiooutputswidget.h" +#include "simulatordialog.h" +#include "simulatorinterface.h" +#include "telemetrysimu.h" +#include "trainersimu.h" +#ifdef JOYSTICKS +#include "joystickdialog.h" +#endif + +#include + +extern AppData g; // ensure what "g" means + +const quint16 SimulatorMainWindow::m_savedUiStateVersion = 1; + +SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, SimulatorInterface * simulator, quint8 flags, Qt::WindowFlags wflags) : + QMainWindow(parent, wflags), + m_simulator(simulator), + ui(new Ui::SimulatorMainWindow), + m_simulatorWidget(NULL), + m_consoleWidget(NULL), + m_outputsWidget(NULL), + m_simulatorDockWidget(NULL), + m_consoleDockWidget(NULL), + m_telemetryDockWidget(NULL), + m_trainerDockWidget(NULL), + m_outputsDockWidget(NULL), + m_radioProfileId(g.sessionId()), + m_firstShow(true), + m_showRadioTitlebar(true), + m_showMenubar(true) +{ + ui->setupUi(this); + ui->centralwidget->hide(); + +//#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) +// setDockOptions(dockOptions() | QMainWindow::GroupedDragging); +//#endif + + setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); + + m_simulatorWidget = new SimulatorDialog(this, m_simulator, flags); + + m_simulatorDockWidget = new QDockWidget(m_simulatorWidget->windowTitle(), this); + m_simulatorDockWidget->setObjectName("RADIO_SIMULATOR"); + m_simulatorDockWidget->setWidget(m_simulatorWidget); + m_simulatorDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + addDockWidget(Qt::BottomDockWidgetArea, m_simulatorDockWidget); + // setCentralWidget(m_simulatorWidget); + + createDockWidgets(); + + ui->actionReloadLua->setIcon(SimulatorIcon("reload_script")); + ui->actionReloadRadioData->setIcon(SimulatorIcon("restart")); + ui->actionJoystickSettings->setIcon(SimulatorIcon("joystick_settings")); + ui->actionScreenshot->setIcon(SimulatorIcon("camera")); + ui->actionShowKeymap->setIcon(SimulatorIcon("info")); + ui->actionToggleRadioTitle->setIcon(ui->toolBar->toggleViewAction()->icon()); + ui->actionToggleMenuBar->setIcon(ui->toolBar->toggleViewAction()->icon()); + + ui->toolBar->toggleViewAction()->setShortcut(tr("Alt+T")); + ui->toolBar->setIconSize(SimulatorIcon::toolbarIconSize(g.iconSize())); + ui->toolBar->insertSeparator(ui->actionReloadLua); + + // add these to this window directly to maintain shorcuts when menubar is hidden + addAction(ui->toolBar->toggleViewAction()); + addAction(ui->actionToggleMenuBar); + + ui->menuView->addSeparator(); + ui->menuView->addAction(ui->toolBar->toggleViewAction()); + ui->menuView->addAction(ui->actionToggleMenuBar); + ui->menuView->addAction(ui->actionToggleRadioTitle); + + // Hide some actions based on board capabilities. + Firmware * firmware = getCurrentFirmware(); + if(!firmware->getCapability(Capability(LuaInputsPerScript))) + ui->actionReloadLua->setDisabled(true); + if (!firmware->getCapability(Capability(SportTelemetry))) + m_telemetryDockWidget->toggleViewAction()->setDisabled(true); +#ifndef JOYSTICKS + ui->actionJoystickSettings->setDisabled(true); +#endif + + // Add radio-specific help text from simulator widget + foreach (keymapHelp_t item, *m_simulatorWidget->getKeymapHelp()) + m_keymapHelp.append(item); + + restoreUiState(); + + setStyleSheet(SimulatorStyle::styleSheet()); + + connect(ui->actionShowKeymap, &QAction::triggered, this, &SimulatorMainWindow::showHelp); + connect(ui->actionJoystickSettings, &QAction::triggered, this, &SimulatorMainWindow::openJoystickDialog); + connect(ui->actionReloadLua, &QAction::triggered, this, &SimulatorMainWindow::luaReload); + connect(ui->actionToggleRadioTitle, &QAction::toggled, this, &SimulatorMainWindow::showRadioTitlebar); + connect(ui->actionToggleMenuBar, &QAction::toggled, this, &SimulatorMainWindow::toggleMenuBar); + if (m_simulatorWidget) { + connect(ui->actionScreenshot, &QAction::triggered, m_simulatorWidget, &SimulatorDialog::captureScreenshot); + connect(ui->actionReloadRadioData, &QAction::triggered, m_simulatorWidget, &SimulatorDialog::restart); + } + if (m_outputsWidget) + connect(ui->actionReloadRadioData, &QAction::triggered, m_outputsWidget, &RadioOutputsWidget::restart); + + // this sets the radio widget to a fixed width while docked, freeform while floating + connect(m_simulatorDockWidget, &QDockWidget::topLevelChanged, [this](bool top) { + if (top) + m_simulatorWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + else + m_simulatorWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + }); + +} + +SimulatorMainWindow::~SimulatorMainWindow() +{ + delete ui; +} + +void SimulatorMainWindow::closeEvent(QCloseEvent *) +{ + saveUiState(); + + if (m_consoleDockWidget) + delete m_consoleDockWidget; + if (m_telemetryDockWidget) + delete m_telemetryDockWidget; + if (m_trainerDockWidget) + delete m_trainerDockWidget; + if (m_outputsDockWidget) + delete m_outputsDockWidget; + if (m_simulatorDockWidget) + delete m_simulatorDockWidget; +} + +void SimulatorMainWindow::changeEvent(QEvent *e) +{ + QMainWindow::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +QMenu* SimulatorMainWindow::createPopupMenu(){ + QMenu * menu = QMainWindow::createPopupMenu(); + menu->addAction(ui->actionToggleMenuBar); + menu->addAction(ui->actionToggleRadioTitle); + return menu; +} + +void SimulatorMainWindow::saveUiState() +{ + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << m_savedUiStateVersion << saveState() << m_showMenubar << m_showRadioTitlebar; + + SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions(); + opts.windowState = state; + opts.windowGeometry = saveGeometry(); + g.profile[m_radioProfileId].simulatorOptions(opts); +} + +void SimulatorMainWindow::restoreUiState() +{ + quint16 ver = 0; + QByteArray windowState; + QByteArray state = g.profile[m_radioProfileId].simulatorOptions().windowState; + QDataStream stream(state); + + stream >> ver; + if (ver && ver <= m_savedUiStateVersion) + stream >> windowState >> m_showMenubar >> m_showRadioTitlebar; + + restoreState(windowState); + restoreGeometry(g.profile[m_radioProfileId].simulatorOptions().windowGeometry); + showRadioTitlebar(m_showRadioTitlebar); + toggleMenuBar(m_showMenubar); +} + +bool SimulatorMainWindow::setRadioData(RadioData * radioData) +{ + return m_simulatorWidget->setRadioData(radioData); +} + +bool SimulatorMainWindow::useTempDataPath(bool deleteOnClose, bool saveOnClose) +{ + return m_simulatorWidget->useTempDataPath(deleteOnClose, saveOnClose); +} + +bool SimulatorMainWindow::setOptions(SimulatorOptions & options, bool withSave) +{ + return m_simulatorWidget->setOptions(options, withSave); +} + +void SimulatorMainWindow::start() +{ + if (m_consoleWidget) + m_consoleWidget->start(); + if (m_simulatorWidget) + m_simulatorWidget->start(); + if (m_outputsWidget) + m_outputsWidget->start(); +} + +void SimulatorMainWindow::createDockWidgets() +{ + if (!m_outputsDockWidget) { + SimulatorIcon icon("radio_outputs"); + m_outputsDockWidget = new QDockWidget(tr("Radio Outputs"), this); + m_outputsWidget = new RadioOutputsWidget(m_simulator, getCurrentFirmware(), this); + m_outputsWidget->setWindowIcon(icon); + m_outputsDockWidget->setWidget(m_outputsWidget); + m_outputsDockWidget->setObjectName("OUTPUTS"); + addTool(m_outputsDockWidget, Qt::BottomDockWidgetArea, icon, QKeySequence(tr("F2"))); + } + + if (!m_telemetryDockWidget) { + SimulatorIcon icon("telemetry"); + m_telemetryDockWidget = new QDockWidget(tr("Telemetry Simulator"), this); + TelemetrySimulator * telem = new TelemetrySimulator(this, m_simulator); + telem->setWindowIcon(icon); + m_telemetryDockWidget->setWidget(telem); + m_telemetryDockWidget->setObjectName("TELEMETRY_SIMULATOR"); + addTool(m_telemetryDockWidget, Qt::LeftDockWidgetArea, icon, QKeySequence(tr("F4"))); + } + + if (!m_trainerDockWidget) { + SimulatorIcon icon("trainer"); + m_trainerDockWidget = new QDockWidget(tr("Trainer Simulator"), this); + TrainerSimulator * trainer = new TrainerSimulator(this, m_simulator); + trainer->setWindowIcon(icon); + m_trainerDockWidget->setWidget(trainer); + m_trainerDockWidget->setObjectName("TRAINER_SIMULATOR"); + addTool(m_trainerDockWidget, Qt::TopDockWidgetArea, icon, QKeySequence(tr("F5"))); + } + + if (!m_consoleDockWidget) { + SimulatorIcon icon("console"); + m_consoleDockWidget = new QDockWidget(tr("Debug Output"), this); + m_consoleWidget = new DebugOutput(this, m_simulator); + m_consoleWidget->setWindowIcon(icon); + m_consoleDockWidget->setWidget(m_consoleWidget); + m_consoleDockWidget->setObjectName("CONSOLE"); + addTool(m_consoleDockWidget, Qt::RightDockWidgetArea, icon, QKeySequence(tr("F6"))); + } +} + +void SimulatorMainWindow::addTool(QDockWidget * widget, Qt::DockWidgetArea area, QIcon icon, QKeySequence shortcut) +{ + QAction* tempAction = widget->toggleViewAction(); + tempAction->setIcon(icon); + tempAction->setShortcut(shortcut); + ui->menuView->addAction(tempAction); + ui->toolBar->insertAction(ui->actionReloadLua, tempAction); + widget->setAllowedAreas(Qt::AllDockWidgetAreas); + widget->setWindowIcon(icon); + addDockWidget(area, widget); + widget->hide(); + widget->setFloating(true); +} + +void SimulatorMainWindow::showRadioTitlebar(bool show) +{ + m_showRadioTitlebar = show; + if (show) { + QWidget * w = m_simulatorDockWidget->titleBarWidget(); + if (w) + w->deleteLater(); + m_simulatorDockWidget->setTitleBarWidget(0); + } + else { + m_simulatorDockWidget->setTitleBarWidget(new QWidget()); + } + if (ui->actionToggleRadioTitle->isChecked() != show) + ui->actionToggleRadioTitle->setChecked(show); + +} + +void SimulatorMainWindow::toggleMenuBar(bool show) +{ + m_showMenubar = show; + ui->menubar->setVisible(show); + if (ui->actionToggleMenuBar->isChecked() != show) + ui->actionToggleMenuBar->setChecked(show); +} + +void SimulatorMainWindow::luaReload(bool) +{ + // force a reload of the lua environment + if (m_simulator) + m_simulator->setLuaStateReloadPermanentScripts(); +} + +void SimulatorMainWindow::openJoystickDialog(bool) +{ +#ifdef JOYSTICKS + joystickDialog * jd = new joystickDialog(this); + if (jd->exec() == QDialog::Accepted && m_simulatorWidget) + m_simulatorWidget->setupJoysticks(); + jd->deleteLater(); +#endif +} + +void SimulatorMainWindow::showHelp(bool show) +{ + QString helpText = tr("Simulator Controls:"); + helpText += ""; + helpText += tr(""); + QString keyTemplate = ""; + foreach (keymapHelp_t pair, m_keymapHelp) + helpText += keyTemplate.arg(pair.first, pair.second); + helpText += "
Key/MouseAction
%1
%2
"; + + QMessageBox * msgBox = new QMessageBox(this); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint); + msgBox->setStandardButtons( QMessageBox::Ok ); + msgBox->setWindowTitle(tr("Simulator Help")); + msgBox->setText(helpText); + msgBox->setModal(false); + msgBox->show(); +} diff --git a/companion/src/simulation/simulatormainwindow.h b/companion/src/simulation/simulatormainwindow.h new file mode 100644 index 000000000..db5bba39e --- /dev/null +++ b/companion/src/simulation/simulatormainwindow.h @@ -0,0 +1,99 @@ +/* + * 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 SIMULATORMAINWINDOW_H +#define SIMULATORMAINWINDOW_H + +#include "simulator.h" + +#include +#include +#include + +class DebugOutput; +class RadioData; +class RadioOutputsWidget; +class SimulatorDialog; +class SimulatorInterface; +class TrainerSimulator; +class TelemetrySimulator; + +class QKeySequence; + +namespace Ui { +class SimulatorMainWindow; +} + +using namespace Simulator; + +class SimulatorMainWindow : public QMainWindow +{ + Q_OBJECT + + public: + explicit SimulatorMainWindow(QWidget * parent, SimulatorInterface * simulator, quint8 flags=0, Qt::WindowFlags wflags = Qt::WindowFlags()); + ~SimulatorMainWindow(); + + bool setRadioData(RadioData * radioData); + bool useTempDataPath(bool deleteOnClose = true, bool saveOnClose = false); + bool setOptions(SimulatorOptions & options, bool withSave = true); + QMenu * createPopupMenu(); + + public slots: + void start(); + void showRadioTitlebar(bool show); + void toggleMenuBar(bool show); + + protected slots: + virtual void closeEvent(QCloseEvent *); + virtual void changeEvent(QEvent *e); + void restoreUiState(); + void saveUiState(); + void luaReload(bool); + void openJoystickDialog(bool); + void showHelp(bool show); + + protected: + void createDockWidgets(); + void addTool(QDockWidget * widget, Qt::DockWidgetArea area, QIcon icon = QIcon(), QKeySequence shortcut = QKeySequence()); + + SimulatorInterface * m_simulator; + + Ui::SimulatorMainWindow * ui; + SimulatorDialog * m_simulatorWidget; + DebugOutput * m_consoleWidget; + RadioOutputsWidget * m_outputsWidget; + + QDockWidget * m_simulatorDockWidget; + QDockWidget * m_consoleDockWidget; + QDockWidget * m_telemetryDockWidget; + QDockWidget * m_trainerDockWidget; + QDockWidget * m_outputsDockWidget; + + QVector m_keymapHelp; + int m_radioProfileId; + bool m_firstShow; + bool m_showRadioTitlebar; + bool m_showMenubar; + + const static quint16 m_savedUiStateVersion; +}; + +#endif // SIMULATORMAINWINDOW_H diff --git a/companion/src/simulation/simulatormainwindow.ui b/companion/src/simulation/simulatormainwindow.ui new file mode 100644 index 000000000..1520cd34c --- /dev/null +++ b/companion/src/simulation/simulatormainwindow.ui @@ -0,0 +1,183 @@ + + + SimulatorMainWindow + + + + 0 + 0 + 644 + 371 + + + + OpenTx Simulator + + + + :/icon.png:/icon.png + + + true + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks + + + + + + 0 + 0 + 644 + 21 + + + + false + + + + View + + + + + Reload... + + + + + + + Tools + + + + + + + + + + + + Toolbar + + + LeftToolBarArea + + + false + + + + + + + + + + + + :/images/simulator/icons/svg/reload_script.svg:/images/simulator/icons/svg/reload_script.svg + + + Reload Lua Scripts + + + Reload the Lua environment on the simulated radio. + + + F7 + + + + + + :/images/simulator/icons/svg/restart.svg:/images/simulator/icons/svg/restart.svg + + + Reload Radio Data + + + Reload all radio data without restarting the simulator. + + + F9 + + + + + + :/images/simulator/icons/svg/info.svg:/images/simulator/icons/svg/info.svg + + + Key Mapping + + + Show keyboard maping reference. + + + F1 + + + + + + :/images/simulator/icons/svg/joystick_settings.svg:/images/simulator/icons/svg/joystick_settings.svg + + + Joystick Settings + + + Open joystick configuration settings dialog. + + + F3 + + + + + + :/images/simulator/icons/svg/camera.svg:/images/simulator/icons/svg/camera.svg + + + LCD Screenshot + + + Save a screenshot of the current simulated LCD screen. + + + F8 + + + + + true + + + Radio Titlebar + + + <html><head/><body><p>The title bar on the main radio window allows you to undock it or move it around separately from the other windows. Hiding the title makes a more compact and streamlined appearance.</p></body></html> + + + + + true + + + Menu Bar + + + Show or hide the top menu bar. + + + Alt+M + + + + + + diff --git a/companion/src/simulation/simulatorstartupdialog.ui b/companion/src/simulation/simulatorstartupdialog.ui index de6ac01c0..ba97d2c75 100644 --- a/companion/src/simulation/simulatorstartupdialog.ui +++ b/companion/src/simulation/simulatorstartupdialog.ui @@ -6,10 +6,16 @@ 0 0 - 488 - 219 + 550 + 217
+ + + 550 + 0 + + OpenTX Simulator - Startup Options @@ -22,13 +28,28 @@
+ + + 0 + 0 + + Simulator Startup Options: + + QLayout::SetNoConstraint + + + QFormLayout::AllNonFixedFieldsGrow + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + 5 @@ -56,6 +77,12 @@ + + + 0 + 0 + + Existing radio profiles are shown here.<br /> Create or edit profiles using the Companion application. @@ -71,6 +98,12 @@ Create or edit profiles using the Companion application. + + + 0 + 0 + + Existing radio simulators are shown here.<br /> The radio type specified in the selected profile is used by default. @@ -107,6 +140,12 @@ The radio type specified in the selected profile is used by default. + + + 0 + 0 + + 4 @@ -140,7 +179,7 @@ The radio type specified in the selected profile is used by default. ... - + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png @@ -153,6 +192,12 @@ The radio type specified in the selected profile is used by default. + + + 0 + 0 + + 4 @@ -186,7 +231,7 @@ New folder(s) with default radio/model will be created here if necessary.... - + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png @@ -199,6 +244,12 @@ New folder(s) with default radio/model will be created here if necessary. + + + 0 + 0 + + 4 @@ -232,7 +283,7 @@ The default is configured in the chosen Radio Profile. ... - + :/themes/monoblue/16/open.png:/themes/monoblue/16/open.png @@ -245,6 +296,12 @@ The default is configured in the chosen Radio Profile. + + + 0 + 0 + + Select which of the data sources (File/Folder/SD Card) you would like to start the simulator with. @@ -312,9 +369,7 @@ The default is configured in the chosen Radio Profile. - - - + buttonBox diff --git a/companion/src/simulation/telemetrysimu.cpp b/companion/src/simulation/telemetrysimu.cpp index 8ca0eca9b..46ff5f3e9 100644 --- a/companion/src/simulation/telemetrysimu.cpp +++ b/companion/src/simulation/telemetrysimu.cpp @@ -25,24 +25,20 @@ #include "radio/src/telemetry/frsky.h" TelemetrySimulator::TelemetrySimulator(QWidget * parent, SimulatorInterface * simulator): - QDialog(parent), + QWidget(parent), ui(new Ui::TelemetrySimulator), simulator(simulator) { ui->setupUi(this); - QPoint dialogCenter = mapToGlobal(rect().center()); - QPoint parentWindowCenter = parent->window()->mapToGlobal( - parent->window()->rect().center()); - move(parentWindowCenter - dialogCenter); - timer = new QTimer(this); + timer->setInterval(10); connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); logTimer = new QTimer(this); connect(logTimer, SIGNAL(timeout()), this, SLOT(onLogTimerEvent())); - connect(ui->Simulate, SIGNAL(clicked(bool)), this, SLOT(onSimulateToggled(bool))); + connect(ui->Simulate, SIGNAL(toggled(bool)), this, SLOT(onSimulateToggled(bool))); connect(ui->loadLogFile, SIGNAL(released()), this, SLOT(onLoadLogFile())); connect(ui->play, SIGNAL(released()), this, SLOT(onPlay())); connect(ui->rewind, SIGNAL(clicked()), this, SLOT(onRewind())); @@ -76,7 +72,7 @@ TelemetrySimulator::~TelemetrySimulator() void TelemetrySimulator::onSimulateToggled(bool isChecked) { if (isChecked) { - timer->start(10); + timer->start(); } else { timer->stop(); @@ -197,9 +193,6 @@ void TelemetrySimulator::showEvent(QShowEvent *event) ui->rxbt_ratio->setValue(simulator->getSensorRatio(BATT_ID) / 10.0); ui->A1_ratio->setValue(simulator->getSensorRatio(ADC1_ID) / 10.0); ui->A2_ratio->setValue(simulator->getSensorRatio(ADC2_ID) / 10.0); - - ui->Simulate->setChecked(true); - onSimulateToggled(true); // not sure why this doesn't fire automatically } void setSportPacketCrc(uint8_t * packet) @@ -216,7 +209,7 @@ void setSportPacketCrc(uint8_t * packet) //TRACE("crc set: %x", packet[FRSKY_SPORT_PACKET_SIZE-1]); } -uint8_t getBit(uint8_t position, uint8_t value) +uint8_t getBit(uint8_t position, uint8_t value) { return (value & (uint8_t)(1 << position)) ? 1 : 0; } @@ -224,7 +217,7 @@ uint8_t getBit(uint8_t position, uint8_t value) bool generateSportPacket(uint8_t * packet, uint8_t dataId, uint8_t prim, uint16_t appId, uint32_t data) { if (dataId > 0x1B ) return false; - + // generate Data ID field uint8_t bit5 = getBit(0, dataId) ^ getBit(1, dataId) ^ getBit(2, dataId); uint8_t bit6 = getBit(2, dataId) ^ getBit(3, dataId) ^ getBit(4, dataId); @@ -483,7 +476,7 @@ uint32_t TelemetrySimulator::FlvssEmulator::setAllCells_GetNextPair(double cellV // encode the double values into telemetry format encodeAllCells(); - // return the value for the current pair + // return the value for the current pair uint32_t cellData = 0; if (nextCellNum >= numCells) { nextCellNum = 0; @@ -644,7 +637,7 @@ TelemetrySimulator::LogPlaybackController::LogPlaybackController(Ui::TelemetrySi colToFuncMap.insert("AccX(g)", ACCX); colToFuncMap.insert("AccY(g)", ACCY); colToFuncMap.insert("AccZ(g)", ACCZ); - + // ACCX Y and Z } @@ -839,7 +832,7 @@ double TelemetrySimulator::LogPlaybackController::convertDegMin(QString input) QString TelemetrySimulator::LogPlaybackController::convertGPS(QString input) { - // input format is DDmm.mmmmH DDDmm.mmmmH (longitude latitude - degrees (2 places) minutes (2 places) decimal minutes (4 places)) + // input format is DDmm.mmmmH DDDmm.mmmmH (longitude latitude - degrees (2 places) minutes (2 places) decimal minutes (4 places)) QStringList lonLat = input.simplified().split(' '); if (lonLat.count() < 2) { return ""; // invalid format diff --git a/companion/src/simulation/telemetrysimu.h b/companion/src/simulation/telemetrysimu.h index b7387f39a..bd0096ba8 100644 --- a/companion/src/simulation/telemetrysimu.h +++ b/companion/src/simulation/telemetrysimu.h @@ -22,7 +22,7 @@ #define _TELEMETRYSIMU_H_ #include -#include +#include #include #include #include @@ -37,102 +37,19 @@ namespace Ui { class TelemetrySimulator; } - -class TelemetrySimulator : public QDialog +class TelemetrySimulator : public QWidget { Q_OBJECT public: + explicit TelemetrySimulator(QWidget * parent, SimulatorInterface * simulator); virtual ~TelemetrySimulator(); - protected: + protected slots: + virtual void closeEvent(QCloseEvent *event); virtual void showEvent(QShowEvent *event); - - private: - class LogPlaybackController - { - public: - LogPlaybackController(Ui::TelemetrySimulator * ui); - bool isReady(); - void loadLogFile(); - void play(); - void stop(); - void rewind(); - void stepForward(bool focusOnStop); - void stepBack(); - void updatePositionLabel(int32_t percentage); - void setUiDataValues(); - double logFrequency; // in seconds - private: - enum CONVERT_TYPE { - RXBT_V, - RSSI, - SWR, - A1, - A2, - A3, - A4, - T1_DEGC, - T1_DEGF, - T2_DEGC, - T2_DEGF, - RPM, - FUEL, - VSPD_MS, - VSPD_FS, - ALT_FEET, - ALT_METERS, - FASV, - FASC, - CELS_GRE, - ASPD_KTS, - ASPD_KMH, - ASPD_MPH, - GALT_FEET, - GALT_METERS, - GSPD_KNTS, - GSPD_KMH, - GSPD_MPH, - GHDG_DEG, - GDATE, - G_LATLON, - ACCX, - ACCY, - ACCZ, - FUEL_QTY, - }; - QMap colToFuncMap; // contains all 'known' column headings and how they are to be processed - Ui::TelemetrySimulator * ui; - struct DATA_TO_FUNC_XREF { - CONVERT_TYPE functionIndex; - int32_t dataIndex; - }; - QStringList csvRecords; // contents of the log file (one string per line); - QStringList columnNames; - QList supportedCols; - int32_t recordIndex; - double convertFeetToMeters(QString input); - double convertFahrenheitToCelsius(QString input); - QString convertGPSDate(QString input); - QString convertGPS(QString input); - void addColumnHash(QString key, CONVERT_TYPE functionIndex); - double convertDegMin(QString input); - bool stepping; - QDateTime parseTransmittterTimestamp(QString row); - void calcLogFrequency(); - }; - -private: - Ui::TelemetrySimulator * ui; - QTimer * timer; - QTimer * logTimer; - SimulatorInterface *simulator; - void generateTelemetryFrame(); - TelemetrySimulator::LogPlaybackController *logPlayback; - - private slots: void onSimulateToggled(bool isChecked); void onTimerEvent(); void onLogTimerEvent(); @@ -145,49 +62,138 @@ private: void onPositionIndicatorChanged(int value); void onReplayRateChanged(int value); + protected: + + Ui::TelemetrySimulator * ui; + QTimer * timer; + QTimer * logTimer; + SimulatorInterface *simulator; + void generateTelemetryFrame(); + + // protected classes follow + + class LogPlaybackController + { + public: + LogPlaybackController(Ui::TelemetrySimulator * ui); + bool isReady(); + void loadLogFile(); + void play(); + void stop(); + void rewind(); + void stepForward(bool focusOnStop); + void stepBack(); + void updatePositionLabel(int32_t percentage); + void setUiDataValues(); + double logFrequency; // in seconds + + private: + enum CONVERT_TYPE { + RXBT_V, + RSSI, + SWR, + A1, + A2, + A3, + A4, + T1_DEGC, + T1_DEGF, + T2_DEGC, + T2_DEGF, + RPM, + FUEL, + VSPD_MS, + VSPD_FS, + ALT_FEET, + ALT_METERS, + FASV, + FASC, + CELS_GRE, + ASPD_KTS, + ASPD_KMH, + ASPD_MPH, + GALT_FEET, + GALT_METERS, + GSPD_KNTS, + GSPD_KMH, + GSPD_MPH, + GHDG_DEG, + GDATE, + G_LATLON, + ACCX, + ACCY, + ACCZ, + FUEL_QTY, + }; + + struct DATA_TO_FUNC_XREF { + CONVERT_TYPE functionIndex; + int32_t dataIndex; + }; + + double convertFeetToMeters(QString input); + double convertFahrenheitToCelsius(QString input); + QString convertGPSDate(QString input); + QString convertGPS(QString input); + void addColumnHash(QString key, CONVERT_TYPE functionIndex); + double convertDegMin(QString input); + QDateTime parseTransmittterTimestamp(QString row); + void calcLogFrequency(); + + QMap colToFuncMap; // contains all 'known' column headings and how they are to be processed + Ui::TelemetrySimulator * ui; + QStringList csvRecords; // contents of the log file (one string per line); + QStringList columnNames; + QList supportedCols; + int32_t recordIndex; + bool stepping; + }; // LogPlaybackController + + LogPlaybackController *logPlayback; - private: // private classes follow class FlvssEmulator { - public: + public: uint32_t setAllCells_GetNextPair(double cellValues[6]); static const uint32_t MAXCELLS = 6; - private: - void encodeAllCells(); - void splitIntoCells(double totalVolts); - static uint32_t encodeCellPair(uint8_t cellNum, uint8_t firstCellNo, double cell1, double cell2); - double cellFloats[6]; - uint32_t nextCellNum; - uint32_t numCells; - uint32_t cellData1; - uint32_t cellData2; - uint32_t cellData3; - }; + + private: + void encodeAllCells(); + void splitIntoCells(double totalVolts); + static uint32_t encodeCellPair(uint8_t cellNum, uint8_t firstCellNo, double cell1, double cell2); + double cellFloats[6]; + uint32_t nextCellNum; + uint32_t numCells; + uint32_t cellData1; + uint32_t cellData2; + uint32_t cellData3; + }; // FlvssEmulator class GPSEmulator { - public: - GPSEmulator(); - uint32_t getNextPacketData(uint32_t packetType); - void setGPSDateTime(QString dateTime); - void setGPSLatLon(QString latLon); - void setGPSCourse(double course); - void setGPSSpeedKMH(double speed); - void setGPSAltitude(double altitude); - private: - QDateTime dt; - bool sendLat; - bool sendDate; - double lat; - double lon; - double course; - double speedKNTS; - double altitude; // in meters - uint32_t encodeLatLon(double latLon, bool isLat); - uint32_t encodeDateTime(uint8_t yearOrHour, uint8_t monthOrMinute, uint8_t dayOrSecond, bool isDate); - }; + public: + GPSEmulator(); + uint32_t getNextPacketData(uint32_t packetType); + void setGPSDateTime(QString dateTime); + void setGPSLatLon(QString latLon); + void setGPSCourse(double course); + void setGPSSpeedKMH(double speed); + void setGPSAltitude(double altitude); -}; + private: + QDateTime dt; + bool sendLat; + bool sendDate; + double lat; + double lon; + double course; + double speedKNTS; + double altitude; // in meters + uint32_t encodeLatLon(double latLon, bool isLat); + uint32_t encodeDateTime(uint8_t yearOrHour, uint8_t monthOrMinute, uint8_t dayOrSecond, bool isDate); + }; // GPSEmulator + +}; // TelemetrySimulator #endif // _TELEMETRYSIMU_H_ diff --git a/companion/src/simulation/telemetrysimu.ui b/companion/src/simulation/telemetrysimu.ui index 0c897fb1c..fbc909e2a 100644 --- a/companion/src/simulation/telemetrysimu.ui +++ b/companion/src/simulation/telemetrysimu.ui @@ -1,13 +1,13 @@ TelemetrySimulator - + 0 0 - 759 - 539 + 715 + 484 @@ -18,1777 +18,2943 @@ Telemetry Simulator - - - - 10 - 50 - 101 - 31 - + + + 7 - - - 12 - + + 7 - - When enabled, sends any non-blank values as simulated telemetry data. + + 7 - - Simulate + + 6 - - - - - 120 - 10 - 621 - 111 - + + 10 - - - 8 - + + 7 - - Replay SD Log File - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - 410 - 10 - 201 - 33 - - - - - QLayout::SetMinimumSize + + + + + 0 + 0 + - - - - false - - - - 30 - 16777215 - - - - Qt::StrongFocus - - - |> - - - false - - - true - - - false - - - - - - - false - - - - 30 - 16777215 - - - - Qt::StrongFocus - - - <| - - - false - - - true - - - false - - - - - - - false - - - - 0 - 0 - - - - - 30 - 16777215 - - - - Qt::StrongFocus - - - > - - - true - - - true - - - false - - - - - - - false - - - - 0 - 0 - - - - - 30 - 16777215 - - - - Qt::StrongFocus - - - <- - - - false - - - true - - - false - - - false - - - - - - - false - - - - 30 - 16777215 - - - - Qt::StrongFocus - - - X - - - true - - - true - - - false - - - - - - - - - 100 - 30 - 201 - 21 - - - - - 8 - - - - No Log File Currently Loaded - - - - - - 10 - 30 - 75 - 23 - - - - Load - - - false - - - - - false - - - - 410 - 50 - 201 - 16 - - - - Qt::Horizontal - - - - - false - - - - 150 - 70 - 161 - 22 - - - - 8 - - - 1 - - - 4 - - - Qt::Horizontal - - - - - - 140 - 90 - 181 - 20 - - - - - 8 - - - - Replay rate (1/5 -> 5x) - - - Qt::AlignCenter - - - - - - 410 - 70 - 191 - 31 - - - - - 8 - - - - Qt::LeftToRight - - - Row # -Timestamp - - - Qt::AlignCenter - - - - - - - 10 - 130 - 741 - 401 - - - - - - - - 8 - + + + 12 + + + + When enabled, sends any non-blank values as simulated telemetry data. + + + Simulate + + + + + + + + 0 + 0 + + + + + 8 + + + + Replay SD Log File + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + 0 - - RxBt + + 3 - - - - - - - 8 - + + 0 - - - - - - + + 8 - - 2 + + Replay rate + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Load + + + false + + + + + + + false + + + + 0 + 0 + - 16777215.000000000000000 + 8 - - 0.100000000000000 + + 1 - 5.200000000000000 + 4 + + + Qt::Horizontal - - - - - 8 - + + + + 4 - - 2 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - - - 8 - - - - V / ratio - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - VFAS - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - Volts - - - - - - - - 8 - - - - RSSI - - - - - - - - 8 - - - - - - - - - 8 - - - - 75 - - - - - - - - 8 - - - - Db - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Curr - - - - - - - - 8 - - - - - - - - - 8 - - - - 1 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - Amps - - - - - - - - 8 - - - - SWR - - - - - - - - 8 - - - - - - - - - 8 - - - - 30 - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Cels - - - - - - - - 8 - - - - - - - - - - - 8 - - - - 0.100000000000000 - - - - - - - - 8 - - - - 0.100000000000000 - - - - - - - - 8 - - - - 0.100000000000000 - - - - - - - - 8 - - - - 0.100000000000000 - - + + + + QLayout::SetMinimumSize + + + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + |> + + + false + + + true + + + false + + + + + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + <| + + + false + + + true + + + false + + + + + + + false + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + > + + + true + + + true + + + false + + + + + + + false + + + + 0 + 0 + + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + <- + + + false + + + true + + + false + + + false + + + + + + + false + + + + 30 + 16777215 + + + + Qt::StrongFocus + + + X + + + true + + + true + + + false + + + + + + + + + false + + + Qt::Horizontal + + + + + + + + 8 + + + + Qt::LeftToRight + + + Row # +Timestamp + + + Qt::AlignCenter + + + + - - - - 8 - + + + + 0 + 0 + - - 0.100000000000000 + + 1/5x + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + 0 + 0 + + + + 5x + + + + + 8 - - 0.100000000000000 + + No Log File Currently Loaded - - - - - - 8 - + + + + + + + 0 + 1 + + + + + 16 - - Volts + + 0 - - - - - - - 8 - + + 0 - - A1 + + 0 - - - - - - - 8 - + + 0 - - - - - - - - 8 - + + + + 0 + 0 + - - 2 + + QFrame::NoFrame - - 16777215.000000000000000 + + Qt::ScrollBarAlwaysOff - - 0.100000000000000 + + true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 346 + 370 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 4 + + + 0 + + + 3 + + + 5 + + + + + + 0 + 0 + + + + + 8 + + + + -16777215 + + + 16777215 + + + 1 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + SWR + + + + + + + + 0 + 0 + + + + + 8 + + + + A2 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + V / ratio + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 30 + + + + + + + + 0 + 0 + + + + + 8 + + + + 75 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + Db + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + V / ratio + + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 99.000000000000000 + + + 0.100000000000000 + + + 5.200000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 99.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 999.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 999.000000000000000 + + + 0.100000000000000 + + + + + + + + + + 0 + 0 + + + + + 8 + + + + RxBt + + + + + + + + 0 + 0 + + + + + 8 + + + + A1 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 999.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + 999.000000000000000 + + + 0.100000000000000 + + + + + + + + + + 0 + 0 + + + + + 8 + + + + V / ratio + + + + + + + + 0 + 0 + + + + + 8 + + + + 16777215 + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Tmp1 + + + + + + + + 0 + 0 + + + + + 8 + + + + A3 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + A4 + + + + + + + + 0 + 0 + + + + + 8 + + + + RSSI + + + + + + + + 0 + 0 + + + + + 8 + + + + 16777215 + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Tmp2 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + RPM + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 100000 + + + + + + + + 0 + 0 + + + + + 8 + + + + Fuel + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + °C + + + + + + + + 0 + 0 + + + + + 8 + + + + 2147483647 + + + + + + + + 0 + 0 + + + + + 8 + + + + ml + + + + + + + + 0 + 0 + + + + + 8 + + + + Deg. C + + + + + + + + 0 + 0 + + + + + 8 + + + + 65535 + + + + + + + + 0 + 0 + + + + + 8 + + + + RPM + + + + + + + + 0 + 0 + + + + + 8 + + + + % + + + + + + + + 0 + 0 + + + + + 8 + + + + Meters + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Alt + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + -16777215 + + + 16777215 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + VSpd + + + + + + + + 0 + 0 + + + + + 8 + + + + m/s + + + + + + + + 0 + 0 + + + + + 8 + + + + Fuel Qty + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - - 8 - + + + + 0 + 0 + - - 2 + + QFrame::NoFrame - - 16777215.000000000000000 + + Qt::ScrollBarAlwaysOff - - 0.100000000000000 + + true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 339 + 370 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 4 + + + 0 + + + 3 + + + 5 + + + + + + 0 + 0 + + + + + 8 + + + + km/h + + + + + + + + 0 + 0 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + VFAS + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Hdg + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + ASpd + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + km/h + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GPS + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + Lat,Lon +(dec.deg.) + + + false + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + AccX + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + dd-MM-yyyy +hh:mm:ss + + + false + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Date + + + + + + + + 0 + 0 + + + + + 8 + + + + Amps + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + + 0 + 0 + + + + + 8 + + + + AccZ + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GAlt + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 1 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + Volts + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Cels + + + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 0.100000000000000 + + + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + Curr + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + Volts + + + + + + + + 0 + 0 + + + + + 8 + + + + AccY + + + + + + + + 0 + 0 + + + + + 8 + + + + Meters + + + + + + + + 0 + 0 + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 1 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + <html><head/><body><p><br/></p></body></html> + + + GSpd + + + + + + + + 0 + 0 + + + + + 8 + + + + Degrees + + + + + + + + 0 + 0 + + + + + 8 + + + + 2 + + + -16777215.000000000000000 + + + 16777215.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - - - - 8 - - - - V / ratio - - - - - - - - 8 - - - - A2 - - - - - - - - 8 - - - - - - - - - - - 8 - - - - 2 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - 2 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - - - 8 - - - - V / ratio - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - ASpd - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - km/h - - - - - - - - 8 - - - - A3 - - - - - - - - 8 - - - - - - - - - 8 - - - - 16777215 - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - GAlt - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - Meters - - - - - - - - 8 - - - - A4 - - - - - - - - 8 - - - - - - - - - 8 - - - - 16777215 - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - GSpd - - - - - - - - 8 - - - - - - - - - 8 - - - - 1 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - km/h - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Tmp1 - - - - - - - - 8 - - - - - - - - - 8 - - - - -16777215 - - - 16777215 - - - 1 - - - - - - - - 8 - - - - Deg. C - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Hdg - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - Degrees - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Tmp2 - - - - - - - - 8 - - - - - - - - - 8 - - - - -16777215 - - - 16777215 - - - - - - - - 8 - - - - °C - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Date - - - - - - - - 8 - - - - - - - - - 8 - - - - - - - - - 8 - - - - dd-MM-yyyy hh:mm:ss - - - - - - - - 8 - - - - RPM - - - - - - - - 8 - - - - - - - - - 8 - - - - 2147483647 - - - - - - - - 8 - - - - RPM - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - GPS - - - - - - - - 8 - - - - - - - - - 8 - - - - - - - - - 8 - - - - Lat,Lon (dec.deg.) - - - - - - - - 8 - - - - Fuel - - - - - - - - 8 - - - - - - - - - 8 - - - - 65535 - - - - - - - - 8 - - - - % - - - - - - - - 8 - - - - AccX - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - G - - - - - - - - 8 - - - - Fuel Qty - - - - - - - - 8 - - - - - - - - - 8 - - - - 100000.000000000000000 - - - - - - - - 8 - - - - ml - - - - - - - - 8 - - - - AccY - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - G - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - VSpd - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - m/s - - - - - - - - 8 - - - - AccZ - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - G - - - - - - - - 8 - - - - <html><head/><body><p><br/></p></body></html> - - - Alt - - - - - - - - 8 - - - - - - - - - 8 - - - - 2 - - - -16777215.000000000000000 - - - 16777215.000000000000000 - - - 0.100000000000000 - - - - - - - - 8 - - - - Meters - - - +
+ -
Simulate @@ -1851,12 +3017,6 @@ Timestamp gps_time gpsll_inst gps_latlon - accx_inst - accx - accy_inst - accy - accz_inst - accz fuel_qty_inst fuel_qty diff --git a/companion/src/simulation/trainersimu.cpp b/companion/src/simulation/trainersimu.cpp index 9e245b3b8..50a506669 100644 --- a/companion/src/simulation/trainersimu.cpp +++ b/companion/src/simulation/trainersimu.cpp @@ -22,37 +22,45 @@ #include "trainersimu.h" #include "ui_trainersimu.h" #include "helpers.h" - -#define GBALL_SIZE 20 -#define RESX 512 +#include "virtualjoystickwidget.h" TrainerSimulator::TrainerSimulator(QWidget * parent, SimulatorInterface * simulator): - QDialog(parent), + QWidget(parent), ui(new Ui::TrainerSimulator), simulator(simulator) { ui->setupUi(this); - leftStick = ui->leftStick; - rightStick = ui->rightStick; - setupSticks(); + vJoyLeft = new VirtualJoystickWidget(this, 'L', false); + vJoyLeft->setStickColor(Qt::cyan); + ui->leftStickLayout->addWidget(vJoyLeft); - // resize(0, 0); // to force min height, min width - setFixedSize(width(), height()); + vJoyRight = new VirtualJoystickWidget(this, 'R', false); + vJoyRight->setStickColor(Qt::cyan); + ui->rightStickLayout->addWidget(vJoyRight); timer = new QTimer(this); + timer->setInterval(10); connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); } TrainerSimulator::~TrainerSimulator() { - timer->stop(); + if (timer) { + timer->stop(); + delete timer; + } + if (vJoyLeft) + delete vJoyLeft; + if (vJoyRight) + delete vJoyRight; + delete ui; } void TrainerSimulator::showEvent(QShowEvent *event) { - timer->start(10); + timer->start(); event->accept(); } @@ -64,71 +72,13 @@ void TrainerSimulator::closeEvent(QCloseEvent *event) void TrainerSimulator::centerSticks() { - if (leftStick->scene()) - nodeLeft->stepToCenter(); + if (vJoyLeft) + vJoyLeft->centerStick(); - if (rightStick->scene()) - nodeRight->stepToCenter(); + if (vJoyRight) + vJoyRight->centerStick(); } -void TrainerSimulator::setupSticks() -{ - QGraphicsScene *leftScene = new QGraphicsScene(leftStick); - leftScene->setItemIndexMethod(QGraphicsScene::NoIndex); - leftStick->setScene(leftScene); - - // leftStick->scene()->addLine(0,10,20,30); - - QGraphicsScene *rightScene = new QGraphicsScene(rightStick); - rightScene->setItemIndexMethod(QGraphicsScene::NoIndex); - rightStick->setScene(rightScene); - - // rightStick->scene()->addLine(0,10,20,30); - - nodeLeft = new Node(); - nodeLeft->setPos(-GBALL_SIZE/2,-GBALL_SIZE/2); - nodeLeft->setBallSize(GBALL_SIZE); - leftScene->addItem(nodeLeft); - - nodeRight = new Node(); - nodeRight->setPos(-GBALL_SIZE/2,-GBALL_SIZE/2); - nodeRight->setBallSize(GBALL_SIZE); - rightScene->addItem(nodeRight); -} - -void TrainerSimulator::resizeEvent(QResizeEvent *event) -{ - if (leftStick->scene()) { - QRect qr = leftStick->contentsRect(); - qreal w = (qreal)qr.width() - GBALL_SIZE; - qreal h = (qreal)qr.height() - GBALL_SIZE; - qreal cx = (qreal)qr.width()/2; - qreal cy = (qreal)qr.height()/2; - leftStick->scene()->setSceneRect(-cx,-cy,w,h); - - QPointF p = nodeLeft->pos(); - p.setX(qMin(cx, qMax(p.x(), -cx))); - p.setY(qMin(cy, qMax(p.y(), -cy))); - nodeLeft->setPos(p); - } - - if (rightStick->scene()) { - QRect qr = rightStick->contentsRect(); - qreal w = (qreal)qr.width() - GBALL_SIZE; - qreal h = (qreal)qr.height() - GBALL_SIZE; - qreal cx = (qreal)qr.width()/2; - qreal cy = (qreal)qr.height()/2; - rightStick->scene()->setSceneRect(-cx,-cy,w,h); - - QPointF p = nodeRight->pos(); - p.setX(qMin(cx, qMax(p.x(), -cx))); - p.setY(qMin(cy, qMax(p.y(), -cy))); - nodeRight->setPos(p); - } - QDialog::resizeEvent(event); -} - - void TrainerSimulator::onTimerEvent() { centerSticks(); @@ -137,8 +87,15 @@ void TrainerSimulator::onTimerEvent() void TrainerSimulator::setTrainerInputs() { - simulator->setTrainerInput(0, int( 512* nodeLeft->getX())); // LEFT HORZ - simulator->setTrainerInput(1, int(-512* nodeLeft->getY())); // LEFT VERT - simulator->setTrainerInput(2, int(-512*nodeRight->getY())); // RGHT VERT - simulator->setTrainerInput(3, int( 512*nodeRight->getX())); // RGHT HORZ -} \ No newline at end of file + if (!simulator) + return; + + if (vJoyLeft) { + simulator->setTrainerInput(0, int( 512 * vJoyLeft->getStickX())); // LEFT HORZ + simulator->setTrainerInput(1, int(-512 * vJoyLeft->getStickY())); // LEFT VERT + } + if (vJoyRight) { + simulator->setTrainerInput(2, int(-512 * vJoyRight->getStickY())); // RGHT VERT + simulator->setTrainerInput(3, int( 512 * vJoyRight->getStickX())); // RGHT HORZ + } +} diff --git a/companion/src/simulation/trainersimu.h b/companion/src/simulation/trainersimu.h index 94826086c..520df6d6c 100644 --- a/companion/src/simulation/trainersimu.h +++ b/companion/src/simulation/trainersimu.h @@ -24,17 +24,17 @@ #include #include -#include +#include #include -#include "modeledit/node.h" #include "simulatorinterface.h" namespace Ui { class TrainerSimulator; } +class VirtualJoystickWidget; -class TrainerSimulator : public QDialog +class TrainerSimulator : public QWidget { Q_OBJECT @@ -43,26 +43,20 @@ class TrainerSimulator : public QDialog virtual ~TrainerSimulator(); - protected: + protected slots: virtual void showEvent(QShowEvent *event); virtual void closeEvent(QCloseEvent *event); + void centerSticks(); + void setTrainerInputs(); + void onTimerEvent(); - private: + protected: Ui::TrainerSimulator * ui; QTimer * timer; SimulatorInterface *simulator; - QGraphicsView * leftStick, * rightStick; - Node *nodeLeft; - Node *nodeRight; - - void centerSticks(); - void setupSticks(); - void resizeEvent(QResizeEvent *event = 0); - void setTrainerInputs(); - - private slots: - void onTimerEvent(); + VirtualJoystickWidget * vJoyLeft; + VirtualJoystickWidget * vJoyRight; }; diff --git a/companion/src/simulation/trainersimu.ui b/companion/src/simulation/trainersimu.ui index d4e0ca2b5..a01c8c35e 100644 --- a/companion/src/simulation/trainersimu.ui +++ b/companion/src/simulation/trainersimu.ui @@ -1,94 +1,70 @@ TrainerSimulator - + 0 0 - 566 - 268 + 385 + 187 + + + 0 + 0 + + Trainer simulator - - - - 0 - 0 - 563 - 267 - - - - QFrame::Panel - - - QFrame::Raised - - - 2 - - - - 5 - - - - - - - Qt::Horizontal + + + + + QFrame::Panel + + + QFrame::Raised + + + 2 + + + + 15 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 - - QSizePolicy::Minimum - - - - 17 - 20 - - - + - - - - - 245 - 245 - + + + + 0 - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - - - - - - 245 - 245 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - + - - - + + + diff --git a/companion/src/simulation/widgets/radiofaderwidget.h b/companion/src/simulation/widgets/radiofaderwidget.h index fda81a7a4..3d1fe52c7 100644 --- a/companion/src/simulation/widgets/radiofaderwidget.h +++ b/companion/src/simulation/widgets/radiofaderwidget.h @@ -47,7 +47,7 @@ class RadioFaderWidget : public RadioWidget SliderWidget * sl = new SliderWidget(this); sl->setOrientation(Qt::Vertical); - sl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + sl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); sl->setMinimumHeight(75); //sl->setMaximumHeight(75); sl->setTickPosition(QSlider::TicksBothSides); diff --git a/companion/src/simulation/widgets/radioswitchwidget.h b/companion/src/simulation/widgets/radioswitchwidget.h index bc1d22e0d..45c50d710 100644 --- a/companion/src/simulation/widgets/radioswitchwidget.h +++ b/companion/src/simulation/widgets/radioswitchwidget.h @@ -23,6 +23,7 @@ #include "radiowidget.h" #include "boards.h" +#include "simulator.h" #include #include @@ -65,11 +66,8 @@ class RadioSwitchWidget : public RadioWidget m_slider->setValue(m_value); if (swType == Board::SWITCH_TOGGLE) { - QIcon icon; - icon.addFile(QStringLiteral(":/images/simulator/icons/8/lock-locked.png"), QSize(8, 8), QIcon::Normal, QIcon::On); - icon.addFile(QStringLiteral(":/images/simulator/icons/8/lock-unlocked.png"), QSize(8, 8), QIcon::Normal, QIcon::Off); QToolButton * lockBtn = new QToolButton(this); - lockBtn->setIcon(icon); + lockBtn->setIcon(Simulator::SimulatorIcon("toggle_lock")); lockBtn->setIconSize(QSize(8, 8)); lockBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); lockBtn->setToolButtonStyle(Qt::ToolButtonIconOnly); @@ -78,7 +76,7 @@ class RadioSwitchWidget : public RadioWidget lockBtn->setToolTip(tr("Latch/unlatch the momentary switch.")); QWidget * container = new QWidget(this); - container->setFixedHeight(50); + container->setFixedHeight(56); container->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QVBoxLayout * cl = new QVBoxLayout(container); cl->setContentsMargins(0, 0, 0, 0); @@ -94,7 +92,7 @@ class RadioSwitchWidget : public RadioWidget connect(this, &RadioWidget::flagsChanged, lockBtn, &QToolButton::setChecked); } else { - m_slider->setFixedHeight(50); + m_slider->setFixedHeight(56); setWidget(m_slider); } diff --git a/companion/src/simulation/widgets/virtualjoystickwidget.cpp b/companion/src/simulation/widgets/virtualjoystickwidget.cpp index 0697100a9..9242b01c4 100644 --- a/companion/src/simulation/widgets/virtualjoystickwidget.cpp +++ b/companion/src/simulation/widgets/virtualjoystickwidget.cpp @@ -18,6 +18,9 @@ * GNU General Public License for more details. */ +#define GBALL_SIZE 20 +#define RESX 1024 + #include "virtualjoystickwidget.h" #include "constants.h" #include "sliderwidget.h" @@ -50,7 +53,9 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s gv = new QGraphicsView(this); gv->setSizePolicy(sizePolicy); - gv->setMinimumSize(QSize(150, 150)); + gv->setMinimumSize(size); +// gv->setMaximumSize(size + size * 3); +// gv->setFixedSize(prefSize); gv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -88,6 +93,9 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s vTrimSlider = vTrimWidget->findChild(); extraSize += QSize(vTrimWidget->sizeHint().width(), hTrimWidget->sizeHint().height()); } + else { + colvx = colvy = colvt; + } if (showBtns) { QVBoxLayout * btnbox = new QVBoxLayout(); @@ -106,8 +114,17 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s if (showValues) { QLayout * valX = createNodeValueLayout('X', nodeLabelX); QLayout * valY = createNodeValueLayout('Y', nodeLabelY); - layout->addLayout(valX, 2, colvx, 1, 1); - layout->addLayout(valY, 2, colvy, 1, 1); + if (!showTrims) { + QVBoxLayout * vertXY = new QVBoxLayout(); + vertXY->addLayout(valX); + vertXY->addLayout(valY); + layout->addLayout(vertXY, 1, colvx, 1, 1); + extraSize += QSize(nodeLabelX->sizeHint().width(), 0); + } + else { + layout->addLayout(valX, 2, colvx, 1, 1); + layout->addLayout(valY, 2, colvy, 1, 1); + } } layout->addItem(new QSpacerItem(0, 0), 0, 0, 1, 5); // r0 c0-4: top v spacer @@ -119,7 +136,7 @@ VirtualJoystickWidget::VirtualJoystickWidget(QWidget *parent, QChar side, bool s connect(node, SIGNAL(xChanged()), this, SLOT(updateNodeValueLabels())); connect(node, SIGNAL(yChanged()), this, SLOT(updateNodeValueLabels())); - setSize(prefSize); + setSize(prefSize, frameSize()); } void VirtualJoystickWidget::setStickX(qreal x) @@ -144,12 +161,12 @@ void VirtualJoystickWidget::centerStick() qreal VirtualJoystickWidget::getStickX() { - return node->getX(); + return getStickPos().x(); } qreal VirtualJoystickWidget::getStickY() { - return node->getY(); + return getStickPos().y(); } QPointF VirtualJoystickWidget::getStickPos() @@ -205,7 +222,22 @@ void VirtualJoystickWidget::setStickConstraint(int which, bool active) } } -void VirtualJoystickWidget::setSize(QSize size) +void VirtualJoystickWidget::setStickColor(const QColor & color) +{ + node->setColor(color); +} + +QSize VirtualJoystickWidget::sizeHint() const { + return prefSize; +} + +void VirtualJoystickWidget::resizeEvent(QResizeEvent * event) +{ + QWidget::resizeEvent(event); + setSize(event->size(), event->oldSize()); +} + +void VirtualJoystickWidget::setSize(const QSize & size, const QSize &) { float thisAspectRatio = (float)size.width() / size.height(); float newGvSz, spacerSz; @@ -237,28 +269,24 @@ void VirtualJoystickWidget::setSize(QSize size) layout->setColumnStretch(2, newGvSz); layout->setRowStretch(1, newGvSz); - prefSize = QSize(newGvSz + extraSize.width(), newGvSz + extraSize.height()); + //prefSize = QSize(newGvSz + extraSize.width(), newGvSz + extraSize.height()); gv->resize(newGvSz, newGvSz); gv->updateGeometry(); - repositionNode(); - //qDebug() << thisAspectRatio << size << newGvSz << spacerSz << extraSize << gv->geometry() << gv->contentsRect() << gv->frameRect() << getStickPos(); -} -void VirtualJoystickWidget::repositionNode() -{ - QRect qr = gv->contentsRect(); - qreal w = (qreal)qr.width() - GBALL_SIZE; - qreal h = (qreal)qr.height() - GBALL_SIZE; - qreal cx = (qreal)qr.width()/2; - qreal cy = (qreal)qr.height()/2; + QRectF qr = (QRectF)gv->contentsRect(); + qreal w = qr.width() - GBALL_SIZE; + qreal h = qr.height() - GBALL_SIZE; + qreal cx = qr.width() / 2; + qreal cy = qr.height() / 2; + qreal nodeX = node->getX(); + qreal nodeY = node->getY(); + scene->setSceneRect(-cx,-cy,w,h); - QPointF p = node->pos(); - p.setX(qMin(cx, qMax(p.x(), -cx))); - p.setY(qMin(cy, qMax(p.y(), -cy))); - node->setPos(p); + node->setX(nodeX); + node->setY(nodeY); - updateNodeValueLabels(); + //qDebug() << thisAspectRatio << size << newGvSz << spacerSz << extraSize << gv->geometry() << gv->contentsRect() << gv->frameRect() << getStickPos(); } QWidget *VirtualJoystickWidget::createTrimWidget(QChar type) @@ -382,6 +410,7 @@ QLayout *VirtualJoystickWidget::createNodeValueLayout(QChar type, QLabel *& valL val->setObjectName(QString("val_%1").arg(type)); val->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); val->setAlignment(Qt::AlignCenter); + val->setMinimumWidth(val->fontMetrics().width("-100 ")); QVBoxLayout * layout = new QVBoxLayout(); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(2); diff --git a/companion/src/simulation/widgets/virtualjoystickwidget.h b/companion/src/simulation/widgets/virtualjoystickwidget.h index 4bbf1ba93..478f6398e 100644 --- a/companion/src/simulation/widgets/virtualjoystickwidget.h +++ b/companion/src/simulation/widgets/virtualjoystickwidget.h @@ -33,9 +33,6 @@ class Node; class SliderWidget; -#define GBALL_SIZE 20 -#define RESX 1024 - class VirtualJoystickWidget : public QWidget { Q_OBJECT @@ -55,7 +52,7 @@ class VirtualJoystickWidget : public QWidget FIX_Y }; - explicit VirtualJoystickWidget(QWidget * parent = NULL, QChar side = 'L', bool showTrims = true, bool showBtns = true, bool showValues = true, QSize size = QSize(245, 245)); + explicit VirtualJoystickWidget(QWidget * parent = NULL, QChar side = 'L', bool showTrims = true, bool showBtns = true, bool showValues = true, QSize size = QSize(125, 125)); void setStickX(qreal x); void setStickY(qreal y); @@ -70,17 +67,10 @@ class VirtualJoystickWidget : public QWidget int getTrimValue(int which); void setStickConstraint(int which, bool active); + void setStickColor(const QColor & color); - virtual QSize sizeHint() const - { - return prefSize; - } - - virtual void resizeEvent(QResizeEvent *event) - { - QWidget::resizeEvent(event); - setSize(event->size()); - } + virtual QSize sizeHint() const; + virtual void resizeEvent(QResizeEvent *event); signals: void trimButtonPressed(int which); @@ -94,8 +84,7 @@ class VirtualJoystickWidget : public QWidget void updateNodeValueLabels(); protected: - void setSize(QSize size); - void repositionNode(); + void setSize(const QSize & size, const QSize &); QWidget * createTrimWidget(QChar type); QPushButton * createButtonWidget(int type); QLayout * createNodeValueLayout(QChar type, QLabel *& valLabel); diff --git a/companion/src/simulator.cpp b/companion/src/simulator.cpp index af5dcad53..bad157b03 100644 --- a/companion/src/simulator.cpp +++ b/companion/src/simulator.cpp @@ -19,13 +19,14 @@ */ #include -//#include +#include +#include #include #include #include #include #include -#include +#include #if defined(JOYSTICKS) || defined(SIMU_AUDIO) #include #undef main @@ -35,10 +36,11 @@ #include "constants.h" #include "eeprominterface.h" #include "simulator.h" -#include "simulatordialog.h" +#include "simulatormainwindow.h" #include "simulatorstartupdialog.h" #include "storage.h" #include "qxtcommandoptions.h" +#include "version.h" #ifdef WIN32 #include @@ -50,21 +52,6 @@ #include #endif -#ifdef __APPLE__ -#include - -class MyProxyStyle : public QProxyStyle - { - public: - void polish ( QWidget * w ) { - QMenu* mn = dynamic_cast(w); - QPushButton* pb = dynamic_cast(w); - if(!(mn || pb) && !w->testAttribute(Qt::WA_MacNormalSize)) - w->setAttribute(Qt::WA_MacSmallSize); - } - }; -#endif - using namespace Simulator; int finish(int exitCode); @@ -126,19 +113,12 @@ int main(int argc, char *argv[]) g.init(); -#ifdef __APPLE__ - app.setStyle(new MyProxyStyle); -#endif - - /* QTranslator companionTranslator; - companionTranslator.load(":/companion_" + locale); + QTranslator companionTranslator; + companionTranslator.load(":/companion_" + g.locale()); QTranslator qtTranslator; - qtTranslator.load((QString)"qt_" + locale.left(2), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + qtTranslator.load((QString)"qt_" + g.locale().left(2), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&companionTranslator); app.installTranslator(&qtTranslator); -*/ - - // QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); #if defined(JOYSTICKS) || defined(SIMU_AUDIO) uint32_t sdlFlags = 0; @@ -269,23 +249,20 @@ int main(int argc, char *argv[]) current_firmware_variant = getFirmware(simOptions.firmwareId); - int oldProfId = g.id(); - g.id(profileId); + g.sessionId(profileId); g.simuLastProfId(profileId); int result = 1; - SimulatorDialog * dialog = new SimulatorDialog(NULL, simulator, SIMULATOR_FLAGS_STANDALONE); - dialog->setRadioProfileId(profileId); - if (dialog->setOptions(simOptions, true)) { - dialog->start(); - dialog->show(); + SimulatorMainWindow * mainWindow = new SimulatorMainWindow(NULL, simulator, SIMULATOR_FLAGS_STANDALONE); + if (mainWindow->setOptions(simOptions, true)) { + mainWindow->start(); + mainWindow->show(); result = app.exec(); } else { result = 3; } - g.id(oldProfId); - delete dialog; + delete mainWindow; delete simulator; return finish(result); diff --git a/companion/src/storage/appdata.cpp b/companion/src/storage/appdata.cpp index ffe84efac..10a1a5e8a 100644 --- a/companion/src/storage/appdata.cpp +++ b/companion/src/storage/appdata.cpp @@ -428,7 +428,9 @@ QString Profile::groupId() // ** AppData class******************** // Get declarations -QStringList AppData::recentFiles() { return _recentFiles; } +QStringList AppData::recentFiles() { return _recentFiles; } +QStringList AppData::simuDbgFilters() { return _simuDbgFilters; } + QByteArray AppData::mainWinGeo() { return _mainWinGeo; } QByteArray AppData::mainWinState() { return _mainWinState; } QByteArray AppData::modelEditGeo() { return _modelEditGeo; } @@ -475,9 +477,12 @@ int AppData::id() { return _id; } int AppData::theme() { return _theme; } int AppData::warningId() { return _warningId; } int AppData::simuLastProfId() { return _simuLastProfId; } +int AppData::sessionId() { return _sessionId; } // Set declarations void AppData::recentFiles (const QStringList x) { store(x, _recentFiles, "recentFileList" );} +void AppData::simuDbgFilters (const QStringList x) { store(x, _simuDbgFilters, "simuDbgFilters" );} + void AppData::mainWinGeo (const QByteArray x) { store(x, _mainWinGeo, "mainWindowGeometry" );} void AppData::mainWinState (const QByteArray x) { store(x, _mainWinState, "mainWindowState" );} void AppData::modelEditGeo (const QByteArray x) { store(x, _modelEditGeo, "modelEditGeometry" );} @@ -520,11 +525,19 @@ void AppData::generalEditTab (const int x) { store(x, _generalEditTab, void AppData::iconSize (const int x) { store(x, _iconSize, "icon_size" );} void AppData::historySize (const int x) { store(x, _historySize, "history_size" );} void AppData::jsCtrl (const int x) { store(x, _jsCtrl, "js_ctrl" );} -void AppData::id (const int x) { store(x, _id, "profileId" );} void AppData::theme (const int x) { store(x, _theme, "theme" );} void AppData::warningId (const int x) { store(x, _warningId, "warningId" );} void AppData::simuLastProfId (const int x) { store(x, _simuLastProfId, "simuLastProfId" );} +void AppData::id(const int x) +{ + store(x, _id, "profileId"); + sessionId(x); +} + +// currently loaded radio profile ID, NOT saved to persistent storage +void AppData::sessionId (const int x) { _sessionId = x; } + // Constructor AppData::AppData() { @@ -667,6 +680,8 @@ void AppData::init() getset( _tempString, "settings_version" ,"220" ); // This is a version marker. Will be used to upgrade the settings later on. getset( _recentFiles, "recentFileList" ,"" ); + getset( _simuDbgFilters, "simuDbgFilters" ,"" ); + getset( _mainWinGeo, "mainWindowGeometry" ,"" ); getset( _mainWinState, "mainWindowState" ,"" ); getset( _modelEditGeo, "modelEditGeometry" ,"" ); @@ -722,6 +737,7 @@ void AppData::init() getset( _warningId, "warningId" ,0 ); getset( _simuLastProfId, "simuLastProfId" ,-1 ); + sessionId(id()); } QMap AppData::getActiveProfiles() diff --git a/companion/src/storage/appdata.h b/companion/src/storage/appdata.h index 4328a4aa1..ba0a1781d 100644 --- a/companion/src/storage/appdata.h +++ b/companion/src/storage/appdata.h @@ -250,6 +250,8 @@ class AppData: protected CompStoreObj private: QStringList _recentFiles; + QStringList _simuDbgFilters; + QByteArray _mainWinGeo; QByteArray _mainWinState; QByteArray _modelEditGeo; @@ -296,10 +298,14 @@ class AppData: protected CompStoreObj int _theme; int _warningId; int _simuLastProfId; + // currently loaded radio profile ID, NOT saved to persistent storage + int _sessionId; public: // All the get definitions QStringList recentFiles(); + QStringList simuDbgFilters(); + QByteArray mainWinGeo(); QByteArray mainWinState(); QByteArray modelEditGeo(); @@ -347,9 +353,12 @@ class AppData: protected CompStoreObj int theme(); int warningId(); int simuLastProfId(); + int sessionId(); // All the set definitions void recentFiles (const QStringList x); + void simuDbgFilters (const QStringList x); + void mainWinGeo (const QByteArray); void mainWinState (const QByteArray); void modelEditGeo (const QByteArray); @@ -398,6 +407,7 @@ class AppData: protected CompStoreObj void theme (const int); void warningId (const int); void simuLastProfId (const int); + void sessionId (const int); // Constructor AppData(); diff --git a/companion/src/themes/default/style-osx.css b/companion/src/themes/default/style-osx.css new file mode 100644 index 000000000..bd7e14c46 --- /dev/null +++ b/companion/src/themes/default/style-osx.css @@ -0,0 +1,31 @@ + +* { + font-size: 11pt; +} + +QToolButton { + border: .05em solid #8f8f91; + border-radius: .3em; + background-color: white; + font-size: 12pt; +} + +QToolButton[autoRaise="true"] { + border: .05em solid transparent; + background-color: transparent; +} + +QToolButton:hover { + border-color: black; + background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(77, 77, 77, 35), stop:1 rgba(149, 149, 149, 10)); +} + +QToolButton:checked { + border-color: black; + background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(77, 77, 77, 85), stop:1 rgba(149, 149, 149, 50)); +} + +QToolButton:pressed { + color: white; + background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(77, 77, 77, 165), stop:1 rgba(77, 77, 77, 255)); +} diff --git a/companion/src/themes/default/style.css b/companion/src/themes/default/style.css new file mode 100644 index 000000000..2236a6973 --- /dev/null +++ b/companion/src/themes/default/style.css @@ -0,0 +1,77 @@ + +/* + MainWindow separators and Splitter +*/ +QMainWindow::separator, +QSplitter::handle { + border: .05em solid palette(dark); +} + +QMainWindow::separator:horizontal { + height: .15em; + margin: .2em 0em; + border-left: 0; + border-right: 0; +} + +QMainWindow::separator:vertical { + width: .15em; + margin: 0em .2em; + border-top: 0; + border-bottom: 0; +} + +QSplitter[orientation="1"]::handle, +QSplitter[orientation="1"] QSplitterHandle { + margin: 0em .15em 0em; + max-width: .15em; + min-width: .15em; + border-top: 0; + border-bottom: 0; +} + +QSplitter[orientation="2"]::handle, +QSplitter[orientation="2"] QSplitterHandle { + margin: .15em 0em 0em; + max-height: .15em; + min-height: .15em; + border-left: 0; + border-right: 0; +} + +QSplitterHandle:hover {} + +QMainWindow::separator:hover, +QSplitter::handle:hover { + background-color: palette(highlight); +} + + +/* + Specific UI class styles +*/ + +/* default radio "case" background */ +SimulatorDialog > QWidget { + background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(242, 242, 242, 255), stop:0.757062 rgba(222, 220, 220, 255), stop:1 rgba(232, 230, 230, 255)); +} +/* radio LCD/controls area under "case" */ +SimulatorDialog #radioUiWidget { + background-color: qlineargradient(spread:reflect, x1:0.22, y1:0, x2:0.55065, y2:0.54, stop:0 rgba(7, 7, 7, 250), stop:0.864407 rgba(66, 66, 66, 255)); + margin: 0; + padding: 0; +} + + +/* outputs display splitter areas (channels, logical switches, global vars) */ +RadioOutputsWidget > QSplitter > QWidget { + border: 1px solid #a1a1a1; +} +/* vertical headings with the area names */ +RadioOutputsWidget QLabel[heading=true] { + background-color: qlineargradient(spread:reflect, x1:0.22, y1:0, x2:0.5, y2:0.55, stop:0 rgba(20, 20, 20, 250), stop:0.85 rgba(86, 86, 86, 255)); + color: rgb(248, 248, 248); + border-color: rgb(180, 180, 180); + padding: 0.8em 0.45em; + font-size: 8pt; +} diff --git a/companion/targets/windows/companion-vs.nsi.in b/companion/targets/windows/companion-vs.nsi.in index 159a92953..1fcda2ea3 100644 --- a/companion/targets/windows/companion-vs.nsi.in +++ b/companion/targets/windows/companion-vs.nsi.in @@ -123,6 +123,7 @@ Section "OpenTX Companion" SecDummy File "@QT_DLL_DIR@\Qt5PrintSupport.dll" File "@QT_DLL_DIR@\Qt5Network.dll" File "@QT_DLL_DIR@\Qt5Multimedia.dll" + File "@QT_DLL_DIR@\Qt5Svg.dll" File "@QT_DLL_DIR@\Qt5Xml.dll" File "@SDL_DIR@\SDL.dll" @@ -137,6 +138,7 @@ Section "OpenTX Companion" SecDummy SetOutPath "$INSTDIR\imageformats" File "@QT_DLL_DIR@\..\plugins\imageformats\qjpeg.dll" + File "@QT_DLL_DIR@\..\plugins\imageformats\qsvg.dll" SetOutPath "$INSTDIR\mediaservice" File "@QT_DLL_DIR@\..\plugins\mediaservice\dsengine.dll" diff --git a/radio/util/Dockerfile b/radio/util/Dockerfile index 507c175d0..a0cd18bbc 100644 --- a/radio/util/Dockerfile +++ b/radio/util/Dockerfile @@ -18,6 +18,7 @@ RUN apt-get update && \ qtmultimedia5-dev \ qttools5-dev \ qttools5-dev-tools \ + libqt5svg5-dev \ software-properties-common \ wget \ zip \