/* * 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 "simulatordialog.h" #include "ui_simulatordialog.h" #include "appdata.h" #include "debugoutput.h" #include "radiofaderwidget.h" #include "radioknobwidget.h" #include "radioswitchwidget.h" #include "radiouiaction.h" #include "sdcard.h" #include "simulateduiwidget.h" #include "simulatorinterface.h" #include "storage.h" #include "telemetrysimu.h" #include "trainersimu.h" #include "virtualjoystickwidget.h" #ifdef JOYSTICKS #include "joystick.h" #include "joystickdialog.h" #endif #include #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), ui(new Ui::SimulatorDialog), simulator(simulator), firmware(getCurrentFirmware()), radioSettings(GeneralSettings()), timer(NULL), 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), startupFromFile(false), deleteTempRadioData(false), saveTempRadioData(false), middleButtonPressed(false) { setWindowFlags(Qt::Window); // install simulator TRACE hook traceCallbackInstance = this; simulator->installTraceHook(traceCb); // defaults setRadioProfileId(radioProfileId); setSdPath(g.profile[radioProfileId].sdPath()); setupUi(); } SimulatorDialog::~SimulatorDialog() { traceCallbackInstance = 0; delete timer; delete simulator; } /* * 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); } void SimulatorDialog::setDataPath(const QString & dataPath) { setPaths(sdCardPath, dataPath); } void SimulatorDialog::setPaths(const QString & sdPath, const QString & dataPath) { sdCardPath = sdPath; radioDataPath = dataPath; if (simulator) simulator->setSdPath(sdPath, dataPath); } void SimulatorDialog::setRadioSettings(const GeneralSettings settings) { radioSettings = settings; } /* * This function can accept no parameters, a file name (QString is a QBA), or a data array. It will attempt to load radio settings data from one of * several sources into a RadioData object, parse the data, and then pass it on as appropriate to the SimulatorInterface in start(). * If given no/blank , and setDataPath() was already called, then it will check that directory for "Horus-style" data files. * If given a file name, set the parameter to 'true'. This will attempt to load radio settings from said file * and later start the simulator interface in start() using the same data. * If is a byte array of data, attempts to load radio settings from there and will also start the simulator interface * with the same data when start() is called. * If you already have a valid RadioData structure, call setRadioData() instead. */ void SimulatorDialog::setStartupData(const QByteArray & dataSource, bool fromFile) { RadioData simuData; quint16 ret = 1; QString error; // If is blank but we have a data path, use that for individual radio/model files. if (dataSource.isEmpty() && !radioDataPath.isEmpty()) { // If directory structure already exists, try to load data from there. // FIXME : need Storage class to return formal error code, not just a boolean, because it would be better // to avoid hard-coding paths like "RADIO" here. E.g. did it fail due to no data at all, or corrupt data, or...? if (QDir(QString(radioDataPath).append("/RADIO")).exists()) { SdcardFormat sdcard(radioDataPath); if (!(ret = sdcard.load(simuData))) error = sdcard.error(); } } // Supposedly we're being given a file name to use, try that out. else if (fromFile) { Storage store = Storage(QString(dataSource)); if ((ret = store.load(simuData))) { if (IS_HORUS(m_board)) { // save the data to a temp folder if (!(ret = useTempDataPath(true, true))) // save data back to file on simulator close error = tr("Error: Could not save data to temporary directory in '%1'").arg(QDir::tempPath()); else ret = saveRadioData(&simuData, radioDataPath, &error); } else if (QString(dataSource).endsWith(".otx", Qt::CaseInsensitive)) { // FIXME : Right now there's no way to read data back into the .otx file after simulation finishes. setRadioData(&simuData); return; } else { startupData = dataSource; // save the file name for start() } } else { error = store.error(); } } // Assume a byte array of radio data was passed, load it. else if (!dataSource.isEmpty()) { ret = firmware->getEEpromInterface()->load(simuData, (uint8_t *)dataSource.constData(), getEEpromSize(m_board)); startupData = dataSource; // save the data for start() } // we're :-( else { ret = 0; error = tr("Could not determine startup data source."); } if (!ret) { if (error.isEmpty()) error = tr("Could not load data, possibly wrong format."); QMessageBox::critical(this, tr("Data Load Error"), error); return; } radioSettings = simuData.generalSettings; startupFromFile = fromFile; } void SimulatorDialog::setRadioData(RadioData * radioData) { if (radioDataPath.isEmpty()) { QByteArray eeprom(getEEpromSize(m_board), 0); if (firmware->getEEpromInterface()->save((uint8_t *)eeprom.data(), *radioData, 0, firmware->getCapability(SimulatorVariant)) > 0) setStartupData(eeprom, false); } else { saveRadioData(radioData, radioDataPath); radioSettings = radioData->generalSettings; } } void SimulatorDialog::setOptions(SimulatorOptions & options, bool withSave) { setSdPath(options.sdPath); if (options.startupDataType == SimulatorOptions::START_WITH_FOLDER && !options.dataFolder.isEmpty()) { setDataPath(options.dataFolder); setStartupData(); } else if (options.startupDataType == SimulatorOptions::START_WITH_SDPATH && !options.sdPath.isEmpty()) { setDataPath(options.sdPath); setStartupData(); } else if (options.startupDataType == SimulatorOptions::START_WITH_FILE && !options.dataFile.isEmpty()) { setStartupData(options.dataFile.toLocal8Bit(), true); } if (withSave) g.profile[radioProfileId].simulatorOptions(options); } bool SimulatorDialog::saveRadioData(RadioData * radioData, const QString & path, QString * error) { QString dir = path; if (dir.isEmpty()) dir = radioDataPath; if (radioData && !dir.isEmpty()) { SdcardFormat sdcard(dir); bool ret = sdcard.write(*radioData); if (!ret && error) *error = sdcard.error(); return ret; } return false; } bool SimulatorDialog::useTempDataPath(bool deleteOnClose, bool saveOnClose) { if (deleteTempRadioData) deleteTempData(); QTemporaryDir tmpDir(QDir::tempPath() + "/otx-XXXXXX"); if (tmpDir.isValid()) { setDataPath(tmpDir.path()); tmpDir.setAutoRemove(false); deleteTempRadioData = deleteOnClose; saveTempRadioData = saveOnClose; qDebug() << __FILE__ << __LINE__ << "Created temporary settings directory" << radioDataPath << "with delteOnClose:" << deleteOnClose << "with saveOnClose:" << saveOnClose; return true; } else { qDebug() << __FILE__ << __LINE__ << "ERROR : Failed to create temporary settings directory" << radioDataPath; return false; } } // This will save radio data from temporary folder structure back into an .otx file, eg. for Horus. bool SimulatorDialog::saveTempData() { bool ret = false; QString error; QString file = g.profile[radioProfileId].simulatorOptions().dataFile; if (!saveTempRadioData || radioDataPath.isEmpty() || file.isEmpty()) return ret; RadioData radioData; SdcardFormat sdcard(radioDataPath); if (!(ret = sdcard.load(radioData))) { error = sdcard.error(); } else { Storage store(file); if (!(ret = store.write(radioData))) error = store.error(); else qDebug() << __FILE__ << __LINE__ << "Saved radio data to file" << file; } if (!ret) QMessageBox::critical(this, tr("Data Save Error"), error); return ret; } void SimulatorDialog::deleteTempData() { if (!radioDataPath.isEmpty()) { QDir tpath(radioDataPath); qDebug() << __FILE__ << __LINE__ << "Deleting temporary settings directory" << tpath.absolutePath(); tpath.removeRecursively(); tpath.rmdir(radioDataPath); // for some reason this is necessary to remove the base folder } } void SimulatorDialog::setUiAreaStyle(const QString & style) { ui->radioUiTab->setStyleSheet(style); } void SimulatorDialog::traceCallback(const char * text) { // this function is called from other threads traceMutex.lock(); // limit the size of list if (traceList.size() < 1000) { traceList.append(QString(text)); } traceMutex.unlock(); } /* * Startup */ void SimulatorDialog::start() { setupRadioWidgets(); setupJoysticks(); setupOutputsDisplay(); setupGVarsDisplay(); restoreRadioWidgetsState(); setTrims(); if (startupData.isEmpty()) simulator->start((const char *)0); else if (startupFromFile) simulator->start(startupData.constData()); else simulator->start(startupData, (flags & SIMULATOR_FLAGS_NOTX) ? false : true); getValues(); setupTimer(); startupData.clear(); // this is safe because simulator->start() makes copy of data/discards the file name } /* * Setup */ void SimulatorDialog::setupUi() { 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_HORUS : 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())); // Hide some main UI buttons based on board capabilities, and add keymap help texts. keymapHelp.append(keymapHelp_t(ui->btn_help->shortcut().toString(QKeySequence::NativeText), ui->btn_help->statusTip())); #ifdef JOYSTICKS keymapHelp.append(keymapHelp_t(ui->btn_joystickDialog->shortcut().toString(QKeySequence::NativeText), ui->btn_joystickDialog->statusTip())); #else ui->btn_joystickDialog->hide(); #endif if (firmware->getCapability(Capability(SportTelemetry))) keymapHelp.append(keymapHelp_t(ui->btn_telemSim->shortcut().toString(QKeySequence::NativeText), ui->btn_telemSim->statusTip())); else ui->btn_telemSim->hide(); keymapHelp.append(keymapHelp_t(ui->btn_trainerSim->shortcut().toString(QKeySequence::NativeText), ui->btn_trainerSim->statusTip())); keymapHelp.append(keymapHelp_t(ui->btn_debugConsole->shortcut().toString(QKeySequence::NativeText), ui->btn_debugConsole->statusTip())); if (!firmware->getCapability(Capability(LuaInputsPerScript))) // hackish! but using "LuaScripts" checks for id "lua" in fw. keymapHelp.append(keymapHelp_t(ui->btn_luaReload->shortcut().toString(QKeySequence::NativeText), ui->btn_luaReload->statusTip())); else ui->btn_luaReload->hide(); keymapHelp.append(keymapHelp_t(ui->btn_screenshot->shortcut().toString(QKeySequence::NativeText), ui->btn_screenshot->statusTip())); } void SimulatorDialog::setupRadioWidgets() { int i, midpos, aIdx, wval; QString wname; Board::Type board = firmware->getBoard(); // First clear out any existing widgets. if (switches.size()) { foreach (RadioWidget * w, switches) { ui->radioWidgetsHTLayout->removeWidget(w); w->deleteLater(); } } if (analogs.size()) { foreach (RadioWidget * w, analogs) { if (w->getType() == RadioWidget::RADIO_WIDGET_KNOB) ui->radioWidgetsHTLayout->removeWidget(w); else ui->VCGridLayout->removeWidget(w); w->deleteLater(); } } // Now set up new widgets. // switches Board::SwitchInfo switchInfo; Board::SwitchType swcfg; // FIXME : CPN_MAX_SWITCHES == 32 but GeneralSettings::switchConfig[18] !! for (i = 0; i < firmware->getCapability(Capability(Switches)) && i < 18 /*CPN_MAX_SWITCHES*/; ++i) { if (radioSettings.switchConfig[i] == Board::SWITCH_NOT_AVAILABLE) continue; swcfg = Board::SwitchType(radioSettings.switchConfig[i]); wval = (swcfg == Board::SWITCH_3POS ? -1 : 0); if ((wname = QString(radioSettings.switchName[i])).isEmpty()) { switchInfo = getSwitchInfo(board, i); wname = QString(switchInfo.name); } RadioSwitchWidget * sw = new RadioSwitchWidget(swcfg, wname, wval, ui->radioWidgetsHT); sw->setIndex(i); ui->radioWidgetsHTLayout->addWidget(sw); switches.append(sw); } midpos = (int)floorf(switches.size() / 2.0f); aIdx = 0; // pots in middle of switches for (i = 0; i < firmware->getCapability(Pots) && i < CPN_MAX_POTS; ++i) { if (!radioSettings.isPotAvailable(i)) continue; if ((wname = QString(radioSettings.potName[i])).isEmpty()) wname = firmware->getAnalogInputName(4 + aIdx + i); RadioKnobWidget * pot = new RadioKnobWidget(Board::PotType(radioSettings.potConfig[i]), wname, 0, ui->radioWidgetsHT); pot->setIndex(aIdx + i); // FIXME : total hack here -- this needs to follow the exception in radio/src/mixer.cpp:evalInputs() if (i == 0 && IS_TARANIS(board) && !IS_TARANIS_X7(board)) pot->setInvertValue(true); ui->radioWidgetsHTLayout->insertWidget(midpos++, pot); analogs.append(pot); } aIdx += i; // faders between sticks int r = 0, c = 0; for (i = 0; i < firmware->getCapability(Sliders) && i + aIdx < CPN_MAX_POTS; ++i) { if (!radioSettings.isSliderAvailable(i)) continue; if ((wname = QString(radioSettings.sliderName[i])).isEmpty()) wname = firmware->getAnalogInputName(4 + aIdx + i); RadioFaderWidget * sl = new RadioFaderWidget(wname, 0, ui->radioWidgetsVC); sl->setIndex(aIdx + i); // FIXME : total hack here -- this needs to follow the exception in radio/src/mixer.cpp:evalInputs() if (i == 0 && IS_TARANIS(board) && !IS_TARANIS_X7(board)) sl->setInvertValue(true); /* 2-row option if (!(i % 2)) { ++r; c = 0; } */ ui->VCGridLayout->addWidget(sl, r, c++, 1, 1); analogs.append(sl); } } void SimulatorDialog::setupOutputsDisplay() { // setup Outputs tab QWidget * outputsWidget = new QWidget(); 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() { int fmodes = firmware->getCapability(FlightModes); int gvars = firmware->getCapability(Gvars); if (gvars>0) { // setup GVars tab QWidget * gvarsWidget = new QWidget(); QGridLayout * gvarsLayout = new QGridLayout(gvarsWidget); ui->tabWidget->addTab(gvarsWidget, QString(tr("GVars"))); 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 static bool joysticksEnabled = false; if (g.jsSupport() && g.jsCtrl() > -1) { int count=0, axe; for (int j=0; j < MAX_JOYSTICKS; j++){ axe = g.joystick[j].stick_axe(); if (axe >= 0 && axe < MAX_JOYSTICKS) { jsmap[axe] = j + 1; jscal[axe][0] = g.joystick[j].stick_min(); jscal[axe][1] = g.joystick[j].stick_med(); jscal[axe][2] = g.joystick[j].stick_max(); jscal[axe][3] = g.joystick[j].stick_inv(); count++; } } if (count<3) { QMessageBox::critical(this, tr("Warning"), tr("Joystick enabled but not configured correctly")); return; } joystick = new Joystick(this); if (joystick && joystick->open(g.jsCtrl())) { int numAxes = std::min(joystick->numAxes, MAX_JOYSTICKS); for (int j=0; jsensitivities[j] = 0; joystick->deadzones[j] = 0; } //mode 1,3 -> THR on right vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, true); vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, true); connect(joystick, SIGNAL(axisValueChanged(int, int)), this, SLOT(onjoystickAxisValueChanged(int, int))); joysticksEnabled = true; } else { QMessageBox::critical(this, tr("Warning"), tr("Cannot open joystick, joystick disabled")); } } else if (joysticksEnabled && joystick) { disconnect(joystick, 0, this, 0); vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_Y, false); vJoyRight->setStickConstraint(VirtualJoystickWidget::HOLD_X, false); vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_Y, false); vJoyLeft->setStickConstraint(VirtualJoystickWidget::HOLD_X, false); } #endif } void SimulatorDialog::setupTimer() { timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(onTimerEvent())); timer->start(10); } void SimulatorDialog::restoreRadioWidgetsState() { RadioWidget::RadioWidgetState state; QMap switchesMap; QMap analogsMap; QList states = g.profile[radioProfileId].simulatorOptions().controlsState; foreach (QByteArray ba, states) { QDataStream stream(ba); stream >> state; if (state.type == RadioWidget::RADIO_WIDGET_SWITCH) switchesMap.insert(state.index, ba); else analogsMap.insert(state.index, ba); } for (int i = 0; i < analogs.size(); ++i) { if (analogsMap.contains(analogs[i]->getIndex())) analogs[i]->setStateData(analogsMap.value(analogs[i]->getIndex())); } for (int i = 0; i < switches.size(); ++i) { if (switchesMap.contains(switches[i]->getIndex())) switches[i]->setStateData(switchesMap.value(switches[i]->getIndex())); } } QList SimulatorDialog::saveRadioWidgetsState() { QList states; for (int i = 0; i < analogs.size(); ++i) states.append(analogs[i]->getStateData()); for (int i = 0; i < switches.size(); ++i) states.append(switches[i]->getStateData()); return states; } /* * Input/Output handlers for SimulatorInterface */ // 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 QString phase_name; static TxOutputs prevOutputs; static int prevPhase = -1; int currentPhase; TxOutputs outputs; simulator->getValues(outputs); for (int i = 0; i < channelSliders.size() && i < CPN_MAX_CHNOUT; i++) { channelSliders[i]->setValue(qMin(1024, qMax(-1024, outputs.chans[i]))); channelValues[i]->setText(QString("%1").arg((qreal)outputs.chans[i]*100/1024, 0, 'f', 1)); } for (int i = 0; i < logicalSwitchLabels.size() && i < CPN_MAX_CSW; i++) { if (prevOutputs.vsw[i] != outputs.vsw[i]) { // setStyleSheet() is very CPU time consuming, doing it only on the actual // switch state change brings down CPU usage time (for Taranis simulation) from 40% to 7% (Linux, one core usage) logicalSwitchLabels[i]->setStyleSheet(outputs.vsw[i] ? CSWITCH_ON : CSWITCH_OFF); } } for (int gv = 0; gv < numGvars; gv++) { for (int fm = 0; fm < numFlightModes; fm++) { if (prevPhase != lastPhase || prevOutputs.gvars[fm][gv] != outputs.gvars[fm][gv]) { // same trick for GVARS, but this has far less effect on CPU usage as setStyleSheet() gvarValues[gv*numFlightModes+fm]->setText(QString((fm == lastPhase) ? "%1" : "%1").arg(outputs.gvars[fm][gv])); } } } // display current flight mode in window title currentPhase = simulator->getPhase(); if (currentPhase != lastPhase) { lastPhase = currentPhase; phase_name = QString(simulator->getPhaseName(currentPhase)); if (phase_name.isEmpty()) phase_name = QString::number(currentPhase); setWindowTitle(windowName + QString(" - Flight Mode %1").arg(phase_name)); } prevOutputs = outputs; prevPhase = lastPhase; } // "get" values from this UI and send them to the firmware simulator. void SimulatorDialog::getValues() { static const int numTrims = firmware->getCapability(NumTrimSwitches); int i; TxInputs inp; memset(&inp, 0, sizeof(TxInputs)); inp.sticks[0] = int(1024 * vJoyLeft->getStickX()); // LEFT HORZ inp.sticks[1] = int(-1024 * vJoyLeft->getStickY()); // LEFT VERT inp.sticks[2] = int(-1024 * vJoyRight->getStickY()); // RGHT VERT inp.sticks[3] = int(1024 * vJoyRight->getStickX()); // RGHT HORZ for (i = 0; i < analogs.size() && i < CPN_MAX_POTS; ++i) inp.pots[analogs[i]->getIndex()] = analogs[i]->getValue(); for (i = 0; i < switches.size() && i < CPN_MAX_SWITCHES; ++i) inp.switches[switches[i]->getIndex()] = switches[i]->getValue(); for (i = 0; i < numTrims; ++i) inp.trims[i] = (trimPressed == i); foreach (RadioUiAction * act, radioUiWidget->getActions()) { if (act->getIndex() > -1 && act->getIndex() < CPN_MAX_KEYS) inp.keys[act->getIndex()] = act->isActive(); } if (radioUiWidget->getRotEncAction()) inp.rotenc = radioUiWidget->getRotEncAction()->isActive(); simulator->setValues(inp); } // Read stick trim values from firmware simulator and set joystick widgets as needed. void SimulatorDialog::setTrims() { typedef VirtualJoystickWidget VJW; static Trims lastTrims; Trims trims; simulator->getTrims(trims); if (trims.values[VJW::TRIM_AXIS_L_X] != lastTrims.values[VJW::TRIM_AXIS_L_X]) vJoyLeft->setTrimValue(VJW::TRIM_AXIS_L_X, trims.values[VJW::TRIM_AXIS_L_X]); if (trims.values[VJW::TRIM_AXIS_L_Y] != lastTrims.values[VJW::TRIM_AXIS_L_Y]) vJoyLeft->setTrimValue(VJW::TRIM_AXIS_L_Y, trims.values[VJW::TRIM_AXIS_L_Y]); if (trims.values[VJW::TRIM_AXIS_R_Y] != lastTrims.values[VJW::TRIM_AXIS_R_Y]) vJoyRight->setTrimValue(VJW::TRIM_AXIS_R_Y, trims.values[VJW::TRIM_AXIS_R_Y]); if (trims.values[VJW::TRIM_AXIS_R_X] != lastTrims.values[VJW::TRIM_AXIS_R_X]) vJoyRight->setTrimValue(VJW::TRIM_AXIS_R_X, trims.values[VJW::TRIM_AXIS_R_X]); if (trims.extended != lastTrims.extended) { int trimMin = -125, trimMax = +125; if (trims.extended) { trimMin = -500; trimMax = +500; } vJoyLeft->setTrimRange(VJW::TRIM_AXIS_L_X, trimMin, trimMax); vJoyLeft->setTrimRange(VJW::TRIM_AXIS_L_Y, trimMin, trimMax); vJoyRight->setTrimRange(VJW::TRIM_AXIS_R_Y, trimMin, trimMax); vJoyRight->setTrimRange(VJW::TRIM_AXIS_R_X, trimMin, trimMax); } lastTrims = trims; } /* * Event handlers/private slots */ void SimulatorDialog::closeEvent(QCloseEvent *) { simulator->stop(); timer->stop(); SimulatorOptions opts = g.profile[radioProfileId].simulatorOptions(); opts.windowGeometry = saveGeometry(); opts.controlsState = saveRadioWidgetsState(); g.profile[radioProfileId].simulatorOptions(opts); if (saveTempRadioData) saveTempData(); if (deleteTempRadioData) deleteTempData(); } void SimulatorDialog::showEvent(QShowEvent *) { static bool firstShow = true; 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::mousePressEvent(QMouseEvent *event) { if (radioUiWidget) radioUiWidget->mousePressEvent(event); } void SimulatorDialog::mouseReleaseEvent(QMouseEvent *event) { if (radioUiWidget) radioUiWidget->mouseReleaseEvent(event); } void SimulatorDialog::wheelEvent(QWheelEvent *event) { if (radioUiWidget) radioUiWidget->wheelEvent(event); } void SimulatorDialog::onTimerEvent() { static unsigned int lcd_counter = 0; if (!simulator->timer10ms()) { QMessageBox::critical(this, "Companion", tr("Firmware %1 error: %2").arg(firmware->getName()).arg(simulator->getError())); timer->stop(); return; } getValues(); if (!(lcd_counter++ % 5)) { setValues(); setTrims(); centerSticks(); } radioUiWidget->timedUpdate(lcd_counter); updateDebugOutput(); } void SimulatorDialog::onTrimPressed(int which) { trimPressed = which; } void SimulatorDialog::onTrimReleased() { trimPressed = TRIM_NONE; } void SimulatorDialog::onTrimSliderMoved(int which, int value) { simulator->setTrim(which, value); } void SimulatorDialog::centerSticks() { if (vJoyLeft) vJoyLeft->centerStick(); if (vJoyRight) vJoyRight->centerStick(); } void SimulatorDialog::openTelemetrySimulator() { // allow only one instance if (TelemetrySimu == 0) { TelemetrySimu = new TelemetrySimulator(this, simulator); 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->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->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) { int stick; if (axis>=0 && axis<=8) { stick=jsmap[axis]; int stickval; if (value>jscal[axis][1]) { if ((jscal[axis][2]-jscal[axis][1])==0) return; stickval=(1024*(value-jscal[axis][1]))/(jscal[axis][2]-jscal[axis][1]); } else { if ((jscal[axis][1]-jscal[axis][0])==0) return; stickval=(1024*(value-jscal[axis][1]))/(jscal[axis][1]-jscal[axis][0]); } if (jscal[axis][3]==1) { stickval*=-1; } if (stick==1 ) { vJoyRight->setStickY(-stickval/1024.0); } else if (stick==2) { vJoyRight->setStickX(stickval/1024.0); } else if (stick==3) { vJoyLeft->setStickY(-stickval/1024.0); } else if (stick==4) { vJoyLeft->setStickX(stickval/1024.0); } else if (stick >= 5 && stick < 5 + analogs.count()) { analogs[stick-5]->setValue(stickval); } } } #endif