mainwindow.cpp
Go to the documentation of this file.
1 /*
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5  */
6 
7 #include <functional>
8 #include <stdio.h>
9 #include <numeric>
10 
11 #include <QApplication>
12 #include <QActionGroup>
13 #include <QCheckBox>
14 #include <QCommandLineParser>
15 #include <QDebug>
16 #include <QDesktopServices>
17 #include <QDomDocument>
18 #include <QDoubleSpinBox>
19 #include <QElapsedTimer>
20 #include <QFileDialog>
21 #include <QInputDialog>
22 #include <QMenu>
23 #include <QGroupBox>
24 #include <QMessageBox>
25 #include <QMimeData>
26 #include <QMouseEvent>
27 #include <QPluginLoader>
28 #include <QPushButton>
29 #include <QKeySequence>
30 #include <QScrollBar>
31 #include <QSettings>
32 #include <QStringListModel>
33 #include <QStringRef>
34 #include <QThread>
35 #include <QTextStream>
36 #include <QWindow>
37 #include <QHeaderView>
38 #include <QStandardPaths>
39 #include <QXmlStreamReader>
40 
41 #include "mainwindow.h"
42 #include "curvelist_panel.h"
43 #include "tabbedplotwidget.h"
44 #include "PlotJuggler/plotdata.h"
45 #include "qwt_plot_canvas.h"
48 #include "utils.h"
49 #include "stylesheet.h"
50 #include "dummy_data.h"
51 #include "PlotJuggler/svg_util.h"
53 #include "multifile_prefix.h"
54 
55 #include "ui_aboutdialog.h"
56 #include "ui_support_dialog.h"
57 #include "preferences_dialog.h"
58 #include "nlohmann_parsers.h"
60 #include "colormap_editor.h"
61 
62 #ifdef COMPILED_WITH_CATKIN
63 
64 #endif
65 #ifdef COMPILED_WITH_AMENT
66 #include <ament_index_cpp/get_package_prefix.hpp>
67 #include <ament_index_cpp/get_package_share_directory.hpp>
68 #endif
69 
70 MainWindow::MainWindow(const QCommandLineParser& commandline_parser, QWidget* parent)
71  : QMainWindow(parent)
72  , ui(new Ui::MainWindow)
73  , _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this)
74  , _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this)
75  , _fullscreen_shortcut(Qt::Key_F10, this)
76  , _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this)
77  , _playback_shotcut(Qt::Key_Space, this)
78  , _minimized(false)
79  , _active_streamer_plugin(nullptr)
80  , _disable_undo_logging(false)
81  , _tracker_time(0)
82  , _tracker_param(CurveTracker::VALUE)
83  , _labels_status(LabelStatus::RIGHT)
84  , _recent_data_files(new QMenu())
85  , _recent_layout_files(new QMenu())
86 {
87  QLocale::setDefault(QLocale::c()); // set as default
88  setAcceptDrops(true);
89 
90  _test_option = commandline_parser.isSet("test");
91  _autostart_publishers = commandline_parser.isSet("publish");
92 
93  if (commandline_parser.isSet("enabled_plugins"))
94  {
96  commandline_parser.value("enabled_plugins").split(";", QString::SkipEmptyParts);
97  // Treat the command-line parameter '--enabled_plugins *' to mean all plugings are
98  // enabled
99  if ((_enabled_plugins.size() == 1) && (_enabled_plugins.contains("*")))
100  {
101  _enabled_plugins.clear();
102  }
103  }
104  if (commandline_parser.isSet("disabled_plugins"))
105  {
107  commandline_parser.value("disabled_plugins").split(";", QString::SkipEmptyParts);
108  }
109 
111 
112  ui->setupUi(this);
113 
114  // setupUi() sets the windowTitle so the skin-based setting must be done after
115  _skin_path = "://resources/skin";
116  if (commandline_parser.isSet("skin_path"))
117  {
118  QDir path(commandline_parser.value("skin_path"));
119  if (path.exists())
120  {
121  _skin_path = path.absolutePath();
122  }
123  }
124  if (commandline_parser.isSet("window_title"))
125  {
126  setWindowTitle(commandline_parser.value("window_title"));
127  }
128  else
129  {
130  QFile fileTitle(_skin_path + "/mainwindow_title.txt");
131  if (fileTitle.open(QIODevice::ReadOnly))
132  {
133  QString title = fileTitle.readAll().trimmed();
134  setWindowTitle(title);
135  }
136  }
137 
138  QSettings settings;
139 
140  ui->playbackLoop->setText("");
141  ui->pushButtonZoomOut->setText("");
142  ui->pushButtonPlay->setText("");
143  ui->pushButtonUseDateTime->setText("");
144  ui->pushButtonActivateGrid->setText("");
145  ui->pushButtonRatio->setText("");
146  ui->pushButtonLink->setText("");
147  ui->pushButtonTimeTracker->setText("");
148  ui->pushButtonLoadDatafile->setText("");
149  ui->pushButtonRemoveTimeOffset->setText("");
150  ui->pushButtonLegend->setText("");
151 
152  if (commandline_parser.isSet("buffer_size"))
153  {
154  int buffer_size = std::max(10, commandline_parser.value("buffer_size").toInt());
155  ui->streamingSpinBox->setMaximum(buffer_size);
156  }
157 
158  _animated_streaming_movie = new QMovie(":/resources/animated_radio.gif");
159  _animated_streaming_movie->setScaledSize(ui->labelStreamingAnimation->size());
160  _animated_streaming_movie->jumpToFrame(0);
161 
162  _animated_streaming_timer = new QTimer();
163  _animated_streaming_timer->setSingleShot(true);
164 
165  connect(_animated_streaming_timer, &QTimer::timeout, this, [this]() {
167  _animated_streaming_movie->jumpToFrame(0);
168  });
169 
173  });
174 
175  ui->labelStreamingAnimation->setMovie(_animated_streaming_movie);
176  ui->labelStreamingAnimation->setHidden(true);
177 
179 
182 
185 
188 
191 
194 
197 
198  connect(ui->timeSlider, &RealSlider::realValueChanged, this,
200 
201  connect(ui->playbackRate, &QDoubleSpinBox::editingFinished, this,
202  [this]() { ui->playbackRate->clearFocus(); });
203 
204  connect(ui->playbackStep, &QDoubleSpinBox::editingFinished, this,
205  [this]() { ui->playbackStep->clearFocus(); });
206 
207  connect(_curvelist_widget, &CurveListPanel::requestDeleteAll, this, [this](int option) {
208  if (option == 1)
209  {
210  deleteAllData();
211  }
212  else if (option == 2)
213  {
215  }
216  });
217 
219  new TabbedPlotWidget("Main Window", this, _mapped_plot_data, this);
220 
221  connect(this, &MainWindow::stylesheetChanged, _main_tabbed_widget,
223 
224  ui->plottingLayout->insertWidget(0, _main_tabbed_widget, 1);
225  ui->leftLayout->addWidget(_curvelist_widget, 1);
226 
227  ui->mainSplitter->setCollapsible(0, true);
228  ui->mainSplitter->setStretchFactor(0, 2);
229  ui->mainSplitter->setStretchFactor(1, 6);
230 
231  ui->layoutTimescale->removeWidget(ui->widgetButtons);
232  _main_tabbed_widget->tabWidget()->setCornerWidget(ui->widgetButtons);
233 
234  connect(ui->mainSplitter, SIGNAL(splitterMoved(int, int)),
235  SLOT(on_splitterMoved(int, int)));
236 
238 
240 
241  //------------ Load plugins -------------
242  auto plugin_extra_folders =
243  commandline_parser.value("plugin_folders").split(";", QString::SkipEmptyParts);
244 
245  _default_streamer = commandline_parser.value("start_streamer");
246 
247  loadAllPlugins(plugin_extra_folders);
248 
249  //------------------------------------
250 
251  _undo_timer.start();
252 
253  // save initial state
255 
256  _replot_timer = new QTimer(this);
257  connect(_replot_timer, &QTimer::timeout, this,
258  [this]() { updateDataAndReplot(false); });
259 
260  _publish_timer = new QTimer(this);
261  _publish_timer->setInterval(20);
262  connect(_publish_timer, &QTimer::timeout, this, &MainWindow::onPlaybackLoop);
263 
264  ui->menuFile->setToolTipsVisible(true);
265 
266  this->setMenuBar(ui->menuBar);
267  ui->menuBar->setNativeMenuBar(false);
268 
269  if (_test_option)
270  {
271  buildDummyData();
272  }
273 
274  bool file_loaded = false;
275  if (commandline_parser.isSet("datafile"))
276  {
277  QStringList datafiles = commandline_parser.values("datafile");
278  file_loaded = loadDataFromFiles(datafiles);
279  }
280  if (commandline_parser.isSet("layout"))
281  {
282  loadLayoutFromFile(commandline_parser.value("layout"));
283  }
284 
285  restoreGeometry(settings.value("MainWindow.geometry").toByteArray());
286  restoreState(settings.value("MainWindow.state").toByteArray());
287 
288  // qDebug() << "restoreGeometry";
289 
290  bool activate_grid = settings.value("MainWindow.activateGrid", false).toBool();
291  ui->pushButtonActivateGrid->setChecked(activate_grid);
292 
293  bool zoom_link_active = settings.value("MainWindow.buttonLink", true).toBool();
294  ui->pushButtonLink->setChecked(zoom_link_active);
295 
296  bool ration_active = settings.value("MainWindow.buttonRatio", true).toBool();
297  ui->pushButtonRatio->setChecked(ration_active);
298 
299  int streaming_buffer_value =
300  settings.value("MainWindow.streamingBufferValue", 5).toInt();
301  ui->streamingSpinBox->setValue(streaming_buffer_value);
302 
303  bool datetime_display = settings.value("MainWindow.dateTimeDisplay", false).toBool();
304  ui->pushButtonUseDateTime->setChecked(datetime_display);
305 
306  bool remove_time_offset = settings.value("MainWindow.removeTimeOffset", true).toBool();
307  ui->pushButtonRemoveTimeOffset->setChecked(remove_time_offset);
308 
309  // ui->widgetOptions->setVisible(ui->pushButtonOptions->isChecked());
310 
311  if (settings.value("MainWindow.hiddenFileFrame", false).toBool())
312  {
313  ui->buttonHideFileFrame->setText("+");
314  ui->frameFile->setHidden(true);
315  }
316  if (settings.value("MainWindow.hiddenStreamingFrame", false).toBool())
317  {
318  ui->buttonHideStreamingFrame->setText("+");
319  ui->frameStreaming->setHidden(true);
320  }
321  if (settings.value("MainWindow.hiddenPublishersFrame", false).toBool())
322  {
323  ui->buttonHidePublishersFrame->setText("+");
324  ui->framePublishers->setHidden(true);
325  }
326 
327  //----------------------------------------------------------
328  QIcon trackerIconA, trackerIconB, trackerIconC;
329 
330  trackerIconA.addFile(QStringLiteral(":/style_light/line_tracker.png"), QSize(36, 36));
331  trackerIconB.addFile(QStringLiteral(":/style_light/line_tracker_1.png"), QSize(36, 36));
332  trackerIconC.addFile(QStringLiteral(":/style_light/line_tracker_a.png"), QSize(36, 36));
333 
337 
338  int tracker_setting =
339  settings.value("MainWindow.timeTrackerSetting", (int)CurveTracker::VALUE).toInt();
340  _tracker_param = static_cast<CurveTracker::Parameter>(tracker_setting);
341 
342  ui->pushButtonTimeTracker->setIcon(_tracker_button_icons[_tracker_param]);
343 
344  forEachWidget([&](PlotWidget* plot) { plot->configureTracker(_tracker_param); });
345 
346  auto editor_layout = new QVBoxLayout();
347  editor_layout->setMargin(0);
348  ui->formulaPage->setLayout(editor_layout);
351  editor_layout->addWidget(_function_editor);
352 
354  [this]() { ui->widgetStack->setCurrentIndex(0); });
355 
358 
361 
362  QString theme = settings.value("Preferences::theme", "light").toString();
363  if (theme != "dark")
364  {
365  theme = "light";
366  }
367  loadStyleSheet(tr(":/resources/stylesheet_%1.qss").arg(theme));
368 
369  // builtin messageParsers
370  auto json_parser = std::make_shared<JSON_ParserFactory>();
371  _parser_factories.insert({ json_parser->encoding(), json_parser });
372 
373  auto cbor_parser = std::make_shared<CBOR_ParserFactory>();
374  _parser_factories.insert({ cbor_parser->encoding(), cbor_parser });
375 
376  auto bson_parser = std::make_shared<BSON_ParserFactory>();
377  _parser_factories.insert({ bson_parser->encoding(), bson_parser });
378 
379  auto msgpack = std::make_shared<MessagePack_ParserFactory>();
380  _parser_factories.insert({ msgpack->encoding(), msgpack });
381 
382  if (!_default_streamer.isEmpty())
383  {
384  auto index = ui->comboStreaming->findText(_default_streamer);
385  if (index != -1)
386  {
387  ui->comboStreaming->setCurrentIndex(index);
388  settings.setValue("MainWindow.previousStreamingPlugin", _default_streamer);
389  }
390  }
391 }
392 
394 {
395  // important: avoid problems with plugins
397 
398  delete ui;
399 }
400 
402 {
404  return;
405 
406  int elapsed_ms = _undo_timer.restart();
407 
408  // overwrite the previous
409  if (elapsed_ms < 100)
410  {
411  if (_undo_states.empty() == false)
412  _undo_states.pop_back();
413  }
414 
415  while (_undo_states.size() >= 100)
416  _undo_states.pop_front();
417  _undo_states.push_back(xmlSaveState());
418  _redo_states.clear();
419  // qDebug() << "undo " << _undo_states.size();
420 }
421 
423 {
424  _disable_undo_logging = true;
425  if (_redo_states.size() > 0)
426  {
427  QDomDocument state_document = _redo_states.back();
428  while (_undo_states.size() >= 100)
429  _undo_states.pop_front();
430  _undo_states.push_back(state_document);
431  _redo_states.pop_back();
432 
433  xmlLoadState(state_document);
434  }
435  // qDebug() << "undo " << _undo_states.size();
436  _disable_undo_logging = false;
437 }
438 
440 {
441  _disable_undo_logging = true;
442  if (_undo_states.size() > 1)
443  {
444  QDomDocument state_document = _undo_states.back();
445  while (_redo_states.size() >= 100)
446  _redo_states.pop_front();
447  _redo_states.push_back(state_document);
448  _undo_states.pop_back();
449  state_document = _undo_states.back();
450 
451  xmlLoadState(state_document);
452  }
453  // qDebug() << "undo " << _undo_states.size();
454  _disable_undo_logging = false;
455 }
456 
458 {
460 }
461 
462 void MainWindow::onTrackerMovedFromWidget(QPointF relative_pos)
463 {
464  _tracker_time = relative_pos.x() + _time_offset.get();
465 
466  auto prev = ui->timeSlider->blockSignals(true);
467  ui->timeSlider->setRealValue(_tracker_time);
468  ui->timeSlider->blockSignals(prev);
469 
471 }
472 
474 {
475  _tracker_time = abs_time;
477 }
478 
479 void MainWindow::onTrackerTimeUpdated(double absolute_time, bool do_replot)
480 {
482 
483  for (auto& it : _state_publisher)
484  {
485  it.second->updateState(absolute_time);
486  }
487 
489 
490  forEachWidget([&](PlotWidget* plot) {
492  if (do_replot)
493  {
494  plot->replot();
495  }
496  });
497 }
498 
500 {
501  _undo_shortcut.setContext(Qt::ApplicationShortcut);
502  _redo_shortcut.setContext(Qt::ApplicationShortcut);
503  _fullscreen_shortcut.setContext(Qt::ApplicationShortcut);
504 
505  connect(&_undo_shortcut, &QShortcut::activated, this, &MainWindow::onUndoInvoked);
506  connect(&_redo_shortcut, &QShortcut::activated, this, &MainWindow::onRedoInvoked);
507  connect(&_streaming_shortcut, &QShortcut::activated, this,
509  connect(&_playback_shotcut, &QShortcut::activated, ui->pushButtonPlay,
510  &QPushButton::toggle);
511  connect(&_fullscreen_shortcut, &QShortcut::activated, this,
513 
514  QShortcut* open_menu_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F), this);
515  connect(open_menu_shortcut, &QShortcut::activated,
516  [this]() { ui->menuFile->exec(ui->menuBar->mapToGlobal(QPoint(0, 25))); });
517 
518  QShortcut* open_help_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H), this);
519  connect(open_help_shortcut, &QShortcut::activated,
520  [this]() { ui->menuHelp->exec(ui->menuBar->mapToGlobal(QPoint(230, 25))); });
521 
522  //---------------------------------------------
523 
524  QSettings settings;
526  settings.value("MainWindow.recentlyLoadedDatafile").toStringList());
528  settings.value("MainWindow.recentlyLoadedLayout").toStringList());
529 }
530 
531 void MainWindow::loadAllPlugins(QStringList command_line_plugin_folders)
532 {
533  QSettings settings;
534  QStringList loaded;
535  QStringList plugin_folders;
536  QStringList builtin_folders;
537 
538  plugin_folders += command_line_plugin_folders;
539  plugin_folders +=
540  settings.value("Preferences::plugin_folders", QStringList()).toStringList();
541 
542  builtin_folders += QCoreApplication::applicationDirPath();
543 
544  try
545  {
546 #ifdef COMPILED_WITH_CATKIN
547  builtin_folders += QCoreApplication::applicationDirPath() + "_ros";
548 
549  const char* env = std::getenv("CMAKE_PREFIX_PATH");
550  if (env)
551  {
552  QString env_catkin_paths = QString::fromStdString(env);
553  env_catkin_paths.replace(";", ":"); // for windows
554  auto catkin_paths = env_catkin_paths.split(":");
555 
556  for (const auto& path : catkin_paths)
557  {
558  builtin_folders += path + "/lib/plotjuggler_ros";
559  }
560  }
561 #endif
562 #ifdef COMPILED_WITH_AMENT
563  auto ros2_path = QString::fromStdString(ament_index_cpp::get_package_prefix("plotjugg"
564  "ler_"
565  "ros"));
566  ros2_path += "/lib/plotjuggler_ros";
567  loaded += initializePlugins(ros2_path);
568 #endif
569  }
570  catch (...)
571  {
572  QMessageBox::warning(nullptr, "Missing package [plotjuggler-ros]",
573  "If you just upgraded from PlotJuggler 2.x to 3.x , try "
574  "installing this package:\n\n"
575  "sudo apt install ros-${ROS_DISTRO}-plotjuggler-ros",
576  QMessageBox::Cancel, QMessageBox::Cancel);
577  }
578 
579  builtin_folders +=
580  QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/PlotJuggl"
581  "er";
582  builtin_folders.removeDuplicates();
583 
584  plugin_folders += builtin_folders;
585  plugin_folders.removeDuplicates();
586 
587  for (const auto& folder : plugin_folders)
588  {
589  loaded += initializePlugins(folder);
590  }
591 
592  settings.setValue("Preferences::builtin_plugin_folders", builtin_folders);
593 }
594 
595 QStringList MainWindow::initializePlugins(QString directory_name)
596 {
597  static std::set<QString> loaded_plugins;
598  QStringList loaded_out;
599 
600  qDebug() << "Loading compatible plugins from directory: " << directory_name;
601  int loaded_count = 0;
602 
603  QDir pluginsDir(directory_name);
604 
605  for (const QString& filename : pluginsDir.entryList(QDir::Files))
606  {
607  QFileInfo fileinfo(filename);
608  if (fileinfo.suffix() != "so" && fileinfo.suffix() != "dll" &&
609  fileinfo.suffix() != "dylib")
610  {
611  continue;
612  }
613 
614  if (loaded_plugins.find(filename) != loaded_plugins.end())
615  {
616  continue;
617  }
618 
619  QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename), this);
620 
621  QObject* plugin;
622  try
623  {
624  plugin = pluginLoader.instance();
625  }
626  catch (std::runtime_error& err)
627  {
628  qDebug() << QString("%1: skipping, because it threw the following exception: %2").arg(filename).arg(err.what());
629  continue;
630  }
631  if (plugin && dynamic_cast<PlotJugglerPlugin*>(plugin))
632  {
633  auto class_name = pluginLoader.metaData().value("className").toString();
634  loaded_out.push_back(class_name);
635 
636  DataLoader* loader = qobject_cast<DataLoader*>(plugin);
637  StatePublisher* publisher = qobject_cast<StatePublisher*>(plugin);
638  DataStreamer* streamer = qobject_cast<DataStreamer*>(plugin);
639  ParserFactoryPlugin* message_parser = qobject_cast<ParserFactoryPlugin*>(plugin);
640  ToolboxPlugin* toolbox = qobject_cast<ToolboxPlugin*>(plugin);
641 
642  QString plugin_name;
643  QString plugin_type;
644  bool is_debug_plugin = dynamic_cast<PlotJugglerPlugin*>(plugin)->isDebugPlugin();
645 
646  if (loader)
647  {
648  plugin_name = loader->name();
649  plugin_type = "DataLoader";
650  }
651  else if (publisher)
652  {
653  plugin_name = publisher->name();
654  plugin_type = "StatePublisher";
655  }
656  else if (streamer)
657  {
658  plugin_name = streamer->name();
659  plugin_type = "DataStreamer";
660  }
661  else if (message_parser)
662  {
663  plugin_name = message_parser->name();
664  plugin_type = "MessageParser";
665  }
666  else if (toolbox)
667  {
668  plugin_name = toolbox->name();
669  plugin_type = "Toolbox";
670  }
671 
672  QString message = QString("%1 is a %2 plugin").arg(filename).arg(plugin_type);
673 
674  if ((_enabled_plugins.size() > 0) &&
675  (_enabled_plugins.contains(fileinfo.baseName()) == false))
676  {
677  qDebug() << message << " ...skipping, because it is not explicitly enabled";
678  continue;
679  }
680  if ((_disabled_plugins.size() > 0) &&
681  (_disabled_plugins.contains(fileinfo.baseName()) == true))
682  {
683  qDebug() << message << " ...skipping, because it is explicitly disabled";
684  continue;
685  }
686  if (!_test_option && is_debug_plugin)
687  {
688  qDebug() << message << " ...disabled, unless option -t is used";
689  continue;
690  }
691  if (loaded_plugins.find(plugin_name) != loaded_plugins.end())
692  {
693  qDebug() << message << " ...skipping, because already loaded";
694  continue;
695  }
696 
697  qDebug() << message;
698 
699  loaded_plugins.insert(plugin_name);
700  loaded_count++;
701 
702  if (loader)
703  {
704  _data_loader.insert(std::make_pair(plugin_name, loader));
705  }
706  else if (publisher)
707  {
708  publisher->setDataMap(&_mapped_plot_data);
709  _state_publisher.insert(std::make_pair(plugin_name, publisher));
710 
711  ui->layoutPublishers->setColumnStretch(0, 1.0);
712 
713  int row = _state_publisher.size() - 1;
714  auto label = new QLabel(plugin_name, ui->framePublishers);
715  ui->layoutPublishers->addWidget(label, row, 0);
716 
717  auto start_checkbox = new QCheckBox(ui->framePublishers);
718  ui->layoutPublishers->addWidget(start_checkbox, row, 1);
719  start_checkbox->setFocusPolicy(Qt::FocusPolicy::NoFocus);
720 
721  connect(start_checkbox, &QCheckBox::toggled, this,
722  [=](bool enable) { publisher->setEnabled(enable); });
723 
724  connect(publisher, &StatePublisher::closed, start_checkbox,
725  [=]() { start_checkbox->setChecked(false); });
726 
727  if (publisher->availableActions().empty())
728  {
729  QFrame* empty = new QFrame(ui->framePublishers);
730  empty->setFixedSize({ 22, 22 });
731  ui->layoutPublishers->addWidget(empty, row, 2);
732  }
733  else
734  {
735  auto options_button = new QPushButton(ui->framePublishers);
736  options_button->setFlat(true);
737  options_button->setFixedSize({ 24, 24 });
738  ui->layoutPublishers->addWidget(options_button, row, 2);
739 
740  options_button->setIcon(LoadSvg(":/resources/svg/settings_cog.svg", "light"));
741  options_button->setIconSize({ 16, 16 });
742 
743  auto optionsMenu = [=]() {
744  PopupMenu* menu = new PopupMenu(options_button, this);
745  for (auto action : publisher->availableActions())
746  {
747  menu->addAction(action);
748  }
749  menu->exec();
750  };
751 
752  connect(options_button, &QPushButton::clicked, options_button, optionsMenu);
753 
754  connect(this, &MainWindow::stylesheetChanged, options_button,
755  [=](QString style) {
756  options_button->setIcon(
757  LoadSvg(":/resources/svg/settings_cog.svg", style));
758  });
759  }
760  }
761  else if (message_parser)
762  {
763  _parser_factories.insert(
764  std::make_pair(message_parser->encoding(), message_parser));
765  }
766  else if (streamer)
767  {
768  if (_default_streamer == fileinfo.baseName())
769  {
770  _default_streamer = plugin_name;
771  }
772  _data_streamer.insert(std::make_pair(plugin_name, streamer));
773 
774  connect(streamer, &DataStreamer::closed, this,
775  [this]() { this->stopStreamingPlugin(); });
776 
777  connect(streamer, &DataStreamer::clearBuffers, this,
779 
780  connect(streamer, &DataStreamer::dataReceived, _animated_streaming_movie,
781  [this]() {
782  _animated_streaming_movie->start();
783  _animated_streaming_timer->start(500);
784  });
785 
786  connect(streamer, &DataStreamer::removeGroup, this,
788 
789  connect(streamer, &DataStreamer::dataReceived, this, [this]() {
790  if (isStreamingActive() && !_replot_timer->isActive())
791  {
792  _replot_timer->setSingleShot(true);
793  _replot_timer->start(40);
794  }
795  });
796 
797  connect(streamer, &DataStreamer::notificationsChanged, this,
799  }
800  else if (toolbox)
801  {
802  toolbox->init(_mapped_plot_data, _transform_functions);
803 
804  _toolboxes.insert(std::make_pair(plugin_name, toolbox));
805 
806  auto action = ui->menuTools->addAction(toolbox->name());
807 
808  int new_index = ui->widgetStack->count();
809  auto provided = toolbox->providedWidget();
810  auto widget = provided.first;
811  ui->widgetStack->addWidget(widget);
812 
813  connect(action, &QAction::triggered, toolbox, &ToolboxPlugin::onShowWidget);
814 
815  connect(action, &QAction::triggered, this,
816  [=]() { ui->widgetStack->setCurrentIndex(new_index); });
817 
818  connect(toolbox, &ToolboxPlugin::closed, this,
819  [=]() { ui->widgetStack->setCurrentIndex(0); });
820 
821  connect(toolbox, &ToolboxPlugin::plotCreated, this, [=](std::string name) {
822  _curvelist_widget->addCustom(QString::fromStdString(name));
825  });
826  }
827  }
828  else
829  {
830  if (pluginLoader.errorString().contains("is not an ELF object") == false)
831  {
832  qDebug() << filename << ": " << pluginLoader.errorString();
833  }
834  }
835  }
836 
837  for (auto& [name, streamer] : _data_streamer)
838  {
839  streamer->setParserFactories(&_parser_factories);
840  }
841 
842  for (auto& [name, loader] : _data_loader)
843  {
844  loader->setParserFactories(&_parser_factories);
845  }
846 
847  if (!_data_streamer.empty())
848  {
849  QSignalBlocker block(ui->comboStreaming);
850  ui->comboStreaming->setEnabled(true);
851  ui->buttonStreamingStart->setEnabled(true);
852 
853  for (const auto& it : _data_streamer)
854  {
855  if (ui->comboStreaming->findText(it.first) == -1)
856  {
857  ui->comboStreaming->addItem(it.first);
858  }
859  }
860 
861  // remember the previous one
862  QSettings settings;
863  QString streaming_name =
864  settings
865  .value("MainWindow.previousStreamingPlugin", ui->comboStreaming->itemText(0))
866  .toString();
867 
868  auto streamer_it = _data_streamer.find(streaming_name);
869  if (streamer_it == _data_streamer.end())
870  {
871  streamer_it = _data_streamer.begin();
872  streaming_name = streamer_it->first;
873  }
874 
875  ui->comboStreaming->setCurrentText(streaming_name);
876 
877  bool contains_options = !streamer_it->second->availableActions().empty();
878  ui->buttonStreamingOptions->setEnabled(contains_options);
879  }
880  qDebug() << "Number of plugins loaded: " << loaded_count << "\n";
881  return loaded_out;
882 }
883 
885 {
886  PlotDataMapRef datamap;
887  BuildDummyData(datamap);
888  importPlotDataMap(datamap, true);
889 }
890 
892 {
893  QList<int> sizes = ui->mainSplitter->sizes();
894  int max_left_size = _curvelist_widget->maximumWidth();
895  int totalWidth = sizes[0] + sizes[1];
896 
897  // this is needed only once to restore the old size
898  static bool first = true;
899  if (sizes[0] != 0 && first)
900  {
901  first = false;
902  QSettings settings;
903  int splitter_width = settings.value("MainWindow.splitterWidth", 200).toInt();
904  auto sizes = ui->mainSplitter->sizes();
905  int tot_splitter_width = sizes[0] + sizes[1];
906  sizes[0] = splitter_width;
907  sizes[1] = tot_splitter_width - splitter_width;
908  ui->mainSplitter->setSizes(sizes);
909  return;
910  }
911 
912  if (sizes[0] > max_left_size)
913  {
914  sizes[0] = max_left_size;
915  sizes[1] = totalWidth - max_left_size;
916  ui->mainSplitter->setSizes(sizes);
917  }
918 }
919 
920 void MainWindow::resizeEvent(QResizeEvent*)
921 {
922  on_splitterMoved(0, 0);
923 }
924 
926 {
928 
930 
932 
933  connect(plot, &PlotWidget::curveListChanged, this, [this]() {
936  });
937 
938  connect(&_time_offset, SIGNAL(valueChanged(double)), plot,
939  SLOT(on_changeTimeOffset(double)));
940 
941  connect(ui->pushButtonUseDateTime, &QPushButton::toggled, plot,
943 
946 
947  connect(plot, &PlotWidget::legendSizeChanged, this, [=](int point_size) {
948  auto visitor = [=](PlotWidget* p) {
949  if (plot != p)
950  p->setLegendSize(point_size);
951  };
952  this->forEachWidget(visitor);
953  });
954 
956 
958  plot->on_changeDateTimeScale(ui->pushButtonUseDateTime->isChecked());
959  plot->activateGrid(ui->pushButtonActivateGrid->isChecked());
961  plot->setKeepRatioXY(ui->pushButtonRatio->isChecked());
963 }
964 
965 void MainWindow::onPlotZoomChanged(PlotWidget* modified_plot, QRectF new_range)
966 {
967  if (ui->pushButtonLink->isChecked())
968  {
969  auto visitor = [=](PlotWidget* plot) {
970  if (plot != modified_plot && !plot->isEmpty() && !plot->isXYPlot() &&
971  plot->isZoomLinkEnabled())
972  {
973  QRectF bound_act = plot->currentBoundingRect();
974  bound_act.setLeft(new_range.left());
975  bound_act.setRight(new_range.right());
976  plot->setZoomRectangle(bound_act, false);
977  plot->on_zoomOutVertical_triggered(false);
978  plot->replot();
979  }
980  };
981  this->forEachWidget(visitor);
982  }
983 
985 }
986 
988 {
989  connect(docker, &PlotDocker::plotWidgetAdded, this, &MainWindow::onPlotAdded);
990 
991  connect(this, &MainWindow::stylesheetChanged, docker,
993 
994  // TODO connect(matrix, &PlotMatrix::undoableChange, this,
995  // &MainWindow::onUndoableChange);
996 }
997 
998 QDomDocument MainWindow::xmlSaveState() const
999 {
1000  QDomDocument doc;
1001  QDomProcessingInstruction instr = doc.createProcessingInstruction("xml", "version='1.0'"
1002  " encoding='"
1003  "UTF-8'");
1004 
1005  doc.appendChild(instr);
1006 
1007  QDomElement root = doc.createElement("root");
1008 
1009  for (auto& it : TabbedPlotWidget::instances())
1010  {
1011  QDomElement tabbed_area = it.second->xmlSaveState(doc);
1012  root.appendChild(tabbed_area);
1013  }
1014 
1015  doc.appendChild(root);
1016 
1017  QDomElement relative_time = doc.createElement("use_relative_time_offset");
1018  relative_time.setAttribute("enabled", ui->pushButtonRemoveTimeOffset->isChecked());
1019  root.appendChild(relative_time);
1020 
1021  return doc;
1022 }
1023 
1024 void MainWindow::checkAllCurvesFromLayout(const QDomElement& root)
1025 {
1026  std::set<std::string> curves;
1027 
1028  for (QDomElement tw = root.firstChildElement("tabbed_widget"); !tw.isNull();
1029  tw = tw.nextSiblingElement("tabbed_widget"))
1030  {
1031  for (QDomElement pm = tw.firstChildElement("plotmatrix"); !pm.isNull();
1032  pm = pm.nextSiblingElement("plotmatrix"))
1033  {
1034  for (QDomElement pl = pm.firstChildElement("plot"); !pl.isNull();
1035  pl = pl.nextSiblingElement("plot"))
1036  {
1037  QDomElement tran_elem = pl.firstChildElement("transform");
1038  std::string trans = tran_elem.attribute("value").toStdString();
1039  bool is_XY_plot = (trans == "XYPlot");
1040 
1041  for (QDomElement cv = pl.firstChildElement("curve"); !cv.isNull();
1042  cv = cv.nextSiblingElement("curve"))
1043  {
1044  if (is_XY_plot)
1045  {
1046  curves.insert(cv.attribute("curve_x").toStdString());
1047  curves.insert(cv.attribute("curve_y").toStdString());
1048  }
1049  else
1050  {
1051  curves.insert(cv.attribute("name").toStdString());
1052  }
1053  }
1054  }
1055  }
1056  }
1057 
1058  std::vector<std::string> missing_curves;
1059 
1060  for (auto& curve_name : curves)
1061  {
1062  if (_mapped_plot_data.numeric.count(curve_name) == 0)
1063  {
1064  missing_curves.push_back(curve_name);
1065  }
1066  if (_mapped_plot_data.strings.count(curve_name) == 0)
1067  {
1068  missing_curves.push_back(curve_name);
1069  }
1070  }
1071  if (missing_curves.size() > 0)
1072  {
1073  QMessageBox msgBox(this);
1074  msgBox.setWindowTitle("Warning");
1075  msgBox.setText(tr("One or more timeseries in the layout haven't been loaded yet\n"
1076  "What do you want to do?"));
1077 
1078  QPushButton* buttonRemove =
1079  msgBox.addButton(tr("Remove curves from plots"), QMessageBox::RejectRole);
1080  QPushButton* buttonPlaceholder =
1081  msgBox.addButton(tr("Create empty placeholders"), QMessageBox::YesRole);
1082  msgBox.setDefaultButton(buttonPlaceholder);
1083  msgBox.exec();
1084  if (msgBox.clickedButton() == buttonPlaceholder)
1085  {
1086  for (auto& name : missing_curves)
1087  {
1088  auto plot_it = _mapped_plot_data.addNumeric(name);
1090  }
1092  }
1093  }
1094 }
1095 
1096 bool MainWindow::xmlLoadState(QDomDocument state_document)
1097 {
1098  QDomElement root = state_document.namedItem("root").toElement();
1099  if (root.isNull())
1100  {
1101  qWarning() << "No <root> element found at the top-level of the XML file!";
1102  return false;
1103  }
1104 
1105  size_t num_floating = 0;
1106  std::map<QString, QDomElement> tabbed_widgets_with_name;
1107 
1108  for (QDomElement tw = root.firstChildElement("tabbed_widget"); tw.isNull() == false;
1109  tw = tw.nextSiblingElement("tabbed_widget"))
1110  {
1111  if (tw.attribute("parent") != ("main_window"))
1112  {
1113  num_floating++;
1114  }
1115  tabbed_widgets_with_name[tw.attribute("name")] = tw;
1116  }
1117 
1118  // add if missing
1119  for (const auto& it : tabbed_widgets_with_name)
1120  {
1121  if (TabbedPlotWidget::instance(it.first) == nullptr)
1122  {
1123  // TODO createTabbedDialog(it.first, nullptr);
1124  }
1125  }
1126 
1127  // remove those which don't share list of names
1128  for (const auto& it : TabbedPlotWidget::instances())
1129  {
1130  if (tabbed_widgets_with_name.count(it.first) == 0)
1131  {
1132  it.second->deleteLater();
1133  }
1134  }
1135 
1136  //-----------------------------------------------------
1138  //-----------------------------------------------------
1139 
1140  for (QDomElement tw = root.firstChildElement("tabbed_widget"); tw.isNull() == false;
1141  tw = tw.nextSiblingElement("tabbed_widget"))
1142  {
1143  TabbedPlotWidget* tabwidget = TabbedPlotWidget::instance(tw.attribute("name"));
1144  tabwidget->xmlLoadState(tw);
1145  }
1146 
1147  QDomElement relative_time = root.firstChildElement("use_relative_time_offset");
1148  if (!relative_time.isNull())
1149  {
1150  bool remove_offset = (relative_time.attribute("enabled") == QString("1"));
1151  ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
1152  }
1153  return true;
1154 }
1155 
1156 void MainWindow::onDeleteMultipleCurves(const std::vector<std::string>& curve_names)
1157 {
1158  std::set<std::string> to_be_deleted;
1159  for (auto& name : curve_names)
1160  {
1161  to_be_deleted.insert(name);
1162  }
1163  // add to the list of curves to delete the derived transforms
1164  size_t prev_size = 0;
1165  while (prev_size < to_be_deleted.size())
1166  {
1167  prev_size = to_be_deleted.size();
1168  for (auto& [trans_name, transform] : _transform_functions)
1169  {
1170  for (const auto& source : transform->dataSources())
1171  {
1172  if (to_be_deleted.count(source->plotName()) > 0)
1173  {
1174  to_be_deleted.insert(trans_name);
1175  }
1176  }
1177  }
1178  }
1179 
1180  for (const auto& curve_name : to_be_deleted)
1181  {
1182  emit dataSourceRemoved(curve_name);
1183  _curvelist_widget->removeCurve(curve_name);
1184  _mapped_plot_data.erase(curve_name);
1185  _transform_functions.erase(curve_name);
1186  }
1187  updateTimeOffset();
1188  forEachWidget([](PlotWidget* plot) { plot->replot(); });
1189 }
1190 
1191 void MainWindow::updateRecentDataMenu(QStringList new_filenames)
1192 {
1193  QMenu* menu = _recent_data_files;
1194 
1195  QAction* separator = nullptr;
1196  QStringList prev_filenames;
1197  for (QAction* action : menu->actions())
1198  {
1199  if (action->isSeparator())
1200  {
1201  separator = action;
1202  break;
1203  }
1204  if (new_filenames.contains(action->text()) == false)
1205  {
1206  prev_filenames.push_back(action->text());
1207  }
1208  menu->removeAction(action);
1209  }
1210 
1211  new_filenames.append(prev_filenames);
1212  while (new_filenames.size() > 10)
1213  {
1214  new_filenames.removeLast();
1215  }
1216 
1217  for (const auto& filename : new_filenames)
1218  {
1219  QAction* action = new QAction(filename, nullptr);
1220  connect(action, &QAction::triggered, this,
1221  [this, filename] { loadDataFromFiles({ filename }); });
1222  menu->insertAction(separator, action);
1223  }
1224 
1225  QSettings settings;
1226  settings.setValue("MainWindow.recentlyLoadedDatafile", new_filenames);
1227  menu->setEnabled(new_filenames.size() > 0);
1228 }
1229 
1230 void MainWindow::updateRecentLayoutMenu(QStringList new_filenames)
1231 {
1232  QMenu* menu = _recent_layout_files;
1233 
1234  QAction* separator = nullptr;
1235  QStringList prev_filenames;
1236  for (QAction* action : menu->actions())
1237  {
1238  if (action->isSeparator())
1239  {
1240  separator = action;
1241  break;
1242  }
1243  if (new_filenames.contains(action->text()) == false)
1244  {
1245  prev_filenames.push_back(action->text());
1246  }
1247  menu->removeAction(action);
1248  }
1249 
1250  new_filenames.append(prev_filenames);
1251  while (new_filenames.size() > 10)
1252  {
1253  new_filenames.removeLast();
1254  }
1255 
1256  for (const auto& filename : new_filenames)
1257  {
1258  QAction* action = new QAction(filename, nullptr);
1259  connect(action, &QAction::triggered, this, [this, filename] {
1260  if (this->loadLayoutFromFile(filename))
1261  {
1262  updateRecentLayoutMenu({ filename });
1263  }
1264  });
1265  menu->insertAction(separator, action);
1266  }
1267 
1268  QSettings settings;
1269  settings.setValue("MainWindow.recentlyLoadedLayout", new_filenames);
1270  menu->setEnabled(new_filenames.size() > 0);
1271 }
1272 
1274 {
1275  forEachWidget([](PlotWidget* plot) { plot->removeAllCurves(); });
1276 
1278  _transform_functions.clear();
1280  _loaded_datafiles.clear();
1281  _undo_states.clear();
1282  _redo_states.clear();
1283 
1284  bool stopped = false;
1285 
1286  for (int idx = 0; idx < ui->layoutPublishers->count(); idx++)
1287  {
1288  QLayoutItem* item = ui->layoutPublishers->itemAt(idx);
1289  if (dynamic_cast<QWidgetItem*>(item))
1290  {
1291  if (auto checkbox = dynamic_cast<QCheckBox*>(item->widget()))
1292  {
1293  if (checkbox->isChecked())
1294  {
1295  checkbox->setChecked(false);
1296  stopped = true;
1297  }
1298  }
1299  }
1300  }
1301 
1302  if (stopped)
1303  {
1304  QMessageBox::warning(this, "State publishers stopped",
1305  "All the state publishers have been stopped because old data "
1306  "has been deleted.");
1307  }
1308 }
1309 
1310 void MainWindow::importPlotDataMap(PlotDataMapRef& new_data, bool remove_old)
1311 {
1312  if (remove_old)
1313  {
1314  auto ClearOldSeries = [](auto& prev_plot_data, auto& new_plot_data) {
1315  for (auto& it : prev_plot_data)
1316  {
1317  // timeseries in both
1318  if (new_plot_data.count(it.first) != 0)
1319  {
1320  it.second.clear();
1321  }
1322  }
1323  };
1324 
1325  ClearOldSeries(_mapped_plot_data.scatter_xy, new_data.scatter_xy);
1326  ClearOldSeries(_mapped_plot_data.numeric, new_data.numeric);
1327  ClearOldSeries(_mapped_plot_data.strings, new_data.strings);
1328  }
1329 
1330  auto [added_curves, curve_updated, data_pushed] =
1331  MoveData(new_data, _mapped_plot_data, remove_old);
1332 
1333  for (const auto& added_curve : added_curves)
1334  {
1335  _curvelist_widget->addCurve(added_curve);
1336  }
1337 
1338  if (curve_updated)
1339  {
1341  }
1342 }
1343 
1345 {
1346  return !ui->buttonStreamingPause->isChecked() && _active_streamer_plugin;
1347 }
1348 
1349 bool MainWindow::loadDataFromFiles(QStringList filenames)
1350 {
1351  filenames.sort();
1352  std::map<QString, QString> filename_prefix;
1353 
1354  if (filenames.size() > 1)
1355  {
1356  DialogMultifilePrefix dialog(filenames, this);
1357  int ret = dialog.exec();
1358  if (ret != QDialog::Accepted)
1359  {
1360  return false;
1361  }
1362  filename_prefix = dialog.getPrefixes();
1363  }
1364 
1365  std::unordered_set<std::string> previous_names = _mapped_plot_data.getAllNames();
1366 
1367  QStringList loaded_filenames;
1368 
1369  for (int i = 0; i < filenames.size(); i++)
1370  {
1371  FileLoadInfo info;
1372  info.filename = filenames[i];
1373  if (filename_prefix.count(info.filename) > 0)
1374  {
1375  info.prefix = filename_prefix[info.filename];
1376  }
1377  auto added_names = loadDataFromFile(info);
1378  if (!added_names.empty())
1379  {
1380  loaded_filenames.push_back(filenames[i]);
1381  }
1382  for (const auto& name : added_names)
1383  {
1384  previous_names.erase(name);
1385  }
1386  }
1387 
1388  bool data_replaced_entirely = false;
1389 
1390  if (previous_names.empty())
1391  {
1392  data_replaced_entirely = true;
1393  }
1394  else
1395  {
1396  QMessageBox::StandardButton reply;
1397  reply = QMessageBox::question(
1398  this, tr("Warning"), tr("Do you want to remove the previously loaded data?\n"),
1399  QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton);
1400 
1401  if (reply == QMessageBox::Yes)
1402  {
1403  std::vector<std::string> to_delete;
1404  for (const auto& name : previous_names)
1405  {
1406  to_delete.push_back(name);
1407  }
1408  onDeleteMultipleCurves(to_delete);
1409  data_replaced_entirely = true;
1410  }
1411  }
1412 
1413  // special case when only the last file should be remembered
1414  if (loaded_filenames.size() == 1 && data_replaced_entirely &&
1415  _loaded_datafiles.size() > 1)
1416  {
1418  _loaded_datafiles.resize(1);
1419  }
1420 
1421  if (loaded_filenames.size() > 0)
1422  {
1423  updateRecentDataMenu(loaded_filenames);
1424  linkedZoomOut();
1425  return true;
1426  }
1427  return false;
1428 }
1429 
1430 std::unordered_set<std::string> MainWindow::loadDataFromFile(const FileLoadInfo& info)
1431 {
1432  ui->pushButtonPlay->setChecked(false);
1433 
1434  const QString extension = QFileInfo(info.filename).suffix().toLower();
1435 
1436  typedef std::map<QString, DataLoaderPtr>::iterator MapIterator;
1437 
1438  std::vector<MapIterator> compatible_loaders;
1439 
1440  for (auto it = _data_loader.begin(); it != _data_loader.end(); ++it)
1441  {
1442  DataLoaderPtr data_loader = it->second;
1443  std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
1444 
1445  for (auto& ext : extensions)
1446  {
1447  if (extension == QString(ext).toLower())
1448  {
1449  compatible_loaders.push_back(it);
1450  break;
1451  }
1452  }
1453  }
1454 
1455  DataLoaderPtr dataloader;
1456  std::unordered_set<std::string> added_names;
1457 
1458  if (compatible_loaders.size() == 1)
1459  {
1460  dataloader = compatible_loaders.front()->second;
1461  }
1462  else
1463  {
1464  static QString last_plugin_name_used;
1465 
1466  QStringList names;
1467  for (auto& cl : compatible_loaders)
1468  {
1469  const auto& name = cl->first;
1470 
1471  if (name == last_plugin_name_used)
1472  {
1473  names.push_front(name);
1474  }
1475  else
1476  {
1477  names.push_back(name);
1478  }
1479  }
1480 
1481  bool ok;
1482  QString plugin_name =
1483  QInputDialog::getItem(this, tr("QInputDialog::getItem()"),
1484  tr("Select the loader to use:"), names, 0, false, &ok);
1485  if (ok && !plugin_name.isEmpty() && (_enabled_plugins.size() == 0 || _enabled_plugins.contains(plugin_name)))
1486  {
1487  dataloader = _data_loader[plugin_name];
1488  last_plugin_name_used = plugin_name;
1489  }
1490  }
1491 
1492  if (dataloader)
1493  {
1494  QFile file(info.filename);
1495 
1496  if (!file.open(QFile::ReadOnly | QFile::Text))
1497  {
1498  QMessageBox::warning(
1499  this, tr("Datafile"),
1500  tr("Cannot read file %1:\n%2.").arg(info.filename).arg(file.errorString()));
1501  return {};
1502  }
1503  file.close();
1504 
1505  try
1506  {
1507  PlotDataMapRef mapped_data;
1508  FileLoadInfo new_info = info;
1509 
1510  if (dataloader->readDataFromFile(&new_info, mapped_data))
1511  {
1512  AddPrefixToPlotData(info.prefix.toStdString(), mapped_data.numeric);
1513  AddPrefixToPlotData(info.prefix.toStdString(), mapped_data.strings);
1514 
1515  added_names = mapped_data.getAllNames();
1516  importPlotDataMap(mapped_data, true);
1517 
1518  QDomElement plugin_elem = dataloader->xmlSaveState(new_info.plugin_config);
1519  new_info.plugin_config.appendChild(plugin_elem);
1520 
1521  bool duplicate = false;
1522 
1523  // substitute an old item of _loaded_datafiles or push_back another item.
1524  for (auto& prev_loaded : _loaded_datafiles)
1525  {
1526  if (prev_loaded.filename == new_info.filename &&
1527  prev_loaded.prefix == new_info.prefix)
1528  {
1529  prev_loaded = new_info;
1530  duplicate = true;
1531  break;
1532  }
1533  }
1534 
1535  if (!duplicate)
1536  {
1537  _loaded_datafiles.push_back(new_info);
1538  }
1539  }
1540  }
1541  catch (std::exception& ex)
1542  {
1543  QMessageBox::warning(this, tr("Exception from the plugin"),
1544  tr("The plugin [%1] thrown the following exception: \n\n %3\n")
1545  .arg(dataloader->name())
1546  .arg(ex.what()));
1547  return {};
1548  }
1549  }
1550  else
1551  {
1552  QMessageBox::warning(this, tr("Error"),
1553  tr("Cannot read files with extension %1.\n No plugin can handle "
1554  "that!\n")
1555  .arg(info.filename));
1556  }
1558 
1559  // clean the custom plot. Function updateDataAndReplot will update them
1560  for (auto& custom_it : _transform_functions)
1561  {
1562  auto it = _mapped_plot_data.numeric.find(custom_it.first);
1563  if (it != _mapped_plot_data.numeric.end())
1564  {
1565  it->second.clear();
1566  }
1567  custom_it.second->reset();
1568  }
1569  forEachWidget([](PlotWidget* plot) { plot->updateCurves(true); });
1570 
1571  updateDataAndReplot(true);
1572  ui->timeSlider->setRealValue(ui->timeSlider->getMinimum());
1573 
1574  return added_names;
1575 }
1576 
1578 {
1579  if (_data_streamer.empty())
1580  {
1581  return;
1582  }
1583  auto streamer = _data_streamer.at(ui->comboStreaming->currentText());
1584  QAction* notification_button_action = streamer->notificationAction().first;
1585  if (notification_button_action != nullptr)
1586  {
1587  notification_button_action->trigger();
1588  }
1589 }
1590 
1592 {
1594  {
1595  paused = true;
1596  }
1597 
1598  ui->pushButtonRemoveTimeOffset->setEnabled(paused);
1599  ui->widgetPlay->setEnabled(paused);
1600 
1601  if (!paused && ui->pushButtonPlay->isChecked())
1602  {
1603  ui->pushButtonPlay->setChecked(false);
1604  }
1605 
1606  forEachWidget([&](PlotWidget* plot) {
1607  plot->enableTracker(paused);
1608  plot->setZoomEnabled(paused);
1609  });
1610 
1611  if (!paused)
1612  {
1613  updateTimeOffset();
1614  }
1615  else
1616  {
1617  onUndoableChange();
1618  }
1619 }
1620 
1622 {
1624  {
1625  bool prev_state = ui->buttonStreamingPause->isChecked();
1626  ui->buttonStreamingPause->setChecked(!prev_state);
1627  }
1628 }
1629 
1631 {
1632  ui->comboStreaming->setEnabled(true);
1633  ui->buttonStreamingStart->setText("Start");
1634  ui->buttonStreamingPause->setEnabled(false);
1635  ui->labelStreamingAnimation->setHidden(true);
1637 
1638  // force the cleanups typically done in on_buttonStreamingPause_toggled
1639  if (ui->buttonStreamingPause->isChecked())
1640  {
1641  // Will call on_buttonStreamingPause_toggled
1642  ui->buttonStreamingPause->setChecked(false);
1643  }
1644  else
1645  {
1646  // call it manually
1648  }
1649 
1651  {
1652  _active_streamer_plugin->shutdown();
1653  _active_streamer_plugin = nullptr;
1654  }
1655 
1656  if (!_mapped_plot_data.numeric.empty())
1657  {
1658  ui->actionDeleteAllData->setToolTip("");
1659  }
1660 
1661  // reset max range.
1662  _mapped_plot_data.setMaximumRangeX(std::numeric_limits<double>::max());
1663 }
1664 
1665 void MainWindow::startStreamingPlugin(QString streamer_name)
1666 {
1668  {
1669  _active_streamer_plugin->shutdown();
1670  _active_streamer_plugin = nullptr;
1671  }
1672 
1673  if (_data_streamer.empty())
1674  {
1675  qDebug() << "Error, no streamer loaded";
1676  return;
1677  }
1678 
1679  auto it = _data_streamer.find(streamer_name);
1680  if (it != _data_streamer.end())
1681  {
1682  _active_streamer_plugin = it->second;
1683  }
1684  else
1685  {
1686  qDebug() << "Error. The streamer " << streamer_name << " can't be loaded";
1687  _active_streamer_plugin = nullptr;
1688  return;
1689  }
1690 
1691  bool started = false;
1692  try
1693  {
1694  // TODO data sources (argument to _active_streamer_plugin->start()
1695  started = _active_streamer_plugin && _active_streamer_plugin->start(nullptr);
1696  }
1697  catch (std::runtime_error& err)
1698  {
1699  QMessageBox::warning(
1700  this, tr("Exception from the plugin"),
1701  tr("The plugin thrown the following exception: \n\n %1\n").arg(err.what()));
1702  _active_streamer_plugin = nullptr;
1703  return;
1704  }
1705 
1706  // The attemp to start the plugin may have succeded or failed
1707  if (started)
1708  {
1709  {
1710  std::lock_guard<std::mutex> lock(_active_streamer_plugin->mutex());
1711  importPlotDataMap(_active_streamer_plugin->dataMap(), false);
1712  }
1713 
1714  ui->actionClearBuffer->setEnabled(true);
1715  ui->actionDeleteAllData->setToolTip("Stop streaming to be able to delete the data");
1716 
1717  ui->buttonStreamingStart->setText("Stop");
1718  ui->buttonStreamingPause->setEnabled(true);
1719  ui->buttonStreamingPause->setChecked(false);
1720  ui->comboStreaming->setEnabled(false);
1721  ui->labelStreamingAnimation->setHidden(false);
1722 
1723  // force start
1725  // this will force the update the max buffer size values
1726  on_streamingSpinBox_valueChanged(ui->streamingSpinBox->value());
1727  }
1728  else
1729  {
1730  QSignalBlocker block(ui->buttonStreamingStart);
1731  ui->buttonStreamingStart->setChecked(false);
1732  qDebug() << "Failed to launch the streamer";
1733  _active_streamer_plugin = nullptr;
1734  }
1735 }
1736 
1738 {
1739  ui->buttonStreamingNotifications->setEnabled(enabled);
1740 
1741  QSettings settings;
1742  QString theme = settings.value("Preferences::theme", "light").toString();
1743 
1744  if (enabled)
1745  {
1746  ui->buttonStreamingNotifications->setIcon(
1747  LoadSvg(":/resources/svg/alarm-bell-active.svg", theme));
1748  }
1749  else
1750  {
1751  ui->buttonStreamingNotifications->setIcon(
1752  LoadSvg(":/resources/svg/alarm-bell.svg", theme));
1753  }
1754 }
1755 
1756 void MainWindow::loadStyleSheet(QString file_path)
1757 {
1758  QFile styleFile(file_path);
1759  styleFile.open(QFile::ReadOnly);
1760  try
1761  {
1762  QString theme = SetApplicationStyleSheet(styleFile.readAll());
1763 
1764  forEachWidget([&](PlotWidget* plot) { plot->replot(); });
1765 
1767  emit stylesheetChanged(theme);
1768  }
1769  catch (std::runtime_error& err)
1770  {
1771  QMessageBox::warning(this, tr("Error loading StyleSheet"), tr(err.what()));
1772  }
1773 }
1774 
1776 {
1777  for (auto& [id, series] : _transform_functions)
1778  {
1779  }
1780 }
1781 
1783 {
1784  std::unordered_set<std::string> updated_curves;
1785 
1786  bool curve_added = false;
1787  for (auto& it : _transform_functions)
1788  {
1789  if (auto reactive_function =
1790  std::dynamic_pointer_cast<PJ::ReactiveLuaFunction>(it.second))
1791  {
1792  reactive_function->setTimeTracker(_tracker_time);
1793  reactive_function->calculate();
1794 
1795  for (auto& name : reactive_function->createdCurves())
1796  {
1797  curve_added |= _curvelist_widget->addCurve(name);
1798  updated_curves.insert(name);
1799  }
1800  }
1801  }
1802  if (curve_added)
1803  {
1805  }
1806 
1807  forEachWidget([&](PlotWidget* plot) {
1808  for (auto& curve : plot->curveList())
1809  {
1810  if (updated_curves.count(curve.src_name) != 0)
1811  {
1812  plot->replot();
1813  }
1814  }
1815  });
1816 }
1817 
1818 void MainWindow::dragEnterEvent(QDragEnterEvent* event)
1819 {
1820  if (event->mimeData()->hasUrls())
1821  {
1822  event->acceptProposedAction();
1823  }
1824 }
1825 
1826 void MainWindow::dropEvent(QDropEvent* event)
1827 {
1828  QStringList file_names;
1829  const auto urls = event->mimeData()->urls();
1830 
1831  for (const auto& url : urls)
1832  {
1833  file_names << QDir::toNativeSeparators(url.toLocalFile());
1834  }
1835 
1836  loadDataFromFiles(file_names);
1837 }
1838 
1840 {
1841  ui->pushButtonLoadDatafile->setIcon(LoadSvg(":/resources/svg/import.svg", theme));
1842  ui->buttonStreamingPause->setIcon(LoadSvg(":/resources/svg/pause.svg", theme));
1843  if (ui->buttonStreamingNotifications->isEnabled())
1844  {
1845  ui->buttonStreamingNotifications->setIcon(
1846  LoadSvg(":/resources/svg/alarm-bell-active.svg", theme));
1847  }
1848  else
1849  {
1850  ui->buttonStreamingNotifications->setIcon(
1851  LoadSvg(":/resources/svg/alarm-bell.svg", theme));
1852  }
1853  ui->buttonRecentData->setIcon(LoadSvg(":/resources/svg/right-arrow.svg", theme));
1854  ui->buttonRecentLayout->setIcon(LoadSvg(":/resources/svg/right-arrow.svg", theme));
1855 
1856  ui->pushButtonZoomOut->setIcon(LoadSvg(":/resources/svg/zoom_max.svg", theme));
1857  ui->playbackLoop->setIcon(LoadSvg(":/resources/svg/loop.svg", theme));
1858  ui->pushButtonPlay->setIcon(LoadSvg(":/resources/svg/play_arrow.svg", theme));
1859  ui->pushButtonUseDateTime->setIcon(LoadSvg(":/resources/svg/datetime.svg", theme));
1860  ui->pushButtonActivateGrid->setIcon(LoadSvg(":/resources/svg/grid.svg", theme));
1861  ui->pushButtonRatio->setIcon(LoadSvg(":/resources/svg/ratio.svg", theme));
1862 
1863  ui->pushButtonLoadLayout->setIcon(LoadSvg(":/resources/svg/import.svg", theme));
1864  ui->pushButtonSaveLayout->setIcon(LoadSvg(":/resources/svg/export.svg", theme));
1865 
1866  ui->pushButtonLink->setIcon(LoadSvg(":/resources/svg/link.svg", theme));
1867  ui->pushButtonRemoveTimeOffset->setIcon(LoadSvg(":/resources/svg/t0.svg", theme));
1868  ui->pushButtonLegend->setIcon(LoadSvg(":/resources/svg/legend.svg", theme));
1869 
1870  ui->buttonStreamingOptions->setIcon(LoadSvg(":/resources/svg/settings_cog.svg", theme));
1871 }
1872 
1873 void MainWindow::loadPluginState(const QDomElement& root)
1874 {
1875  QDomElement plugins = root.firstChildElement("Plugins");
1876 
1877  for (QDomElement plugin_elem = plugins.firstChildElement();
1878  plugin_elem.isNull() == false; plugin_elem = plugin_elem.nextSiblingElement())
1879  {
1880  const QString plugin_name = plugin_elem.attribute("ID");
1881 
1882  if (plugin_elem.nodeName() != "plugin" || plugin_name.isEmpty())
1883  {
1884  QMessageBox::warning(this, tr("Error loading Plugin State from Layout"),
1885  tr("The method xmlSaveState() must return a node like this "
1886  "<plugin ID=\"PluginName\" "));
1887  }
1888 
1889  if (_data_loader.find(plugin_name) != _data_loader.end())
1890  {
1891  _data_loader[plugin_name]->xmlLoadState(plugin_elem);
1892  }
1893  if (_data_streamer.find(plugin_name) != _data_streamer.end())
1894  {
1895  _data_streamer[plugin_name]->xmlLoadState(plugin_elem);
1896  }
1897  if (_toolboxes.find(plugin_name) != _toolboxes.end())
1898  {
1899  _toolboxes[plugin_name]->xmlLoadState(plugin_elem);
1900  }
1901  if (_state_publisher.find(plugin_name) != _state_publisher.end())
1902  {
1903  StatePublisherPtr publisher = _state_publisher[plugin_name];
1904  publisher->xmlLoadState(plugin_elem);
1905 
1906  if (_autostart_publishers && plugin_elem.attribute("status") == "active")
1907  {
1908  publisher->setEnabled(true);
1909  }
1910  }
1911  }
1912 }
1913 
1914 QDomElement MainWindow::savePluginState(QDomDocument& doc)
1915 {
1916  QDomElement list_plugins = doc.createElement("Plugins");
1917 
1918  auto AddPlugins = [&](auto& plugins) {
1919  for (auto& [name, plugin] : plugins)
1920  {
1921  QDomElement elem = plugin->xmlSaveState(doc);
1922  list_plugins.appendChild(elem);
1923  }
1924  };
1925 
1926  AddPlugins(_data_loader);
1927  AddPlugins(_data_streamer);
1928  AddPlugins(_toolboxes);
1929  AddPlugins(_state_publisher);
1930 
1931  for (auto& it : _state_publisher)
1932  {
1933  const auto& state_publisher = it.second;
1934  QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
1935  plugin_elem.setAttribute("status", state_publisher->enabled() ? "active" : "idle");
1936  }
1937 
1938  return list_plugins;
1939 }
1940 
1941 std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
1942 {
1943  // find min max time
1944  double min_time = std::numeric_limits<double>::max();
1945  double max_time = std::numeric_limits<double>::lowest();
1946  int max_steps = 0;
1947 
1948  forEachWidget([&](const PlotWidget* widget) {
1949  for (auto& it : widget->curveList())
1950  {
1951  const auto& curve_name = it.src_name;
1952 
1953  auto plot_it = _mapped_plot_data.numeric.find(curve_name);
1954  if (plot_it == _mapped_plot_data.numeric.end())
1955  {
1956  continue; // FIXME?
1957  }
1958  const auto& data = plot_it->second;
1959  if (data.size() >= 1)
1960  {
1961  const double t0 = data.front().x;
1962  const double t1 = data.back().x;
1963  min_time = std::min(min_time, t0);
1964  max_time = std::max(max_time, t1);
1965  max_steps = std::max(max_steps, (int)data.size());
1966  }
1967  }
1968  });
1969 
1970  // needed if all the plots are empty
1971  if (max_steps == 0 || max_time < min_time)
1972  {
1973  for (const auto& it : _mapped_plot_data.numeric)
1974  {
1975  const PlotData& data = it.second;
1976  if (data.size() >= 1)
1977  {
1978  const double t0 = data.front().x;
1979  const double t1 = data.back().x;
1980  min_time = std::min(min_time, t0);
1981  max_time = std::max(max_time, t1);
1982  max_steps = std::max(max_steps, (int)data.size());
1983  }
1984  }
1985  }
1986 
1987  // last opportunity. Everything else failed
1988  if (max_steps == 0 || max_time < min_time)
1989  {
1990  min_time = 0.0;
1991  max_time = 1.0;
1992  max_steps = 1;
1993  }
1994  return std::tuple<double, double, int>(min_time, max_time, max_steps);
1995 }
1996 
1997 bool MainWindow::loadLayoutFromFile(QString filename)
1998 {
1999  QSettings settings;
2000 
2001  QFile file(filename);
2002  if (!file.open(QFile::ReadOnly | QFile::Text))
2003  {
2004  QMessageBox::warning(
2005  this, tr("Layout"),
2006  tr("Cannot read file %1:\n%2.").arg(filename).arg(file.errorString()));
2007  return false;
2008  }
2009 
2010  QString errorStr;
2011  int errorLine, errorColumn;
2012 
2013  QDomDocument domDocument;
2014 
2015  if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn))
2016  {
2017  QMessageBox::information(
2018  window(), tr("XML Layout"),
2019  tr("Parse error at line %1:\n%2").arg(errorLine).arg(errorStr));
2020  return false;
2021  }
2022 
2023  //-------------------------------------------------
2024  // refresh plugins
2025  QDomElement root = domDocument.namedItem("root").toElement();
2026 
2027  loadPluginState(root);
2028  //-------------------------------------------------
2029  QDomElement previously_loaded_datafile = root.firstChildElement("previouslyLoaded_"
2030  "Datafiles");
2031 
2032  QDomElement datafile_elem = previously_loaded_datafile.firstChildElement("fileInfo");
2033  while (!datafile_elem.isNull())
2034  {
2035  QString datafile_path = datafile_elem.attribute("filename");
2036  if (QDir(datafile_path).isRelative())
2037  {
2038  QDir layout_directory = QFileInfo(filename).absoluteDir();
2039  QString new_path = layout_directory.filePath(datafile_path);
2040  datafile_path = QFileInfo(new_path).absoluteFilePath();
2041  }
2042 
2043  FileLoadInfo info;
2044  info.filename = datafile_path;
2045  info.prefix = datafile_elem.attribute("prefix");
2046 
2047  QDomElement datasources_elem = datafile_elem.firstChildElement("selected_"
2048  "datasources");
2049  QString topics_list = datasources_elem.attribute("value");
2050  info.selected_datasources = topics_list.split(";", QString::SkipEmptyParts);
2051 
2052  auto plugin_elem = datafile_elem.firstChildElement("plugin");
2053  info.plugin_config.appendChild(info.plugin_config.importNode(plugin_elem, true));
2054 
2055  loadDataFromFile(info);
2056  datafile_elem = datafile_elem.nextSiblingElement("fileInfo");
2057  }
2058 
2059  QDomElement previous_streamer = root.firstChildElement("previouslyLoaded_Streamer");
2060  if (!previous_streamer.isNull())
2061  {
2062  QString streamer_name = previous_streamer.attribute("name");
2063 
2064  QMessageBox msgBox(this);
2065  msgBox.setWindowTitle("Start Streaming?");
2066  msgBox.setText(
2067  tr("Start the previously used streaming plugin?\n\n %1 \n\n").arg(streamer_name));
2068  QPushButton* yes = msgBox.addButton(tr("Yes"), QMessageBox::YesRole);
2069  QPushButton* no = msgBox.addButton(tr("No"), QMessageBox::RejectRole);
2070  msgBox.setDefaultButton(yes);
2071  msgBox.exec();
2072 
2073  if (msgBox.clickedButton() == yes)
2074  {
2075  if (_data_streamer.count(streamer_name) != 0)
2076  {
2077  auto allCurves = readAllCurvesFromXML(root);
2078 
2079  // create placeholders, if necessary
2080  for (auto curve_name : allCurves)
2081  {
2082  std::string curve_str = curve_name.toStdString();
2083  if (_mapped_plot_data.numeric.count(curve_str) == 0)
2084  {
2085  _mapped_plot_data.addNumeric(curve_str);
2086  }
2087  }
2088 
2089  startStreamingPlugin(streamer_name);
2090  }
2091  else
2092  {
2093  QMessageBox::warning(
2094  this, tr("Error Loading Streamer"),
2095  tr("The streamer named %1 can not be loaded.").arg(streamer_name));
2096  }
2097  }
2098  }
2099  //-------------------------------------------------
2100  // autostart_publishers
2101  QDomElement plugins = root.firstChildElement("Plugins");
2102 
2103  if (!plugins.isNull() && _autostart_publishers)
2104  {
2105  for (QDomElement plugin_elem = plugins.firstChildElement();
2106  plugin_elem.isNull() == false; plugin_elem = plugin_elem.nextSiblingElement())
2107  {
2108  const QString plugin_name = plugin_elem.nodeName();
2109  if (_state_publisher.find(plugin_name) != _state_publisher.end())
2110  {
2111  StatePublisherPtr publisher = _state_publisher[plugin_name];
2112 
2113  if (plugin_elem.attribute("status") == "active")
2114  {
2115  publisher->setEnabled(true);
2116  }
2117  }
2118  }
2119  }
2120  //-------------------------------------------------
2121  auto custom_equations = root.firstChildElement("customMathEquations");
2122 
2123  if (!custom_equations.isNull())
2124  {
2125  using SnippetPair = std::pair<SnippetData, QDomElement>;
2126  std::vector<SnippetPair> snippets;
2127 
2128  for (QDomElement custom_eq = custom_equations.firstChildElement("snippet");
2129  custom_eq.isNull() == false; custom_eq = custom_eq.nextSiblingElement("snippet"))
2130  {
2131  snippets.push_back({ GetSnippetFromXML(custom_eq), custom_eq });
2132  }
2133  // A custom plot may depend on other custom plots.
2134  // Reorder them to respect the mutual depencency.
2135  auto DependOn = [](const SnippetPair& a, const SnippetPair& b) {
2136  if (b.first.linked_source == a.first.alias_name)
2137  {
2138  return true;
2139  }
2140  for (const auto& source : b.first.additional_sources)
2141  {
2142  if (source == a.first.alias_name)
2143  {
2144  return true;
2145  }
2146  }
2147  return false;
2148  };
2149  std::sort(snippets.begin(), snippets.end(), DependOn);
2150 
2151  for (const auto& [snippet, custom_eq] : snippets)
2152  {
2153  try
2154  {
2155  CustomPlotPtr new_custom_plot = std::make_shared<LuaCustomFunction>(snippet);
2156  new_custom_plot->xmlLoadState(custom_eq);
2157 
2158  new_custom_plot->calculateAndAdd(_mapped_plot_data);
2159  const auto& alias_name = new_custom_plot->aliasName();
2160  _curvelist_widget->addCustom(alias_name);
2161 
2162  _transform_functions.insert({ alias_name.toStdString(), new_custom_plot });
2163  }
2164  catch (std::runtime_error& err)
2165  {
2166  QMessageBox::warning(this, tr("Exception"),
2167  tr("Failed to load customMathEquation [%1] \n\n %2\n")
2168  .arg(snippet.alias_name)
2169  .arg(err.what()));
2170  }
2171  }
2173  }
2174 
2175  auto colormaps = root.firstChildElement("colorMaps");
2176 
2177  if (!colormaps.isNull())
2178  {
2179  for (auto colormap = colormaps.firstChildElement("colorMap");
2180  colormap.isNull() == false; colormap = colormap.nextSiblingElement("colorMap"))
2181  {
2182  QString name = colormap.attribute("name");
2183  ColorMapLibrary()[name]->setScrip(colormap.text());
2184  }
2185  }
2186 
2187  QByteArray snippets_saved_xml =
2188  settings.value("AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
2189 
2190  auto snippets_element = root.firstChildElement("snippets");
2191  if (!snippets_element.isNull())
2192  {
2193  auto snippets_previous = GetSnippetsFromXML(snippets_saved_xml);
2194  auto snippets_layout = GetSnippetsFromXML(snippets_element);
2195 
2196  bool snippets_are_different = false;
2197  for (const auto& snippet_it : snippets_layout)
2198  {
2199  auto prev_it = snippets_previous.find(snippet_it.first);
2200 
2201  if (prev_it == snippets_previous.end() ||
2202  prev_it->second.function != snippet_it.second.function ||
2203  prev_it->second.global_vars != snippet_it.second.global_vars)
2204  {
2205  snippets_are_different = true;
2206  break;
2207  }
2208  }
2209 
2210  if (snippets_are_different)
2211  {
2212  QMessageBox msgBox(this);
2213  msgBox.setWindowTitle("Overwrite custom transforms?");
2214  msgBox.setText("Your layout file contains a set of custom transforms different "
2215  "from "
2216  "the last one you used.\nWant to load these transformations?");
2217  msgBox.addButton(QMessageBox::No);
2218  msgBox.addButton(QMessageBox::Yes);
2219  msgBox.setDefaultButton(QMessageBox::Yes);
2220 
2221  if (msgBox.exec() == QMessageBox::Yes)
2222  {
2223  for (const auto& snippet_it : snippets_layout)
2224  {
2225  snippets_previous[snippet_it.first] = snippet_it.second;
2226  }
2227  QDomDocument doc;
2228  auto snippets_root_element = ExportSnippets(snippets_previous, doc);
2229  doc.appendChild(snippets_root_element);
2230  settings.setValue("AddCustomPlotDialog.savedXML", doc.toByteArray(2));
2231  }
2232  }
2233  }
2234 
2236 
2237  xmlLoadState(domDocument);
2238 
2239  linkedZoomOut();
2240 
2241  _undo_states.clear();
2242  _undo_states.push_back(domDocument);
2243  return true;
2244 }
2245 
2247 {
2248  if (ui->pushButtonLink->isChecked())
2249  {
2250  for (const auto& it : TabbedPlotWidget::instances())
2251  {
2252  auto tabs = it.second->tabWidget();
2253  for (int t = 0; t < tabs->count(); t++)
2254  {
2255  if (PlotDocker* matrix = dynamic_cast<PlotDocker*>(tabs->widget(t)))
2256  {
2257  bool first = true;
2258  Range range;
2259  // find the ideal zoom
2260  for (int index = 0; index < matrix->plotCount(); index++)
2261  {
2262  PlotWidget* plot = matrix->plotAt(index);
2263  if (plot->isEmpty())
2264  {
2265  continue;
2266  }
2267 
2268  auto rect = plot->maxZoomRect();
2269  if (first)
2270  {
2271  range.min = rect.left();
2272  range.max = rect.right();
2273  first = false;
2274  }
2275  else
2276  {
2277  range.min = std::min(rect.left(), range.min);
2278  range.max = std::max(rect.right(), range.max);
2279  }
2280  }
2281 
2282  for (int index = 0; index < matrix->plotCount() && !first; index++)
2283  {
2284  PlotWidget* plot = matrix->plotAt(index);
2285  if (plot->isEmpty())
2286  {
2287  continue;
2288  }
2289  QRectF bound_act = plot->maxZoomRect();
2290  bound_act.setLeft(range.min);
2291  bound_act.setRight(range.max);
2292  plot->setZoomRectangle(bound_act, false);
2293  plot->replot();
2294  }
2295  }
2296  }
2297  }
2298  }
2299  else
2300  {
2301  this->forEachWidget([](PlotWidget* plot) { plot->zoomOut(false); });
2302  }
2303 }
2304 
2306 {
2307  this->setFocus();
2308 }
2309 
2311  std::function<void(PlotWidget*, PlotDocker*, int)> operation)
2312 {
2313  auto func = [&](QTabWidget* tabs) {
2314  for (int t = 0; t < tabs->count(); t++)
2315  {
2316  PlotDocker* matrix = dynamic_cast<PlotDocker*>(tabs->widget(t));
2317  if (!matrix)
2318  {
2319  continue;
2320  }
2321 
2322  for (int index = 0; index < matrix->plotCount(); index++)
2323  {
2324  PlotWidget* plot = matrix->plotAt(index);
2325  operation(plot, matrix, index);
2326  }
2327  }
2328  };
2329 
2330  for (const auto& it : TabbedPlotWidget::instances())
2331  {
2332  func(it.second->tabWidget());
2333  }
2334 }
2335 
2336 void MainWindow::forEachWidget(std::function<void(PlotWidget*)> op)
2337 {
2338  forEachWidget([&](PlotWidget* plot, PlotDocker*, int) { op(plot); });
2339 }
2340 
2342 {
2343  auto range = calculateVisibleRangeX();
2344 
2345  ui->timeSlider->setLimits(std::get<0>(range), std::get<1>(range), std::get<2>(range));
2346 
2347  _tracker_time = std::max(_tracker_time, ui->timeSlider->getMinimum());
2348  _tracker_time = std::min(_tracker_time, ui->timeSlider->getMaximum());
2349 }
2350 
2352 {
2353  auto range = calculateVisibleRangeX();
2354  double min_time = std::get<0>(range);
2355 
2356  const bool remove_offset = ui->pushButtonRemoveTimeOffset->isChecked();
2357  if (remove_offset && min_time != std::numeric_limits<double>::max())
2358  {
2359  _time_offset.set(min_time);
2360  }
2361  else
2362  {
2363  _time_offset.set(0.0);
2364  }
2365 }
2366 
2367 void MainWindow::updateDataAndReplot(bool replot_hidden_tabs)
2368 {
2369  _replot_timer->stop();
2370 
2371  MoveDataRet move_ret;
2372 
2374  {
2375  {
2376  std::lock_guard<std::mutex> lock(_active_streamer_plugin->mutex());
2377  move_ret = MoveData(_active_streamer_plugin->dataMap(), _mapped_plot_data, false);
2378  }
2379 
2380  for (const auto& str : move_ret.added_curves)
2381  {
2383  }
2384 
2385  if (move_ret.curves_updated)
2386  {
2388  }
2389 
2390  _mapped_plot_data.setMaximumRangeX(ui->streamingSpinBox->value());
2391  }
2392 
2393  const bool is_streaming_active = isStreamingActive();
2394 
2395  //--------------------------------
2396  std::vector<TransformFunction*> transforms;
2397  transforms.reserve(_transform_functions.size());
2398  for (auto& [id, function] : _transform_functions)
2399  {
2400  transforms.push_back(function.get());
2401  }
2402  std::sort(
2403  transforms.begin(), transforms.end(),
2404  [](TransformFunction* a, TransformFunction* b) { return a->order() < b->order(); });
2405 
2406  // Update the reactive plots
2408 
2409  // update all transforms, but not the ReactiveLuaFunction
2410  for (auto& function : transforms)
2411  {
2412  if (dynamic_cast<ReactiveLuaFunction*>(function) == nullptr)
2413  {
2414  function->calculate();
2415  }
2416  }
2417 
2418  forEachWidget([](PlotWidget* plot) { plot->updateCurves(false); });
2419 
2420  //--------------------------------
2421  // trigger again the execution of this callback if steaming == true
2422  if (is_streaming_active)
2423  {
2424  auto range = calculateVisibleRangeX();
2425  double max_time = std::get<1>(range);
2426  _tracker_time = max_time;
2427  onTrackerTimeUpdated(_tracker_time, false);
2428  }
2429  else
2430  {
2431  updateTimeOffset();
2432  updateTimeSlider();
2433  }
2434  //--------------------------------
2435  linkedZoomOut();
2436 }
2437 
2439 {
2440  double real_value = value;
2441 
2442  if (isStreamingActive() == false)
2443  {
2444  return;
2445  }
2446 
2447  _mapped_plot_data.setMaximumRangeX(real_value);
2448 
2450  {
2451  _active_streamer_plugin->setMaximumRangeX(real_value);
2452  }
2453 }
2454 
2456 {
2457  this->close();
2458 }
2459 
2461 {
2462  updateTimeOffset();
2464 
2465  forEachWidget([](PlotWidget* plot) { plot->replot(); });
2466 
2467  if (this->signalsBlocked() == false)
2468  {
2469  onUndoableChange();
2470  }
2471 }
2472 
2474 {
2475  QLineEdit* timeLine = ui->displayTime;
2476  const double relative_time = _tracker_time - _time_offset.get();
2477  if (ui->pushButtonUseDateTime->isChecked())
2478  {
2479  if (ui->pushButtonRemoveTimeOffset->isChecked())
2480  {
2481  QTime time = QTime::fromMSecsSinceStartOfDay(std::round(relative_time * 1000.0));
2482  timeLine->setText(time.toString("HH:mm::ss.zzz"));
2483  }
2484  else
2485  {
2486  QDateTime datetime =
2487  QDateTime::fromMSecsSinceEpoch(std::round(_tracker_time * 1000.0));
2488  timeLine->setText(datetime.toString("[yyyy MMM dd] HH:mm::ss.zzz"));
2489  }
2490  }
2491  else
2492  {
2493  timeLine->setText(QString::number(relative_time, 'f', 3));
2494  }
2495 
2496  QFontMetrics fm(timeLine->font());
2497  int width = fm.width(timeLine->text()) + 10;
2498  timeLine->setFixedWidth(std::max(100, width));
2499 }
2500 
2502 {
2503  forEachWidget([checked](PlotWidget* plot) {
2504  plot->activateGrid(checked);
2505  plot->replot();
2506  });
2507 }
2508 
2510 {
2511  forEachWidget([checked](PlotWidget* plot) {
2512  plot->setKeepRatioXY(checked);
2513  plot->replot();
2514  });
2515 }
2516 
2518 {
2519  if (checked)
2520  {
2521  _publish_timer->start();
2522  _prev_publish_time = QDateTime::currentDateTime();
2523  }
2524  else
2525  {
2526  _publish_timer->stop();
2527  }
2528 }
2529 
2531 {
2532  for (auto& it : _mapped_plot_data.numeric)
2533  {
2534  it.second.clear();
2535  }
2536 
2537  for (auto& it : _mapped_plot_data.strings)
2538  {
2539  it.second.clear();
2540  }
2541 
2542  for (auto& it : _mapped_plot_data.user_defined)
2543  {
2544  it.second.clear();
2545  }
2546 
2547  for (auto& it : _transform_functions)
2548  {
2549  it.second->reset();
2550  }
2551 
2552  forEachWidget([](PlotWidget* plot) {
2553  plot->reloadPlotData();
2554  plot->replot();
2555  });
2556 }
2557 
2558 void MainWindow::on_deleteSerieFromGroup(std::string group_name)
2559 {
2560  std::vector<std::string> names;
2561 
2562  auto AddFromGroup = [&](auto& series) {
2563  for (auto& it : series)
2564  {
2565  const auto& group = it.second.group();
2566  if (group && group->name() == group_name)
2567  {
2568  names.push_back(it.first);
2569  }
2570  }
2571  };
2572  AddFromGroup(_mapped_plot_data.numeric);
2573  AddFromGroup(_mapped_plot_data.strings);
2574  AddFromGroup(_mapped_plot_data.user_defined);
2575 
2576  onDeleteMultipleCurves(names);
2577 }
2578 
2580 {
2581  if (active_count > 0 && _active_streamer_plugin)
2582  {
2584 
2585  QString tooltipText = QString("%1 has %2 outstanding notitication%3")
2586  .arg(_active_streamer_plugin->name())
2587  .arg(active_count)
2588  .arg(active_count > 1 ? "s" : "");
2589  ui->buttonStreamingNotifications->setToolTip(tooltipText);
2590  }
2591  else
2592  {
2594  ui->buttonStreamingNotifications->setToolTip("View streaming alerts");
2595  }
2596 }
2597 
2599 {
2600  static bool first = true;
2601  if (checked && ui->pushButtonRemoveTimeOffset->isChecked())
2602  {
2603  if (first)
2604  {
2605  QMessageBox::information(this, tr("Note"),
2606  tr("When \"Use Date Time\" is checked, the option "
2607  "\"Remove Time Offset\" "
2608  "is automatically disabled.\n"
2609  "This message will be shown only once."));
2610  first = false;
2611  }
2612  ui->pushButtonRemoveTimeOffset->setChecked(false);
2613  }
2615 }
2616 
2618 {
2620  {
2622  }
2623  else if (_tracker_param == CurveTracker::VALUE)
2624  {
2626  }
2628  {
2630  }
2631  ui->pushButtonTimeTracker->setIcon(_tracker_button_icons[_tracker_param]);
2632 
2633  forEachWidget([&](PlotWidget* plot) {
2634  plot->configureTracker(_tracker_param);
2635  plot->replot();
2636  });
2637 }
2638 
2639 void MainWindow::closeEvent(QCloseEvent* event)
2640 {
2641  _replot_timer->stop();
2642  _publish_timer->stop();
2643 
2645  {
2646  _active_streamer_plugin->shutdown();
2647  _active_streamer_plugin = nullptr;
2648  }
2649  QSettings settings;
2650  settings.setValue("MainWindow.geometry", saveGeometry());
2651  settings.setValue("MainWindow.state", saveState());
2652 
2653  settings.setValue("MainWindow.activateGrid", ui->pushButtonActivateGrid->isChecked());
2654  settings.setValue("MainWindow.removeTimeOffset",
2655  ui->pushButtonRemoveTimeOffset->isChecked());
2656  settings.setValue("MainWindow.dateTimeDisplay", ui->pushButtonUseDateTime->isChecked());
2657  settings.setValue("MainWindow.buttonLink", ui->pushButtonLink->isChecked());
2658  settings.setValue("MainWindow.buttonRatio", ui->pushButtonRatio->isChecked());
2659 
2660  settings.setValue("MainWindow.streamingBufferValue", ui->streamingSpinBox->value());
2661  settings.setValue("MainWindow.timeTrackerSetting", (int)_tracker_param);
2662  settings.setValue("MainWindow.splitterWidth", ui->mainSplitter->sizes()[0]);
2663 
2664  _data_loader.clear();
2665  _data_streamer.clear();
2666  _state_publisher.clear();
2667  _toolboxes.clear();
2668 }
2669 
2670 void MainWindow::onAddCustomPlot(const std::string& plot_name)
2671 {
2672  ui->widgetStack->setCurrentIndex(1);
2673  _function_editor->setLinkedPlotName(QString::fromStdString(plot_name));
2675 }
2676 
2677 void MainWindow::onEditCustomPlot(const std::string& plot_name)
2678 {
2679  ui->widgetStack->setCurrentIndex(1);
2680  auto custom_it = _transform_functions.find(plot_name);
2681  if (custom_it == _transform_functions.end())
2682  {
2683  qWarning("failed to find custom equation");
2684  return;
2685  }
2687  std::dynamic_pointer_cast<LuaCustomFunction>(custom_it->second));
2688 }
2689 
2690 void MainWindow::onRefreshCustomPlot(const std::string& plot_name)
2691 {
2692  try
2693  {
2694  auto custom_it = _transform_functions.find(plot_name);
2695  if (custom_it == _transform_functions.end())
2696  {
2697  qWarning("failed to find custom equation");
2698  return;
2699  }
2700  CustomPlotPtr ce = std::dynamic_pointer_cast<LuaCustomFunction>(custom_it->second);
2702 
2704  updateDataAndReplot(true);
2705  }
2706  catch (const std::runtime_error& e)
2707  {
2708  QMessageBox::critical(this, "error",
2709  "Failed to refresh data : " + QString::fromStdString(e.what()));
2710  }
2711 }
2712 
2714 {
2715  qint64 delta_ms =
2716  (QDateTime::currentMSecsSinceEpoch() - _prev_publish_time.toMSecsSinceEpoch());
2717  _prev_publish_time = QDateTime::currentDateTime();
2718  delta_ms = std::max((qint64)_publish_timer->interval(), delta_ms);
2719 
2720  _tracker_time += delta_ms * 0.001 * ui->playbackRate->value();
2721  if (_tracker_time >= ui->timeSlider->getMaximum())
2722  {
2723  if (!ui->playbackLoop->isChecked())
2724  {
2725  ui->pushButtonPlay->setChecked(false);
2726  }
2727  _tracker_time = ui->timeSlider->getMinimum();
2728  }
2730  auto prev = ui->timeSlider->blockSignals(true);
2731  ui->timeSlider->setRealValue(_tracker_time);
2732  ui->timeSlider->blockSignals(prev);
2733 
2738 
2739  for (auto& it : _state_publisher)
2740  {
2741  it.second->play(_tracker_time);
2742  }
2743 
2744  forEachWidget([&](PlotWidget* plot) {
2745  plot->setTrackerPosition(_tracker_time);
2746  plot->replot();
2747  });
2748 }
2749 
2750 void MainWindow::onCustomPlotCreated(std::vector<CustomPlotPtr> custom_plots)
2751 {
2752  std::set<PlotWidget*> widget_to_replot;
2753 
2754  for (auto custom_plot : custom_plots)
2755  {
2756  const std::string& curve_name = custom_plot->aliasName().toStdString();
2757  // clear already existing data first
2758  auto data_it = _mapped_plot_data.numeric.find(curve_name);
2759  if (data_it != _mapped_plot_data.numeric.end())
2760  {
2761  data_it->second.clear();
2762  }
2763  try
2764  {
2765  custom_plot->calculateAndAdd(_mapped_plot_data);
2766  }
2767  catch (std::exception& ex)
2768  {
2769  QMessageBox::warning(this, tr("Warning"),
2770  tr("Failed to create the custom timeseries. "
2771  "Error:\n\n%1")
2772  .arg(ex.what()));
2773  }
2774 
2775  // keep data for reference
2776  auto custom_it = _transform_functions.find(curve_name);
2777  if (custom_it == _transform_functions.end())
2778  {
2779  _transform_functions.insert({ curve_name, custom_plot });
2780  _curvelist_widget->addCustom(QString::fromStdString(curve_name));
2781  }
2782  else
2783  {
2784  custom_it->second = custom_plot;
2785  }
2786 
2787  forEachWidget([&](PlotWidget* plot) {
2788  if (plot->curveFromTitle(QString::fromStdString(curve_name)))
2789  {
2790  widget_to_replot.insert(plot);
2791  }
2792  });
2793  }
2794 
2796  ui->widgetStack->setCurrentIndex(0);
2798 
2799  for (auto plot : widget_to_replot)
2800  {
2801  plot->updateCurves(true);
2802  plot->replot();
2803  }
2805 }
2806 
2808 {
2809  QDesktopServices::openUrl(QUrl("https://github.com/facontidavide/PlotJuggler/issues"));
2810 }
2811 
2813 {
2814  QDesktopServices::openUrl(QUrl("https://twitter.com/intent/"
2815  "tweet?hashtags=PlotJuggler"));
2816 }
2817 
2819 {
2820  QDialog* dialog = new QDialog(this);
2821  auto ui = new Ui::AboutDialog();
2822  ui->setupUi(dialog);
2823 
2824  ui->label_version->setText(QString("version: ") + QApplication::applicationVersion());
2825  dialog->setAttribute(Qt::WA_DeleteOnClose);
2826 
2827  QFile fileTitle(_skin_path + "/about_window_title.html");
2828  if (fileTitle.open(QIODevice::ReadOnly))
2829  {
2830  ui->titleTextBrowser->setHtml(fileTitle.readAll());
2831  }
2832 
2833  QFile fileBody(_skin_path + "/about_window_body.html");
2834  if (fileBody.open(QIODevice::ReadOnly))
2835  {
2836  ui->bodyTextBrowser->setHtml(fileBody.readAll());
2837  }
2838 
2839  dialog->setAttribute(Qt::WA_DeleteOnClose);
2840  dialog->exec();
2841 }
2842 
2844 {
2845  QSettings settings;
2846 
2847  CheatsheetDialog* dialog = new CheatsheetDialog(this);
2848  dialog->restoreGeometry(settings.value("Cheatsheet.geometry").toByteArray());
2849  dialog->exec();
2850  settings.setValue("Cheatsheet.geometry", dialog->saveGeometry());
2851  dialog->deleteLater();
2852 }
2853 
2855 {
2856  QDialog* dialog = new QDialog(this);
2857  auto ui = new Ui::SupportDialog();
2858  ui->setupUi(dialog);
2859 
2860  dialog->setAttribute(Qt::WA_DeleteOnClose);
2861 
2862  dialog->exec();
2863 }
2864 
2865 /*
2866 void MainWindow::on_actionSaveAllPlotTabs_triggered()
2867 {
2868  QSettings settings;
2869  QString directory_path = settings.value("MainWindow.saveAllPlotTabs",
2870 QDir::currentPath()).toString();
2871  // Get destination folder
2872  QFileDialog saveDialog(this);
2873  saveDialog.setDirectory(directory_path);
2874  saveDialog.setFileMode(QFileDialog::FileMode::Directory);
2875  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
2876  saveDialog.exec();
2877 
2878  uint image_number = 1;
2879  if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
2880  {
2881  // Save Plots
2882  QString directory = saveDialog.selectedFiles().first();
2883  settings.setValue("MainWindow.saveAllPlotTabs", directory);
2884 
2885  QStringList file_names;
2886  QStringList existing_files;
2887  QDateTime current_date_time(QDateTime::currentDateTime());
2888  QString current_date_time_name(current_date_time.toString("yyyy-MM-dd_HH-mm-ss"));
2889  for (const auto& it : TabbedPlotWidget::instances())
2890  {
2891  auto tab_widget = it.second->tabWidget();
2892  for (int i = 0; i < tab_widget->count(); i++)
2893  {
2894  PlotDocker* matrix = static_cast<PlotDocker*>(tab_widget->widget(i));
2895  QString name = QString("%1/%2_%3_%4.png")
2896  .arg(directory)
2897  .arg(current_date_time_name)
2898  .arg(image_number, 2, 10, QLatin1Char('0'))
2899  .arg(matrix->name());
2900  file_names.push_back(name);
2901  image_number++;
2902 
2903  QFileInfo check_file(file_names.back());
2904  if (check_file.exists() && check_file.isFile())
2905  {
2906  existing_files.push_back(name);
2907  }
2908  }
2909  }
2910  if (existing_files.isEmpty() == false)
2911  {
2912  QMessageBox msgBox;
2913  msgBox.setText("One or more files will be overwritten. ant to continue?");
2914  QString all_files;
2915  for (const auto& str : existing_files)
2916  {
2917  all_files.push_back("\n");
2918  all_files.append(str);
2919  }
2920  msgBox.setInformativeText(all_files);
2921  msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok);
2922  msgBox.setDefaultButton(QMessageBox::Ok);
2923 
2924  if (msgBox.exec() != QMessageBox::Ok)
2925  {
2926  return;
2927  }
2928  }
2929 
2930  image_number = 0;
2931  for (const auto& it : TabbedPlotWidget::instances())
2932  {
2933  auto tab_widget = it.second->tabWidget();
2934  for (int i = 0; i < tab_widget->count(); i++)
2935  {
2936  PlotDocker* matrix = static_cast<PlotDocker*>(tab_widget->widget(i));
2937  TabbedPlotWidget::saveTabImage(file_names[image_number], matrix);
2938  image_number++;
2939  }
2940  }
2941  }
2942 }*/
2943 
2945 {
2946  if (_data_loader.empty())
2947  {
2948  QMessageBox::warning(this, tr("Warning"),
2949  tr("No plugin was loaded to process a data file\n"));
2950  return;
2951  }
2952 
2953  QSettings settings;
2954 
2955  QString file_extension_filter;
2956 
2957  std::set<QString> extensions;
2958 
2959  for (auto& it : _data_loader)
2960  {
2961  DataLoaderPtr loader = it.second;
2962  for (QString extension : loader->compatibleFileExtensions())
2963  {
2964  extensions.insert(extension.toLower());
2965  }
2966  }
2967 
2968  for (const auto& it : extensions)
2969  {
2970  file_extension_filter.append(QString(" *.") + it);
2971  }
2972 
2973  QString directory_path =
2974  settings.value("MainWindow.lastDatafileDirectory", QDir::currentPath()).toString();
2975 
2976  QFileDialog loadDialog(this);
2977  loadDialog.setFileMode(QFileDialog::ExistingFiles);
2978  loadDialog.setViewMode(QFileDialog::Detail);
2979  loadDialog.setNameFilter(file_extension_filter);
2980  loadDialog.setDirectory(directory_path);
2981 
2982  QStringList fileNames;
2983  if (loadDialog.exec())
2984  {
2985  fileNames = loadDialog.selectedFiles();
2986  }
2987 
2988  if (fileNames.isEmpty())
2989  {
2990  return;
2991  }
2992 
2993  directory_path = QFileInfo(fileNames[0]).absolutePath();
2994  settings.setValue("MainWindow.lastDatafileDirectory", directory_path);
2995 
2996  if (loadDataFromFiles(fileNames))
2997  {
2998  updateRecentDataMenu(fileNames);
2999  }
3000 }
3001 
3003 {
3004  QSettings settings;
3005 
3006  QString directory_path =
3007  settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3008  QString filename =
3009  QFileDialog::getOpenFileName(this, "Open Layout", directory_path, "*.xml");
3010  if (filename.isEmpty())
3011  {
3012  return;
3013  }
3014 
3015  if (loadLayoutFromFile(filename))
3016  {
3017  updateRecentLayoutMenu({ filename });
3018  }
3019 
3020  directory_path = QFileInfo(filename).absolutePath();
3021  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
3022 }
3023 
3025 {
3026  QDomDocument doc = xmlSaveState();
3027 
3028  QSettings settings;
3029 
3030  QString directory_path =
3031  settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3032 
3033  QFileDialog saveDialog(this);
3034  saveDialog.setOption(QFileDialog::DontUseNativeDialog, true);
3035 
3036  QGridLayout* save_layout = static_cast<QGridLayout*>(saveDialog.layout());
3037 
3038  QFrame* frame = new QFrame;
3039  frame->setFrameStyle(QFrame::Box | QFrame::Plain);
3040  frame->setLineWidth(1);
3041 
3042  QVBoxLayout* vbox = new QVBoxLayout;
3043  QLabel* title = new QLabel("Save Layout options");
3044  QFrame* separator = new QFrame;
3045  separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
3046 
3047  auto checkbox_datasource = new QCheckBox("Save data source");
3048  checkbox_datasource->setToolTip("the layout will remember the source of your data,\n"
3049  "i.e. the Datafile used or the Streaming Plugin loaded "
3050  "?");
3051  checkbox_datasource->setFocusPolicy(Qt::NoFocus);
3052  checkbox_datasource->setChecked(
3053  settings.value("MainWindow.saveLayoutDataSource", true).toBool());
3054 
3055  auto checkbox_snippets = new QCheckBox("Save Scripts (transforms and colormaps)");
3056  checkbox_snippets->setToolTip("Do you want the layout to save your Lua scripts?");
3057  checkbox_snippets->setFocusPolicy(Qt::NoFocus);
3058  checkbox_snippets->setChecked(
3059  settings.value("MainWindow.saveLayoutSnippets", true).toBool());
3060 
3061  vbox->addWidget(title);
3062  vbox->addWidget(separator);
3063  vbox->addWidget(checkbox_datasource);
3064  vbox->addWidget(checkbox_snippets);
3065  frame->setLayout(vbox);
3066 
3067  int rows = save_layout->rowCount();
3068  int col = save_layout->columnCount();
3069  save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
3070 
3071  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
3072  saveDialog.setDefaultSuffix("xml");
3073  saveDialog.setNameFilter("XML (*.xml)");
3074  saveDialog.setDirectory(directory_path);
3075  saveDialog.exec();
3076 
3077  if (saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
3078  {
3079  return;
3080  }
3081 
3082  QString fileName = saveDialog.selectedFiles().first();
3083 
3084  if (fileName.isEmpty())
3085  {
3086  return;
3087  }
3088 
3089  directory_path = QFileInfo(fileName).absolutePath();
3090  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
3091  settings.setValue("MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked());
3092  settings.setValue("MainWindow.saveLayoutSnippets", checkbox_snippets->isChecked());
3093 
3094  QDomElement root = doc.namedItem("root").toElement();
3095 
3096  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
3097 
3098  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
3099 
3100  root.appendChild(savePluginState(doc));
3101 
3102  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
3103 
3104  if (checkbox_datasource->isChecked())
3105  {
3106  QDomElement loaded_list = doc.createElement("previouslyLoaded_Datafiles");
3107 
3108  for (const auto& loaded : _loaded_datafiles)
3109  {
3110  QString loaded_datafile = QDir(directory_path).relativeFilePath(loaded.filename);
3111 
3112  QDomElement file_elem = doc.createElement("fileInfo");
3113  file_elem.setAttribute("filename", loaded_datafile);
3114  file_elem.setAttribute("prefix", loaded.prefix);
3115 
3116  QDomElement datasources_elem = doc.createElement("selected_datasources");
3117  QString topics_list = loaded.selected_datasources.join(";");
3118  datasources_elem.setAttribute("value", topics_list);
3119  file_elem.appendChild(datasources_elem);
3120 
3121  file_elem.appendChild(loaded.plugin_config.firstChild());
3122  loaded_list.appendChild(file_elem);
3123  }
3124  root.appendChild(loaded_list);
3125 
3127  {
3128  QDomElement loaded_streamer = doc.createElement("previouslyLoaded_Streamer");
3129  QString streamer_name = _active_streamer_plugin->name();
3130  loaded_streamer.setAttribute("name", streamer_name);
3131  root.appendChild(loaded_streamer);
3132  }
3133  }
3134  //-----------------------------------
3135  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
3136  if (checkbox_snippets->isChecked())
3137  {
3138  QDomElement custom_equations = doc.createElement("customMathEquations");
3139  for (const auto& custom_it : _transform_functions)
3140  {
3141  const auto& custom_plot = custom_it.second;
3142  custom_plot->xmlSaveState(doc, custom_equations);
3143  }
3144  root.appendChild(custom_equations);
3145 
3146  QByteArray snippets_xml_text =
3147  settings.value("AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
3148  auto snipped_saved = GetSnippetsFromXML(snippets_xml_text);
3149  auto snippets_root = ExportSnippets(snipped_saved, doc);
3150  root.appendChild(snippets_root);
3151 
3152  QDomElement color_maps = doc.createElement("colorMaps");
3153  for (const auto& it : ColorMapLibrary())
3154  {
3155  QString colormap_name = it.first;
3156  QDomElement colormap = doc.createElement("colorMap");
3157  QDomText colormap_script = doc.createTextNode(it.second->script());
3158  colormap.setAttribute("name", colormap_name);
3159  colormap.appendChild(colormap_script);
3160  color_maps.appendChild(colormap);
3161  }
3162  }
3163  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
3164  //------------------------------------
3165  QFile file(fileName);
3166  if (file.open(QIODevice::WriteOnly))
3167  {
3168  QTextStream stream(&file);
3169  stream << doc.toString() << endl;
3170  }
3171 }
3172 
3174 {
3175  static bool first_call = true;
3176  if (first_call && !_minimized)
3177  {
3178  first_call = false;
3179  QMessageBox::information(this, "Remember!",
3180  "Press F10 to switch back to the normal view");
3181  }
3182 
3184 
3185  ui->leftMainWindowFrame->setVisible(!_minimized);
3186  // ui->widgetOptions->setVisible(!_minimized && ui->pushButtonOptions->isChecked());
3187  ui->widgetTimescale->setVisible(!_minimized);
3188  ui->menuBar->setVisible(!_minimized);
3189 
3190  for (auto& it : TabbedPlotWidget::instances())
3191  {
3192  it.second->setControlsVisible(!_minimized);
3193  }
3194 }
3195 
3197 {
3198  QMenu* menu = _recent_data_files;
3199  for (QAction* action : menu->actions())
3200  {
3201  if (action->isSeparator())
3202  {
3203  break;
3204  }
3205  menu->removeAction(action);
3206  }
3207  menu->setEnabled(false);
3208  QSettings settings;
3209  settings.setValue("MainWindow.recentlyLoadedDatafile", {});
3210 }
3211 
3213 {
3214  QMenu* menu = _recent_layout_files;
3215  for (QAction* action : menu->actions())
3216  {
3217  if (action->isSeparator())
3218  {
3219  break;
3220  }
3221  menu->removeAction(action);
3222  }
3223  menu->setEnabled(false);
3224  QSettings settings;
3225  settings.setValue("MainWindow.recentlyLoadedLayout", {});
3226 }
3227 
3229 {
3230  QMessageBox msgBox(this);
3231  msgBox.setWindowTitle("Warning. Can't be undone.");
3232  msgBox.setText(tr("Do you want to remove the previously loaded data?\n"));
3233  msgBox.addButton(QMessageBox::No);
3234  msgBox.addButton(QMessageBox::Yes);
3235  msgBox.setDefaultButton(QMessageBox::Yes);
3236  auto reply = msgBox.exec();
3237 
3238  if (reply == QMessageBox::No)
3239  {
3240  return;
3241  }
3242 
3243  deleteAllData();
3244 }
3245 
3247 {
3248  QSettings settings;
3249  QString prev_style = settings.value("Preferences::theme", "light").toString();
3250 
3251  PreferencesDialog dialog;
3252  dialog.exec();
3253 
3254  QString theme = settings.value("Preferences::theme").toString();
3255 
3256  if (!theme.isEmpty() && theme != prev_style)
3257  {
3258  loadStyleSheet(tr(":/resources/stylesheet_%1.qss").arg(theme));
3259  }
3260 }
3261 
3263 {
3264  ui->timeSlider->setFocus();
3265  ui->timeSlider->setRealStepValue(step);
3266 }
3267 
3269 {
3270  QSettings settings;
3271  QString directory_path =
3272  settings.value("MainWindow.loadStyleSheetDirectory", QDir::currentPath())
3273  .toString();
3274 
3275  QString fileName = QFileDialog::getOpenFileName(this, tr("Load StyleSheet"),
3276  directory_path, tr("(*.qss)"));
3277  if (fileName.isEmpty())
3278  {
3279  return;
3280  }
3281 
3282  loadStyleSheet(fileName);
3283 
3284  directory_path = QFileInfo(fileName).absolutePath();
3285  settings.setValue("MainWindow.loadStyleSheetDirectory", directory_path);
3286 }
3287 
3289 {
3290  switch (_labels_status)
3291  {
3292  case LabelStatus::LEFT:
3293  _labels_status = LabelStatus::HIDDEN;
3294  break;
3295  case LabelStatus::RIGHT:
3296  _labels_status = LabelStatus::LEFT;
3297  break;
3298  case LabelStatus::HIDDEN:
3299  _labels_status = LabelStatus::RIGHT;
3300  break;
3301  }
3302 
3303  auto visitor = [=](PlotWidget* plot) {
3304  plot->activateLegend(_labels_status != LabelStatus::HIDDEN);
3305 
3306  if (_labels_status == LabelStatus::LEFT)
3307  {
3308  plot->setLegendAlignment(Qt::AlignLeft);
3309  }
3310  else if (_labels_status == LabelStatus::RIGHT)
3311  {
3312  plot->setLegendAlignment(Qt::AlignRight);
3313  }
3314  plot->replot();
3315  };
3316 
3317  this->forEachWidget(visitor);
3318 }
3319 
3321 {
3322  linkedZoomOut();
3323  onUndoableChange();
3324 }
3325 
3326 void MainWindow::on_comboStreaming_currentIndexChanged(const QString& current_text)
3327 {
3328  QSettings settings;
3329  settings.setValue("MainWindow.previousStreamingPlugin", current_text);
3330  auto streamer = _data_streamer.at(current_text);
3331  ui->buttonStreamingOptions->setEnabled(!streamer->availableActions().empty());
3332 
3333  std::pair<QAction*, int> notifications_pair = streamer->notificationAction();
3334  if (notifications_pair.first == nullptr)
3335  {
3336  ui->buttonStreamingNotifications->setEnabled(false);
3337  }
3338  else
3339  {
3340  on_streamingNotificationsChanged(notifications_pair.second);
3341  }
3342 }
3343 
3345 {
3346  ui->buttonStreamingStart->setEnabled(false);
3347  if (ui->buttonStreamingStart->text() == "Start")
3348  {
3349  startStreamingPlugin(ui->comboStreaming->currentText());
3350  }
3351  else
3352  {
3354  }
3355  ui->buttonStreamingStart->setEnabled(true);
3356 }
3357 
3358 PopupMenu::PopupMenu(QWidget* relative_widget, QWidget* parent)
3359  : QMenu(parent), _w(relative_widget)
3360 {
3361 }
3362 
3363 void PopupMenu::showEvent(QShowEvent*)
3364 {
3365  QPoint p = _w->mapToGlobal({});
3366  QRect geo = _w->geometry();
3367  this->move(p.x() + geo.width(), p.y());
3368 }
3369 
3371 {
3372  close();
3373 }
3374 
3375 void PopupMenu::closeEvent(QCloseEvent*)
3376 {
3377  _w->setAttribute(Qt::WA_UnderMouse, false);
3378 }
3379 
3381 {
3382  PopupMenu* menu = new PopupMenu(ui->buttonRecentData, this);
3383 
3384  for (auto action : _recent_data_files->actions())
3385  {
3386  menu->addAction(action);
3387  }
3388  menu->exec();
3389 }
3390 
3392 {
3393  if (_data_streamer.empty())
3394  {
3395  return;
3396  }
3397  auto streamer = _data_streamer.at(ui->comboStreaming->currentText());
3398 
3399  PopupMenu* menu = new PopupMenu(ui->buttonStreamingOptions, this);
3400  for (auto action : streamer->availableActions())
3401  {
3402  menu->addAction(action);
3403  }
3404  menu->show();
3405 }
3406 
3408 {
3409  bool hidden = !ui->frameFile->isHidden();
3410  ui->buttonHideFileFrame->setText(hidden ? "+" : " -");
3411  ui->frameFile->setHidden(hidden);
3412 
3413  QSettings settings;
3414  settings.setValue("MainWindow.hiddenFileFrame", hidden);
3415 }
3416 
3418 {
3419  bool hidden = !ui->frameStreaming->isHidden();
3420  ui->buttonHideStreamingFrame->setText(hidden ? "+" : " -");
3421  ui->frameStreaming->setHidden(hidden);
3422 
3423  QSettings settings;
3424  settings.setValue("MainWindow.hiddenStreamingFrame", hidden);
3425 }
3426 
3428 {
3429  bool hidden = !ui->framePublishers->isHidden();
3430  ui->buttonHidePublishersFrame->setText(hidden ? "+" : " -");
3431  ui->framePublishers->setHidden(hidden);
3432 
3433  QSettings settings;
3434  settings.setValue("MainWindow.hiddenPublishersFrame", hidden);
3435 }
3436 
3438 {
3439  PopupMenu* menu = new PopupMenu(ui->buttonRecentLayout, this);
3440 
3441  for (auto action : _recent_layout_files->actions())
3442  {
3443  menu->addAction(action);
3444  }
3445  menu->exec();
3446 }
3447 
3448 QStringList MainWindow::readAllCurvesFromXML(QDomElement root_node)
3449 {
3450  QStringList curves;
3451 
3452  QStringList level_names = { "tabbed_widget", "Tab", "Container", "DockSplitter",
3453  "DockArea", "plot", "curve" };
3454 
3455  std::function<void(int, QDomElement)> recursiveXmlStream;
3456  recursiveXmlStream = [&](int level, QDomElement parent_elem) {
3457  QString level_name = level_names[level];
3458  for (auto elem = parent_elem.firstChildElement(level_name); elem.isNull() == false;
3459  elem = elem.nextSiblingElement(level_name))
3460  {
3461  if (level_name == "curve")
3462  {
3463  curves.push_back(elem.attribute("name"));
3464  }
3465  else
3466  {
3467  recursiveXmlStream(level + 1, elem);
3468  }
3469  }
3470  };
3471 
3472  // start recursion
3473  recursiveXmlStream(0, root_node);
3474 
3475  return curves;
3476 }
3477 
3479 {
3480  ColorMapEditor dialog;
3481  dialog.exec();
3482 }
void on_actionExit_triggered()
void accept(std::vector< CustomPlotPtr > plot)
void onRedoInvoked()
Definition: mainwindow.cpp:422
QTimer * _animated_streaming_timer
Definition: mainwindow.h:165
void on_comboStreaming_currentIndexChanged(const QString &current_text)
void on_actionClearBuffer_triggered()
MoveDataRet MoveData(PlotDataMapRef &source, PlotDataMapRef &destination, bool remove_older)
Definition: utils.cpp:10
QString _skin_path
Definition: mainwindow.h:179
void onActionFullscreenTriggered()
void setLinkedPlotName(const QString &linkedPlotName)
QShortcut _streaming_shortcut
Definition: mainwindow.h:113
QTimer * _replot_timer
Definition: mainwindow.h:156
void on_actionColorMap_Editor_triggered()
void on_actionSupportPlotJuggler_triggered()
int plotCount() const
Ui::MainWindow * ui
Definition: mainwindow.h:106
void dataSourceRemoved(const std::string &name)
unsigned order() const
double max
Definition: plotdatabase.h:32
void on_pushButtonLoadDatafile_clicked()
void on_stylesheetChanged(QString style_dir)
void BuildDummyData(PlotDataMapRef &datamap)
Definition: dummy_data.cpp:12
void on_actionShare_the_love_triggered()
QShortcut _fullscreen_shortcut
Definition: mainwindow.h:112
PlotWidget * plotAt(int index)
Generic interface for a multi input - multi output transformation function. Contrariwise to other plu...
void onEditCustomPlot(const std::string &plot_name)
void on_buttonStreamingStart_clicked()
void updateReactivePlots()
void onPlotTabAdded(PlotDocker *docker)
Definition: mainwindow.cpp:987
#define nullptr
Definition: backward.hpp:386
void on_stylesheetChanged(QString theme)
void editMathPlot(const std::string &plot_name)
void on_actionPreferences_triggered()
void plotWidgetAdded(PlotWidget *)
void rectChanged(PlotWidget *self, QRectF rect)
CurveListPanel * _curvelist_widget
Definition: mainwindow.h:118
void calculateAndAdd(PlotDataMapRef &src_data)
void on_stylesheetChanged(QString style_name)
void updatedDisplayTime()
bool _test_option
Definition: mainwindow.h:140
void on_changeDateTimeScale(bool enable)
std::vector< FileLoadInfo > _loaded_datafiles
Definition: mainwindow.h:149
QTimer * _publish_timer
Definition: mainwindow.h:157
void deleteAllData()
void resizeEvent(QResizeEvent *)
Definition: mainwindow.cpp:920
void undoableChange()
std::map< QString, ColorMap::Ptr > & ColorMapLibrary()
Definition: color_map.cpp:48
bool loadDataFromFiles(QStringList filenames)
void linkedZoomOut()
void checkAllCurvesFromLayout(const QDomElement &root)
The DataLoader plugin type is used to load files.
PlotDataMapRef _mapped_plot_data
Definition: mainwindow.h:120
void closeEvent(QCloseEvent *event)
std::map< CurveTracker::Parameter, QIcon > _tracker_button_icons
Definition: mainwindow.h:152
bool xmlLoadState(QDomDocument state_document)
bool loadLayoutFromFile(QString filename)
void forEachWidget(std::function< void(PlotWidget *, PlotDocker *, int)> op)
std::unordered_set< std::string > getAllNames() const
Definition: plotdata.cpp:107
void on_actionReportBug_triggered()
The DataStreamer base classm used to read streaming of data.
bool erase(const std::string &name)
Definition: plotdata.cpp:148
QStringList selected_datasources
Optional list of pre-selected datasource.
void on_actionClearRecentLayout_triggered()
SnippetData GetSnippetFromXML(const QDomElement &element)
void dropEvent(QDropEvent *event)
void on_actionCheatsheet_triggered()
void onTrackerMovedFromWidget(QPointF pos)
Definition: mainwindow.cpp:462
void on_pushButtonZoomOut_clicked()
void setZoomEnabled(bool enabled)
std::map< QString, ToolboxPluginPtr > _toolboxes
Definition: mainwindow.h:127
QMovie * _animated_streaming_movie
Definition: mainwindow.h:164
bool curves_updated
Definition: utils.h:47
void on_actionClearRecentData_triggered()
std::deque< QDomDocument > _redo_states
Definition: mainwindow.h:136
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1736
void onPlotAdded(PlotWidget *plot)
Definition: mainwindow.cpp:925
QString filename
name of the file to open
void updateDataAndReplot(bool replot_hidden_tabs)
void refreshMathPlot(const std::string &curve_name)
void onTimeSlider_valueChanged(double abs_time)
Definition: mainwindow.cpp:473
void createMathPlot(const std::string &linked_plot)
QStringList initializePlugins(QString subdir_name)
Definition: mainwindow.cpp:595
std::map< QString, DataStreamerPtr > _data_streamer
Definition: mainwindow.h:126
void startStreamingPlugin(QString streamer_name)
double get() const
Definition: utils.h:33
QRectF maxZoomRect() const
void on_buttonStreamingNotifications_clicked()
void setMaximumRangeX(double range)
Definition: plotdata.cpp:132
MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent=nullptr)
Definition: mainwindow.cpp:70
const QPixmap & LoadSvg(QString filename, QString style_name="light")
Definition: svg_util.h:26
QStringList readAllCurvesFromXML(QDomElement root_node)
void trackerMoved(QPointF pos)
TransformsMap _transform_functions
Definition: mainwindow.h:122
void initializeActions()
Definition: mainwindow.cpp:499
std::map< QString, StatePublisherPtr > _state_publisher
Definition: mainwindow.h:125
QDomElement ExportSnippets(const SnippetsMap &snippets, QDomDocument &doc)
void updateDerivedSeries()
void leaveEvent(QEvent *) override
void editExistingPlot(CustomPlotPtr data)
double _tracker_time
Definition: mainwindow.h:144
void reloadPlotData()
Definition: plotwidget.cpp:972
void on_pushButtonPlay_toggled(bool checked)
void buildDummyData()
Definition: mainwindow.cpp:884
void onPlotZoomChanged(PlotWidget *modified_plot, QRectF new_range)
Definition: mainwindow.cpp:965
void closeEvent(QCloseEvent *) override
QElapsedTimer _undo_timer
Definition: mainwindow.h:137
TimeseriesMap numeric
Numerical timeseries.
Definition: plotdata.h:38
virtual size_t size() const
Definition: plotdatabase.h:182
void realValueChanged(double)
void zoomOut(bool emit_signal)
void on_pushButtonLegend_clicked()
void on_pushButtonLoadLayout_clicked()
void onTrackerTimeUpdated(double absolute_time, bool do_replot)
Definition: mainwindow.cpp:479
TabbedPlotWidget * _main_tabbed_widget
Definition: mainwindow.h:108
const std::list< CurveInfo > & curveList() const
void curvesDropped()
Definition: lz4.c:1706
void loadAllPlugins(QStringList command_line_plugin_folders)
Definition: mainwindow.cpp:531
NLOHMANN_BASIC_JSON_TPL_DECLARATION void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression) is_nothrow_move_assignable< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
Definition: json.hpp:21884
void updateRecentLayoutMenu(QStringList new_filenames)
QShortcut _playback_shotcut
Definition: mainwindow.h:114
void onPlaybackLoop()
const Point & front() const
Definition: plotdatabase.h:244
void on_buttonHidePublishersFrame_clicked()
void requestDeleteAll(int)
void loadPluginState(const QDomElement &root)
void on_deleteSerieFromGroup(std::string group_name)
void loadStyleSheet(QString file_path)
QString prefix
prefix to be added to the name of the series (optional)
bool xmlLoadState(QDomElement &tabbed_area)
QStringList _disabled_plugins
Definition: mainwindow.h:147
std::shared_ptr< CustomFunction > CustomPlotPtr
void deleteCurves(const std::vector< std::string > &curve_names)
static void block(LexState *ls)
Definition: lparser.c:1293
void on_actionLoadStyleSheet_triggered()
StringSeriesMap strings
Series of strings.
Definition: plotdata.h:45
void on_buttonHideFileFrame_clicked()
void on_stylesheetChanged(QString theme)
void legendSizeChanged(int new_size)
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void on_tabbedAreaDestroyed(QObject *object)
LabelStatus _labels_status
Definition: mainwindow.h:174
QDateTime _prev_publish_time
Definition: mainwindow.h:160
CurveTracker::Parameter _tracker_param
Definition: mainwindow.h:150
void updateTimeSlider()
TimeseriesMap::iterator addNumeric(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:51
bool addCurve(const std::string &plot_name)
void on_pushButtonRatio_toggled(bool checked)
void on_streamingSpinBox_valueChanged(int value)
static int sort(lua_State *L)
Definition: ltablib.c:398
QMenu * _recent_data_files
Definition: mainwindow.h:176
void onUndoInvoked()
Definition: mainwindow.cpp:439
void configureTracker(CurveTracker::Parameter val)
void stopStreamingPlugin()
void enableStreamingNotificationsButton(bool enabled)
void stylesheetChanged(QString style_name)
void onAddCustomPlot(const std::string &plot_name)
std::map< QString, QString > getPrefixes() const
#define op
QDomElement savePluginState(QDomDocument &doc)
QShortcut _redo_shortcut
Definition: mainwindow.h:111
static TabbedPlotWidget * instance(const QString &key)
bool _minimized
Definition: mainwindow.h:116
void dragEnterEvent(QDragEnterEvent *event)
PJ::DelayedCallback _tracker_delay
Definition: mainwindow.h:158
void on_actionDeleteAllData_triggered()
void on_buttonRecentData_clicked()
void on_buttonHideStreamingFrame_clicked()
const char * source
Definition: lz4.h:699
FunctionEditorWidget * _function_editor
Definition: mainwindow.h:162
void onDeleteMultipleCurves(const std::vector< std::string > &curve_names)
void removeAllCurves() override
Definition: plotwidget.cpp:461
void curveListChanged()
const T & move(const T &v)
Definition: backward.hpp:394
void addCustom(const QString &item_name)
bool _autostart_publishers
Definition: mainwindow.h:142
MonitoredValue _time_offset
Definition: mainwindow.h:154
void on_actionAbout_triggered()
std::deque< QDomDocument > _undo_states
Definition: mainwindow.h:135
ParserFactories _parser_factories
Definition: mainwindow.h:131
const T & first(const T &value, const Tail &...)
Definition: compile.h:178
void set(double newValue)
Definition: utils.h:23
ScatterXYMap scatter_xy
Definition: plotdata.h:35
void LoadColorMapFromSettings()
Definition: color_map.cpp:65
std::shared_ptr< StatePublisher > StatePublisherPtr
std::map< QString, DataLoaderPtr > _data_loader
Definition: mainwindow.h:124
void triggerSignal(int delay_ms)
float time
Definition: mqtt_test.py:17
void setTrackerPosition(double abs_time)
CurveInfo * curveFromTitle(const QString &title)
void on_buttonRecentLayout_clicked()
void on_streamingToggled()
std::shared_ptr< DataStreamer > _active_streamer_plugin
Definition: mainwindow.h:133
PopupMenu(QWidget *relative_widget, QWidget *parent=nullptr)
void on_stylesheetChanged(QString theme)
bool isStreamingActive() const
void on_changeTimeOffset(double offset)
std::enable_if_t< all< Args... >::value, enable_t > enable
Definition: sol.hpp:2244
void hiddenItemsChanged()
void on_pushButtonUseDateTime_toggled(bool checked)
std::vector< std::string > added_curves
Definition: utils.h:46
void updateTimeOffset()
void onRefreshCustomPlot(const std::string &plot_name)
void showEvent(QShowEvent *) override
void on_buttonStreamingPause_toggled(bool paused)
QStringList _enabled_plugins
Definition: mainwindow.h:146
void onCustomPlotCreated(std::vector< CustomPlotPtr > plot)
double min
Definition: plotdatabase.h:31
virtual const char * name() const =0
Name of the plugin type, NOT the particular instance.
QShortcut _undo_shortcut
Definition: mainwindow.h:110
void AddPrefixToPlotData(const std::string &prefix, std::unordered_map< std::string, Value > &data)
Definition: plotdata.h:84
QWidget * _w
Definition: mainwindow.h:302
AnySeriesMap user_defined
Definition: plotdata.h:42
const Point & back() const
Definition: plotdatabase.h:249
void setKeepRatioXY(bool active)
Definition: core.h:1131
void on_pushButtonActivateGrid_toggled(bool checked)
void importPlotDataMap(PlotDataMapRef &new_data, bool remove_old)
void on_buttonStreamingOptions_clicked()
void on_playbackStep_valueChanged(double arg1)
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
void on_pushButtonTimeTracker_pressed()
bool _disable_undo_logging
Definition: mainwindow.h:138
void onDataSourceRemoved(const std::string &src_name)
Definition: plotwidget.cpp:425
void onUndoableChange()
Definition: mainwindow.cpp:401
QMenu * _recent_layout_files
Definition: mainwindow.h:177
void setZoomRectangle(QRectF rect, bool emit_signal)
Definition: plotwidget.cpp:945
QString SetApplicationStyleSheet(QString style)
Definition: stylesheet.h:19
QString _default_streamer
Definition: mainwindow.h:129
void activateGrid(bool activate)
QDomDocument xmlSaveState() const
Definition: mainwindow.cpp:998
void on_splitterMoved(int, int)
Definition: mainwindow.cpp:891
void enableTracker(bool enable)
void on_pushButtonRemoveTimeOffset_toggled(bool checked)
void connectCallback(Function callback)
void updateCurves(bool reset_older_data)
Definition: format.h:895
std::shared_ptr< DataLoader > DataLoaderPtr
void update2ndColumnValues(double time)
void on_pushButtonSaveLayout_clicked()
void updateRecentDataMenu(QStringList new_filenames)
void onUpdateLeftTableValues()
Definition: mainwindow.cpp:457
std::unordered_set< std::string > loadDataFromFile(const FileLoadInfo &info)
void on_streamingNotificationsChanged(int active_notifications_count)
The PlotJugglerPlugin is the base class of all the plugins.
Definition: pj_plugin.h:22
void removeCurve(const std::string &name)
std::tuple< double, double, int > calculateVisibleRangeX()
static const std::map< QString, TabbedPlotWidget * > & instances()


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Jun 19 2023 03:01:38