RTXI  3.0.0
The Real-Time eXperiment Interface Reference Manual
main_window.cpp
Go to the documentation of this file.
1 /*
2  The Real-Time eXperiment Interface (RTXI)
3  Copyright (C) 2011 Georgia Institute of Technology, University of Utah,
4  Will Cornell Medical College
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18 
19 */
20 
21 #include <QApplication>
22 #include <QDesktopServices>
23 #include <QDir>
24 #include <QFileDialog>
25 #include <QMenuBar>
26 #include <QMessageBox>
27 #include <QSettings>
28 #include <QString>
29 #include <QUrl>
30 #include <algorithm>
31 #include <string>
32 
33 #include "main_window.hpp"
34 
35 #include <fmt/core.h>
36 
37 #include "connector/connector.hpp"
39 #include "debug.hpp"
40 #include "event.hpp"
44 #include "rtxiConfig.h"
46 #include "userprefs/userprefs.hpp"
47 #include "widgets.hpp"
48 #include "workspace.hpp"
49 
51  : QMainWindow(nullptr, Qt::Window)
52  , event_manager(ev_manager)
53  , mdiArea(new QMdiArea)
54 {
55  // Make central RTXI parent widget
56  setCentralWidget(mdiArea);
57 
58  /* Initialize Window Settings */
59  setWindowTitle("RTXI - Real-time eXperimental Interface");
60  setWindowIcon(QIcon("/usr/share/rtxi/RTXI-icon.png"));
61 
62  /* Set Qt Settings Information */
63  QApplication::setOrganizationName("RTXI");
64  QApplication::setOrganizationDomain("rtxi.org");
65  QApplication::setApplicationName("RTXI");
66 
67  /* Initialize Menus */
68  createFileActions();
69  createFileMenu();
70 
71  /* Initialize Widget Menu */
72  createWidgetMenu();
73 
74  /* Initialize Utilities menu */
75  createUtilMenu();
76 
77  /* Initialize System Menu */
78  createSystemActions();
79  createSystemMenu();
80 
81  /* Initialize Windows Menu */
82  createWindowsMenu();
83 
84  /* Initialize Help Menu */
85  createHelpActions();
86  createHelpMenu();
87 }
88 
90 {
91  return moduleMenu->addSeparator();
92 }
93 
94 QAction* MainWindow::createFileMenuItem(const QString& label)
95 {
96  return fileMenu->addAction(label);
97 }
98 
100 {
101  // Clear but add back default actions
102  fileMenu->clear();
103  fileMenu->addAction(load);
104  fileMenu->addAction(save);
105  fileMenu->addAction(reset);
106  fileMenu->addSeparator();
107  fileMenu->addAction(quit);
108  fileMenu->addSeparator();
109 }
110 
111 QAction* MainWindow::createWidgetMenuItem(const QString& text)
112 {
113  return moduleMenu->addAction(text);
114 }
115 
116 QAction* MainWindow::createWidgetMenuItem(const QString& text,
117  const QObject* receiver,
118  const char* member)
119 {
120  return moduleMenu->addAction(text, receiver, member);
121 }
122 
123 void MainWindow::setWidgetMenuItemParameter(QAction* action, int parameter)
124 {
125  action->setData(parameter);
126 }
127 
129 {
130  moduleMenu->clear();
131 }
132 
133 void MainWindow::changeWidgetMenuItem(QAction* action, const QString& text)
134 {
135  action->setText(text);
136 }
137 
138 void MainWindow::removeWidgetMenuItem(QAction* action)
139 {
140  const QList<QAction*> actionList = moduleMenu->actions();
141  if (!actionList.empty()) {
142  moduleMenu->removeAction(action);
143  }
144 }
145 
146 QAction* MainWindow::createUtilMenuItem(const QString& label,
147  const QObject* handler,
148  const char* slot)
149 {
150  return utilMenu->addAction(label, handler, slot);
151 }
152 
153 void MainWindow::createFileMenu()
154 {
155  fileMenu = menuBar()->addMenu(tr("&File"));
156  fileMenu->addAction(load);
157  fileMenu->addAction(save);
158  fileMenu->addAction(reset);
159  fileMenu->addSeparator();
160  fileMenu->addAction(quit);
161  fileMenu->addSeparator();
162  connect(fileMenu,
163  SIGNAL(triggered(QAction*)),
164  this,
165  SLOT(fileMenuActivated(QAction*)));
166 }
167 
168 void MainWindow::createWidgetMenu()
169 {
170  moduleMenu = menuBar()->addMenu(tr("&Widgets"));
171  this->loadDynamicWidget = new QAction("Load Plugin", this);
172  moduleMenu->addAction(this->loadDynamicWidget);
173  connect(moduleMenu,
174  SIGNAL(triggered(QAction*)),
175  this,
176  SLOT(modulesMenuActivated(QAction*)));
177 }
178 
179 void MainWindow::createUtilMenu()
180 {
181  utilMenu = menuBar()->addMenu(tr("&Utilities"));
182  filtersSubMenu = utilMenu->addMenu(tr("&Filters"));
183  signalsSubMenu = utilMenu->addMenu(tr("&Signals"));
184  utilitiesSubMenu = utilMenu->addMenu(tr("&Utilities"));
185  connect(utilMenu,
186  SIGNAL(triggered(QAction*)),
187  this,
188  SLOT(utilitiesMenuActivated(QAction*)));
189 
190  QDir libsDir = QCoreApplication::applicationDirPath() + QDir::separator()
191  + QString("rtxi_modules");
192  if (!libsDir.exists()) {
193  return;
194  }
195  libsDir.setNameFilters(QStringList("*.so"));
196  for (const auto& entryItem : libsDir.entryList()) {
197  utilItem = new QAction(entryItem, this);
198  if (entryItem.contains("analysis") || entryItem.contains("sync")
199  || entryItem.contains("mimic"))
200  {
201  utilitiesSubMenu->addAction(utilItem);
202  } else if (entryItem.contains("iir") || entryItem.contains("fir")) {
203  filtersSubMenu->addAction(utilItem);
204  } else if (entryItem.contains("signal") || entryItem.contains("noise")
205  || entryItem.contains("ttl") || entryItem.contains("maker"))
206  {
207  signalsSubMenu->addAction(utilItem);
208  } else {
209  delete utilItem;
210  }
211  }
212 }
213 
214 void MainWindow::createSystemMenu()
215 {
216  this->systemMenu = menuBar()->addMenu(tr("&System"));
217  this->systemMenu->addAction(this->openRTBenchmarks);
218  this->systemMenu->addAction(this->openUserPrefs);
219  this->systemMenu->addAction(this->openControlPanel);
220  this->systemMenu->addAction(this->openConnector);
221  this->systemMenu->addAction(this->openOscilloscope);
222  this->systemMenu->addAction(this->openDataRecorder);
223  this->systemMenu->addAction(this->openRTXIWizard);
224  connect(systemMenu,
225  SIGNAL(triggered(QAction*)),
226  this,
227  SLOT(systemMenuActivated(QAction*)));
228 }
229 
230 void MainWindow::createWindowsMenu()
231 {
232  windowsMenu = menuBar()->addMenu(tr("&Windows"));
233  connect(
234  windowsMenu, SIGNAL(aboutToShow()), this, SLOT(windowsMenuAboutToShow()));
235 }
236 
237 void MainWindow::createHelpMenu()
238 {
239  this->helpMenu = menuBar()->addMenu(tr("&Help"));
240  this->helpMenu->addSeparator();
241  this->helpMenu->addAction(artxi);
242  this->helpMenu->addAction(axeno);
243  this->helpMenu->addAction(aqt);
244  this->helpMenu->addSeparator();
245  this->helpMenu->addAction(adocs);
246  this->helpMenu->addAction(sub_issue);
247 }
248 
249 void MainWindow::createFileActions()
250 {
251  this->load = new QAction(tr("&Load Workspace"), this);
252  this->load->setShortcuts(QKeySequence::Open);
253  this->load->setStatusTip(tr("Load a saved workspace"));
254  connect(load, SIGNAL(triggered()), this, SLOT(loadSettings()));
255 
256  this->save = new QAction(tr("&Save Workspace"), this);
257  this->save->setShortcuts(QKeySequence::Save);
258  this->save->setStatusTip(tr("Save current workspace"));
259  connect(save, SIGNAL(triggered()), this, SLOT(saveSettings()));
260 
261  this->reset = new QAction(tr("&Reset Workspace"), this);
262  this->reset->setStatusTip(tr("Reset to default RTXI workspace"));
263  connect(reset, SIGNAL(triggered()), this, SLOT(resetSettings()));
264 
265  this->quit = new QAction(tr("&Quit"), this);
266  this->quit->setShortcut(tr("Ctrl+Q"));
267  this->quit->setStatusTip(tr("Quit RTXI"));
268  connect(QCoreApplication::instance(),
269  SIGNAL(aboutToQuit()),
270  mdiArea,
271  SLOT(closeAllSubWindows()));
272  connect(quit, SIGNAL(triggered()), this, SLOT(close()));
273 }
274 
275 // void MainWindow::createMdi(QMdiSubWindow* subWindow)
276 //{
277 // mdiArea->addSubWindow(subWindow);
278 // }
279 
280 void MainWindow::createHelpActions()
281 {
282  artxi = new QAction(tr("About &RTXI"), this);
283  connect(artxi, SIGNAL(triggered()), this, SLOT(about()));
284 
285  axeno = new QAction(tr("About &Xenomai"), this);
286  connect(axeno, SIGNAL(triggered()), this, SLOT(aboutXeno()));
287 
288  aqt = new QAction(tr("About &Qt"), this);
289  connect(aqt, SIGNAL(triggered()), this, SLOT(aboutQt()));
290 
291  adocs = new QAction(tr("&Documentation"), this);
292  connect(adocs, SIGNAL(triggered()), this, SLOT(openDocs()));
293 
294  sub_issue = new QAction(tr("&Submit Issue"), this);
295  connect(sub_issue, SIGNAL(triggered()), this, SLOT(openSubIssue()));
296 }
297 
298 void MainWindow::createSystemActions()
299 {
300  this->openRTBenchmarks = new QAction(
301  tr(std::string(PerformanceMeasurement::MODULE_NAME).c_str()), this);
302  this->openUserPrefs =
303  new QAction(tr(std::string(UserPrefs::MODULE_NAME).c_str()), this);
304  this->openControlPanel =
305  new QAction(tr(std::string(SystemControl::MODULE_NAME).c_str()), this);
306  this->openConnector =
307  new QAction(tr(std::string(Connector::MODULE_NAME).c_str()), this);
308  this->openOscilloscope =
309  new QAction(tr(std::string(Oscilloscope::MODULE_NAME).c_str()), this);
310  this->openDataRecorder =
311  new QAction(tr(std::string(DataRecorder::MODULE_NAME).c_str()), this);
312  this->openRTXIWizard =
313  new QAction(tr(std::string(RTXIWizard::MODULE_NAME).c_str()), this);
314 }
315 
316 void MainWindow::about()
317 {
318  const std::string version_str = fmt::format(
319  "{}.{}.{}", RTXI_VERSION_MAJOR, RTXI_VERSION_MINOR, RTXI_VERSION_PATCH);
320  QMessageBox::about(
321  this,
322  "About RTXI",
323  QString("RTXI Version ") + QString(version_str.c_str())
324  + QString(
325  "\n\nReleased under the GPLv3.\nSee www.rtxi.org for details."));
326 }
327 
328 void MainWindow::aboutQt()
329 {
330  QMessageBox::aboutQt(this);
331 }
332 
333 void MainWindow::aboutXeno()
334 {
335  QMessageBox::about(
336  this, "About Xenomai", "Running POSIX (non-RT) real-time core");
337 }
338 
339 void MainWindow::openDocs()
340 {
341  QDesktopServices::openUrl(QUrl("http://rtxi.org/docs/", QUrl::TolerantMode));
342 }
343 
344 void MainWindow::openSubIssue()
345 {
346  QDesktopServices::openUrl(
347  QUrl("https://github.com/rtxi/rtxi/issues", QUrl::TolerantMode));
348 }
349 
350 /*
351  * Load MainWindow settings
352  */
354 {
355  QSettings userprefs;
356  userprefs.beginGroup("MainWindow");
357  restoreGeometry(userprefs.value("geometry", saveGeometry()).toByteArray());
358  move(userprefs.value("pos", pos()).toPoint());
359  resize(userprefs.value("size", size()).toSize());
360  if (userprefs.value("maximized", isMaximized()).toBool()) {
361  showMaximized();
362  }
363  userprefs.endGroup();
364  show();
365 }
366 
367 void MainWindow::loadSettings()
368 {
369  const QSettings userprefs;
370  const QString env_var = QString::fromLocal8Bit(qgetenv("HOME"));
371 
372  const QString filename = QFileDialog::getOpenFileName(
373  this,
374  tr("Load saved workspace"),
375  userprefs.value("/dirs/setfiles", env_var).toString(),
376  tr("Settings (*.set)"));
377 
378  if (QFile(filename).exists()) {
379  systemMenu->clear();
380  mdiArea->closeAllSubWindows();
381  }
382 }
383 
384 void MainWindow::saveSettings()
385 {
386  QSettings userprefs;
387  userprefs.beginGroup("Workspaces");
388  auto* save_settings_dialog = new QInputDialog(this);
389  save_settings_dialog->setInputMode(QInputDialog::TextInput);
390  save_settings_dialog->setComboBoxEditable(true);
391  save_settings_dialog->setComboBoxItems(userprefs.childGroups());
392  save_settings_dialog->setLabelText("Profile");
393  save_settings_dialog->setOkButtonText("Save");
394  userprefs.endGroup();
395  save_settings_dialog->exec();
396 }
397 
398 void MainWindow::resetSettings()
399 {
400  // systemMenu->clear();
401  // mdiArea->closeAllSubWindows();
402  // Settings::Manager::getInstance()->load("/usr/local/share/rtxi/rtxi.conf");
403 }
404 
405 void MainWindow::utilitiesMenuActivated(QAction* id)
406 {
407  this->loadWidget(QCoreApplication::applicationDirPath() + QDir::separator()
408  + QString("rtxi_modules") + QDir::separator() + id->text());
409 }
410 
411 void MainWindow::loadWidget(const QString& module_name)
412 {
414  event.setParam("pluginName", std::any(module_name.toStdString()));
415  this->event_manager->postEvent(&event);
416 
417  // If something goes wrong just give up
418  auto status = std::any_cast<std::string>(event.getParam("status"));
419  if (status == "failure") {
420  return;
421  }
422 
423  auto create_rtxi_panel_func =
424  std::any_cast<Widgets::Panel* (*)(QMainWindow*, Event::Manager*)>(
425  event.getParam("createRTXIPanel"));
426  auto* rtxi_plugin_pointer =
427  std::any_cast<Widgets::Plugin*>(event.getParam("pluginPointer"));
428  auto* rtxi_panel_pointer = create_rtxi_panel_func(this, this->event_manager);
429  rtxi_plugin_pointer->attachPanel(rtxi_panel_pointer);
430  // finally plugins can also receive events so make sure to register them
431  this->event_manager->registerHandler(rtxi_plugin_pointer);
432 
433  // show the panel please
434  rtxi_panel_pointer->show();
435 }
436 
437 void MainWindow::systemMenuActivated(QAction* id)
438 {
439  this->loadWidget(id->text());
440 }
441 
442 void MainWindow::windowsMenuAboutToShow()
443 {
444  // Clear previous entries
445  windowsMenu->clear();
446 
447  // Add default options
448  windowsMenu->addAction(tr("Cascade"), mdiArea, SLOT(cascadeSubWindows()));
449  windowsMenu->addAction(tr("Tile"), mdiArea, SLOT(tileSubWindows()));
450  windowsMenu->addSeparator();
451 
452  // Get list of open subwindows in Mdi Area
453  subWindows = mdiArea->subWindowList();
454 
455  // Make sure it isn't empty
456  if (subWindows.isEmpty()) {
457  return;
458  }
459  // Create windows list based off of what's open
460  for (auto* subwin : subWindows) {
461  // auto* item = new QAction(subwin->widget()->windowTitle(), this);
462  windowsMenu->addAction(new QAction(subwin->widget()->windowTitle(), this));
463  }
464  connect(windowsMenu,
465  SIGNAL(triggered(QAction*)),
466  this,
467  SLOT(windowsMenuActivated(QAction*)));
468 }
469 
470 void MainWindow::windowsMenuActivated(QAction* id)
471 {
472  // Get list of open subwindows in Mdi Area
473  subWindows = mdiArea->subWindowList();
474 
475  // Make sure it isn't empty
476  if (subWindows.isEmpty()) {
477  return;
478  }
479  for (QMdiSubWindow* subwindow : this->subWindows) {
480  if (subwindow->widget()->windowTitle() == id->text()) {
481  mdiArea->setActiveSubWindow(subwindow);
482  }
483  }
484 }
485 
486 void MainWindow::modulesMenuActivated(QAction* /*unused*/)
487 {
488  const QString filename = QFileDialog::getOpenFileName(
489  this,
490  tr("Load Plugin"),
491  QCoreApplication::applicationDirPath() + QDir::separator()
492  + QString("rtxi_modules"),
493  tr("Plugins (*.so);;All (*.*)"));
494  if (!filename.isNull()) {
495  this->loadWidget(filename);
496  }
497 }
498 
499 void MainWindow::fileMenuActivated(QAction* id)
500 {
501  // Annoying but the best way to do it is to tie an action to the entire menu
502  // so we have to tell it to ignore the first three items
503  if (id->text().contains("Load Workspace")
504  || id->text().contains("Save Workspace")
505  || id->text().contains("Reset Workspace") || id->text().contains("Quit"))
506  {
507  return;
508  }
509 
510  // Have to trim the first three characters before loading
511  // or else parser will include qstring formatting
512  systemMenu->clear();
513  mdiArea->closeAllSubWindows();
514  // Settings::Manager::getInstance()->load(id->text().remove(0,
515  // 3).toStdString());
516 }
517 
518 void MainWindow::closeEvent(QCloseEvent* /*event*/)
519 {
520  /*
521  * Save MainWindow settings
522  */
523  QSettings userprefs;
524  userprefs.beginGroup("MainWindow");
525  userprefs.setValue("geometry", saveGeometry());
526  userprefs.setValue("maximized", isMaximized());
527  if (!isMaximized()) {
528  userprefs.setValue("pos", pos());
529  userprefs.setValue("size", size());
530  }
531  userprefs.endGroup();
532 }
void registerHandler(Handler *handler)
Definition: event.cpp:336
void postEvent(Object *event)
Definition: event.cpp:299
static void changeWidgetMenuItem(QAction *action, const QString &text)
QAction * insertWidgetMenuSeparator()
Definition: main_window.cpp:89
void removeWidgetMenuItem(QAction *action)
QAction * createFileMenuItem(const QString &label)
Definition: main_window.cpp:94
MainWindow(Event::Manager *ev_manager)
Definition: main_window.cpp:50
static void setWidgetMenuItemParameter(QAction *action, int parameter)
void clearFileMenu()
Definition: main_window.cpp:99
QAction * createUtilMenuItem(const QString &label, const QObject *handler, const char *slot)
void clearWidgetMenu()
void loadWindow()
QAction * createWidgetMenuItem(const QString &text)
constexpr std::string_view MODULE_NAME
Definition: connector.hpp:35
constexpr std::string_view MODULE_NAME
@ PLUGIN_INSERT_EVENT
Definition: event.hpp:77
constexpr std::string_view MODULE_NAME
constexpr std::string_view MODULE_NAME
constexpr std::string_view MODULE_NAME
Definition: rtxi_wizard.hpp:35
constexpr std::string_view MODULE_NAME
constexpr std::string_view MODULE_NAME
Definition: userprefs.hpp:31