[Simulator] Asynchronous SimulatorInterface & a few new features. (#4738)
[opentx.git] / companion / src / simulation / simulatormainwindow.cpp
blob9fe5e969e311a1416efce9ac6b74674c1530911d
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "simulatormainwindow.h"
22 #include "ui_simulatormainwindow.h"
24 #include "appdata.h"
25 #include "debugoutput.h"
26 #include "radiooutputswidget.h"
27 #include "simulatorwidget.h"
28 #include "simulatorinterface.h"
29 #include "telemetrysimu.h"
30 #include "trainersimu.h"
31 #ifdef JOYSTICKS
32 #include "joystickdialog.h"
33 #endif
35 #include <QDebug>
36 #include <QLabel>
37 #include <QMessageBox>
39 extern AppData g; // ensure what "g" means
41 const quint16 SimulatorMainWindow::m_savedUiStateVersion = 2;
43 SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, const QString & firmwareId, quint8 flags, Qt::WindowFlags wflags) :
44 QMainWindow(parent, wflags),
45 ui(new Ui::SimulatorMainWindow),
46 m_simulatorWidget(NULL),
47 m_consoleWidget(NULL),
48 m_outputsWidget(NULL),
49 m_simulatorDockWidget(NULL),
50 m_consoleDockWidget(NULL),
51 m_telemetryDockWidget(NULL),
52 m_trainerDockWidget(NULL),
53 m_outputsDockWidget(NULL),
54 m_simulatorId(firmwareId),
55 m_exitStatusCode(0),
56 m_radioProfileId(g.sessionId()),
57 m_radioSizeConstraint(Qt::Horizontal | Qt::Vertical),
58 m_firstShow(true),
59 m_showRadioDocked(true),
60 m_showMenubar(true)
62 if (m_simulatorId.isEmpty()) {
63 m_simulatorId = SimulatorLoader::findSimulatorByFirmwareName(getCurrentFirmware()->getId());
65 m_simulator = SimulatorLoader::loadSimulator(m_simulatorId);
66 if (!m_simulator) {
67 m_exitStatusMsg = tr("ERROR: Failed to create simulator interface, possibly missing or bad library.");
68 m_exitStatusCode = -1;
69 return;
72 m_simulator->moveToThread(&simuThread);
73 simuThread.start();
75 ui->setupUi(this);
77 setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
78 setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
79 setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
80 setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
82 m_simulatorWidget = new SimulatorWidget(this, m_simulator, flags);
83 setWindowTitle(m_simulatorWidget->windowTitle());
85 toggleRadioDocked(true);
86 createDockWidgets();
88 ui->actionReloadLua->setIcon(SimulatorIcon("reload_script"));
89 ui->actionReloadRadioData->setIcon(SimulatorIcon("restart"));
90 ui->actionJoystickSettings->setIcon(SimulatorIcon("joystick_settings"));
91 ui->actionScreenshot->setIcon(SimulatorIcon("camera"));
92 ui->actionShowKeymap->setIcon(SimulatorIcon("info"));
93 ui->actionToggleMenuBar->setIcon(ui->toolBar->toggleViewAction()->icon());
94 ui->actionFixedRadioWidth->setIcon(ui->toolBar->toggleViewAction()->icon());
95 ui->actionFixedRadioHeight->setIcon(ui->toolBar->toggleViewAction()->icon());
96 ui->actionDockRadio->setIcon(ui->toolBar->toggleViewAction()->icon());
98 ui->toolBar->toggleViewAction()->setShortcut(tr("Alt+T"));
99 ui->toolBar->setIconSize(SimulatorIcon::toolbarIconSize(g.iconSize()));
100 ui->toolBar->insertSeparator(ui->actionReloadLua);
102 // add these to this window directly to maintain shorcuts when menubar is hidden
103 addAction(ui->toolBar->toggleViewAction());
104 addAction(ui->actionToggleMenuBar);
106 ui->menuView->insertSeparator(ui->actionToggleMenuBar);
107 ui->menuView->insertAction(ui->actionToggleMenuBar, ui->toolBar->toggleViewAction());
109 // Hide some actions based on simulator capabilities.
110 if(!m_simulator->getCapability(SimulatorInterface::CAP_LUA))
111 ui->actionReloadLua->setDisabled(true);
112 if(!m_simulator->getCapability(SimulatorInterface::CAP_TELEM_FRSKY_SPORT))
113 m_telemetryDockWidget->toggleViewAction()->setDisabled(true);
114 #ifndef JOYSTICKS
115 ui->actionJoystickSettings->setDisabled(true);
116 #endif
118 restoreUiState();
120 setStyleSheet(SimulatorStyle::styleSheet());
122 connect(ui->actionShowKeymap, &QAction::triggered, this, &SimulatorMainWindow::showHelp);
123 connect(ui->actionJoystickSettings, &QAction::triggered, this, &SimulatorMainWindow::openJoystickDialog);
124 connect(ui->actionToggleMenuBar, &QAction::toggled, this, &SimulatorMainWindow::showMenuBar);
125 connect(ui->actionFixedRadioWidth, &QAction::toggled, this, &SimulatorMainWindow::showRadioFixedWidth);
126 connect(ui->actionFixedRadioHeight, &QAction::toggled, this, &SimulatorMainWindow::showRadioFixedHeight);
127 connect(ui->actionDockRadio, &QAction::toggled, this, &SimulatorMainWindow::showRadioDocked);
128 connect(ui->actionReloadRadioData, &QAction::triggered, this, &SimulatorMainWindow::simulatorRestart);
130 connect(ui->actionReloadLua, &QAction::triggered, m_simulator, &SimulatorInterface::setLuaStateReloadPermanentScripts);
132 if (m_outputsWidget) {
133 connect(this, &SimulatorMainWindow::simulatorStart, m_outputsWidget, &RadioOutputsWidget::start);
134 connect(this, &SimulatorMainWindow::simulatorRestart, m_outputsWidget, &RadioOutputsWidget::restart);
137 if (m_simulatorWidget) {
138 connect(this, &SimulatorMainWindow::simulatorStart, m_simulatorWidget, &SimulatorWidget::start);
139 connect(this, &SimulatorMainWindow::simulatorRestart, m_simulatorWidget, &SimulatorWidget::restart);
140 connect(ui->actionScreenshot, &QAction::triggered, m_simulatorWidget, &SimulatorWidget::captureScreenshot);
141 connect(m_simulatorWidget, &SimulatorWidget::windowTitleChanged, this, &SimulatorMainWindow::setWindowTitle);
147 SimulatorMainWindow::~SimulatorMainWindow()
149 if (m_telemetryDockWidget)
150 delete m_telemetryDockWidget;
151 if (m_trainerDockWidget)
152 delete m_trainerDockWidget;
153 if (m_outputsDockWidget)
154 delete m_outputsDockWidget;
155 if (m_simulatorDockWidget)
156 delete m_simulatorDockWidget;
157 else if (m_simulatorWidget)
158 delete m_simulatorWidget;
159 if (m_consoleDockWidget)
160 delete m_consoleDockWidget;
162 delete ui;
164 if (m_simulator) {
165 simuThread.quit();
166 simuThread.wait();
167 delete m_simulator;
169 SimulatorLoader::unloadSimulator(m_simulatorId);
172 void SimulatorMainWindow::closeEvent(QCloseEvent *)
174 saveUiState();
177 void SimulatorMainWindow::show()
179 QMainWindow::show();
180 if (m_firstShow) {
181 m_firstShow = false;
182 #ifdef Q_OS_LINUX
183 // for whatever reason, w/out this workaround any floating docks may appear and get "stuck" behind other windows, eg. Terminal or Companion.
184 restoreUiState();
185 #endif
186 start();
190 void SimulatorMainWindow::changeEvent(QEvent *e)
192 QMainWindow::changeEvent(e);
193 switch (e->type()) {
194 case QEvent::LanguageChange:
195 ui->retranslateUi(this);
196 break;
197 default:
198 break;
202 QMenu * SimulatorMainWindow::createPopupMenu()
204 QMenu * menu = QMainWindow::createPopupMenu();
205 menu->clear();
206 menu->addActions(ui->menuView->actions());
207 return menu;
210 void SimulatorMainWindow::saveUiState()
212 QByteArray state;
213 QDataStream stream(&state, QIODevice::WriteOnly);
214 stream << m_savedUiStateVersion << saveState(m_savedUiStateVersion)
215 << m_showMenubar << m_showRadioDocked << m_radioSizeConstraint;
217 SimulatorOptions opts = g.profile[m_radioProfileId].simulatorOptions();
218 opts.windowState = state;
219 opts.windowGeometry = saveGeometry();
220 g.profile[m_radioProfileId].simulatorOptions(opts);
223 void SimulatorMainWindow::restoreUiState()
225 quint16 ver = 0;
226 QByteArray windowState;
227 QByteArray state = g.profile[m_radioProfileId].simulatorOptions().windowState;
228 QDataStream stream(state);
230 stream >> ver;
231 if (ver && ver <= m_savedUiStateVersion) {
232 stream >> windowState >> m_showMenubar >> m_showRadioDocked;
233 if (ver >= 2)
234 stream >> m_radioSizeConstraint;
237 toggleRadioDocked(m_showRadioDocked);
238 setRadioSizePolicy(m_radioSizeConstraint);
239 toggleMenuBar(m_showMenubar);
240 restoreGeometry(g.profile[m_radioProfileId].simulatorOptions().windowGeometry);
241 restoreState(windowState, m_savedUiStateVersion);
244 int SimulatorMainWindow::getExitStatus(QString * msg)
246 if (msg)
247 *msg = m_exitStatusMsg;
248 return m_exitStatusCode;
251 bool SimulatorMainWindow::setRadioData(RadioData * radioData)
253 return m_simulatorWidget->setRadioData(radioData);
256 bool SimulatorMainWindow::useTempDataPath(bool deleteOnClose)
258 return m_simulatorWidget->useTempDataPath(deleteOnClose);
261 bool SimulatorMainWindow::setOptions(SimulatorOptions & options, bool withSave)
263 return m_simulatorWidget->setOptions(options, withSave);
266 void SimulatorMainWindow::start()
268 emit simulatorStart();
271 void SimulatorMainWindow::createDockWidgets()
273 if (!m_outputsDockWidget) {
274 SimulatorIcon icon("radio_outputs");
275 m_outputsDockWidget = new QDockWidget(tr("Radio Outputs"), this);
276 m_outputsWidget = new RadioOutputsWidget(m_simulator, getCurrentFirmware(), this);
277 m_outputsDockWidget->setWidget(m_outputsWidget);
278 m_outputsDockWidget->setObjectName("OUTPUTS");
279 addTool(m_outputsDockWidget, Qt::RightDockWidgetArea, icon, QKeySequence(tr("F2")));
282 if (!m_telemetryDockWidget) {
283 SimulatorIcon icon("telemetry");
284 m_telemetryDockWidget = new QDockWidget(tr("Telemetry Simulator"), this);
285 TelemetrySimulator * telem = new TelemetrySimulator(this, m_simulator);
286 m_telemetryDockWidget->setWidget(telem);
287 m_telemetryDockWidget->setObjectName("TELEMETRY_SIMULATOR");
288 addTool(m_telemetryDockWidget, Qt::LeftDockWidgetArea, icon, QKeySequence(tr("F4")));
291 if (!m_trainerDockWidget) {
292 SimulatorIcon icon("trainer");
293 m_trainerDockWidget = new QDockWidget(tr("Trainer Simulator"), this);
294 TrainerSimulator * trainer = new TrainerSimulator(this, m_simulator);
295 m_trainerDockWidget->setWidget(trainer);
296 m_trainerDockWidget->setObjectName("TRAINER_SIMULATOR");
297 addTool(m_trainerDockWidget, Qt::TopDockWidgetArea, icon, QKeySequence(tr("F5")));
300 if (!m_consoleDockWidget) {
301 SimulatorIcon icon("console");
302 m_consoleDockWidget = new QDockWidget(tr("Debug Output"), this);
303 m_consoleWidget = new DebugOutput(this, m_simulator);
304 m_consoleDockWidget->setWidget(m_consoleWidget);
305 m_consoleDockWidget->setObjectName("CONSOLE");
306 addTool(m_consoleDockWidget, Qt::RightDockWidgetArea, icon, QKeySequence(tr("F6")));
310 void SimulatorMainWindow::addTool(QDockWidget * widget, Qt::DockWidgetArea area, QIcon icon, QKeySequence shortcut)
312 QAction* tempAction = widget->toggleViewAction();
313 tempAction->setIcon(icon);
314 tempAction->setShortcut(shortcut);
315 ui->menuView->insertAction(ui->actionToggleMenuBar, tempAction);
316 ui->toolBar->insertAction(ui->actionReloadLua, tempAction);
317 widget->setWindowIcon(icon);
318 widget->widget()->setWindowIcon(icon);
319 addDockWidget(area, widget);
320 widget->hide();
321 widget->setFloating(true);
323 // Upon subsequent launches of application, any previously un-shown floating widgets get
324 // positioned at screen location (0,0 - frameGeometry.topLeft) which is awkward at best.
325 // This ensures newly shown floating widgets don't get stuck in top left corner.
326 connect(widget, &QDockWidget::visibilityChanged, [this, widget](bool visible) {
327 if (visible && widget->isFloating() && widget->geometry().topLeft() == QPoint(0,0)) {
328 // position top left corner in middle of this parent window.
329 QPoint newPos(pos() + (geometry().bottomRight() - geometry().topLeft()) / 2);
330 widget->move(newPos);
335 void SimulatorMainWindow::showMenuBar(bool show)
337 if (m_showMenubar != show)
338 toggleMenuBar(show);
341 void SimulatorMainWindow::toggleMenuBar(bool show)
343 ui->menubar->setVisible(show);
344 m_showMenubar = show;
345 if (ui->actionToggleMenuBar->isChecked() != show)
346 ui->actionToggleMenuBar->setChecked(show);
349 void SimulatorMainWindow::showRadioFixedSize(Qt::Orientation orientation, bool fixed)
351 int fix = m_radioSizeConstraint;
352 if (fixed)
353 fix |= orientation;
354 else
355 fix &= ~(orientation);
357 if (m_radioSizeConstraint != fix)
358 setRadioSizePolicy(fix);
361 void SimulatorMainWindow::showRadioFixedWidth(bool fixed)
363 showRadioFixedSize(Qt::Horizontal, fixed);
366 void SimulatorMainWindow::showRadioFixedHeight(bool fixed)
368 showRadioFixedSize(Qt::Vertical, fixed);
371 void SimulatorMainWindow::setRadioSizePolicy(int fixType)
373 QSizePolicy sp;
374 sp.setHorizontalPolicy((fixType & Qt::Horizontal) ? QSizePolicy::Maximum : QSizePolicy::Preferred);
375 sp.setVerticalPolicy((fixType & Qt::Vertical) ? QSizePolicy::Maximum : QSizePolicy::Preferred);
376 m_simulatorWidget->setSizePolicy(sp);
378 m_radioSizeConstraint = fixType;
380 if (ui->actionFixedRadioWidth->isChecked() != bool(fixType & Qt::Horizontal))
381 ui->actionFixedRadioWidth->setChecked((fixType & Qt::Horizontal));
382 if (ui->actionFixedRadioHeight->isChecked() != bool(fixType & Qt::Vertical))
383 ui->actionFixedRadioHeight->setChecked((fixType & Qt::Vertical));
386 void SimulatorMainWindow::showRadioDocked(bool dock)
388 if (m_showRadioDocked != dock)
389 toggleRadioDocked(dock);
392 void SimulatorMainWindow::toggleRadioDocked(bool dock)
394 if (!m_simulatorWidget)
395 return;
397 if (dock) {
398 if (m_simulatorDockWidget) {
399 m_simulatorDockWidget->setWidget(0);
400 m_simulatorDockWidget->deleteLater();
401 m_simulatorDockWidget = NULL;
404 QWidget * w = takeCentralWidget();
405 if (w && w != m_simulatorWidget)
406 w->deleteLater();
407 setCentralWidget(m_simulatorWidget);
408 setRadioSizePolicy(m_radioSizeConstraint);
409 ui->actionFixedRadioWidth->setEnabled(true);
410 ui->actionFixedRadioHeight->setEnabled(true);
411 m_simulatorWidget->show();
413 else {
415 if (m_simulatorDockWidget) {
416 m_simulatorDockWidget->deleteLater();
417 m_simulatorDockWidget = NULL;
420 takeCentralWidget();
421 QLabel * dummy = new QLabel("");
422 dummy->setFixedSize(0, 0);
423 dummy->setEnabled(false);
424 setCentralWidget(dummy);
426 m_simulatorDockWidget = new QDockWidget(m_simulatorWidget->windowTitle(), this);
427 m_simulatorDockWidget->setObjectName("RADIO_SIMULATOR");
428 m_simulatorDockWidget->setWidget(m_simulatorWidget);
429 m_simulatorDockWidget->setFeatures(QDockWidget::DockWidgetFloatable);
430 m_simulatorDockWidget->setAllowedAreas(Qt::BottomDockWidgetArea);
431 addDockWidget(Qt::BottomDockWidgetArea, m_simulatorDockWidget);
432 m_simulatorDockWidget->setFloating(true);
433 m_simulatorWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
434 ui->actionFixedRadioWidth->setDisabled(true);
435 ui->actionFixedRadioHeight->setDisabled(true);
436 restoreDockWidget(m_simulatorDockWidget);
437 if (!m_simulatorDockWidget->isVisible())
438 m_simulatorDockWidget->show();
439 if (m_simulatorDockWidget->geometry().topLeft() == QPoint(0,0) && this->isVisible()) {
440 // default position top left corner in middle of this parent window.
441 QPoint newPos(pos() + (geometry().bottomRight() - geometry().topLeft()) / 2);
442 m_simulatorDockWidget->move(newPos);
445 connect(m_simulatorWidget, &SimulatorWidget::windowTitleChanged, m_simulatorDockWidget, &QDockWidget::setWindowTitle);
446 connect(m_simulatorDockWidget, &QDockWidget::topLevelChanged, [this](bool top) {
447 showRadioDocked(!top);
451 m_showRadioDocked = dock;
453 if (ui->actionDockRadio->isChecked() != dock)
454 ui->actionDockRadio->setChecked(dock);
458 void SimulatorMainWindow::openJoystickDialog(bool)
460 #ifdef JOYSTICKS
461 joystickDialog * jd = new joystickDialog(this);
462 if (jd->exec() == QDialog::Accepted && m_simulatorWidget)
463 m_simulatorWidget->setupJoysticks();
464 jd->deleteLater();
465 #endif
468 void SimulatorMainWindow::showHelp(bool show)
470 QString helpText = ""
471 "<style>"
472 " td { text-align: center; vertical-align: middle; font-size: large; padding: 0 1em; white-space: nowrap; }"
473 " th { background-color: palette(alternate-base); }"
474 " img { vertical-align: text-top; }"
475 "</style>";
476 helpText += tr("<b>Simulator Controls:</b>");
477 helpText += "<table cellspacing=4 cellpadding=0>";
478 helpText += tr("<tr><th>Key/Mouse</th><th>Action</th></tr>", "note: must match html layout of each table row (keyTemplate).");
480 QString keyTemplate = tr("<tr><td><kbd>%1</kbd></td><td>%2</td></tr>", "note: must match html layout of help text table header.");
481 keymapHelp_t pair;
482 // Add our own help text (if any)
483 foreach (pair, m_keymapHelp)
484 helpText += keyTemplate.arg(pair.first, pair.second);
485 // Add any radio-specific help text from simulator widget
486 foreach (pair, m_simulatorWidget->getKeymapHelp())
487 helpText += keyTemplate.arg(pair.first, pair.second);
489 helpText += "</table>";
491 QMessageBox * msgBox = new QMessageBox(this);
492 msgBox->setObjectName("SimulatorHelpText");
493 msgBox->setAttribute(Qt::WA_DeleteOnClose);
494 msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint);
495 msgBox->setStandardButtons( QMessageBox::Ok );
496 msgBox->setWindowTitle(tr("Simulator Help"));
497 msgBox->setTextFormat(Qt::RichText);
498 msgBox->setText(helpText);
499 msgBox->setModal(false);
500 msgBox->show();