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


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Nov 11 2024 03:23:45