mainwindow.cpp
Go to the documentation of this file.
1 #include <functional>
2 #include <stdio.h>
3 #include <numeric>
4 
5 #include <QApplication>
6 #include <QActionGroup>
7 #include <QCheckBox>
8 #include <QCommandLineParser>
9 #include <QDebug>
10 #include <QDesktopServices>
11 #include <QDomDocument>
12 #include <QDoubleSpinBox>
13 #include <QElapsedTimer>
14 #include <QFileDialog>
15 #include <QInputDialog>
16 #include <QMenu>
17 #include <QGroupBox>
18 #include <QMessageBox>
19 #include <QMimeData>
20 #include <QMouseEvent>
21 #include <QPluginLoader>
22 #include <QPushButton>
23 #include <QKeySequence>
24 #include <QScrollBar>
25 #include <QSettings>
26 #include <QStringListModel>
27 #include <QStringRef>
28 #include <QThread>
29 #include <QTextStream>
30 #include <QWindow>
31 #include <QHeaderView>
32 #include <QStandardPaths>
33 
34 #include "mainwindow.h"
35 #include "curvelist_panel.h"
36 #include "tabbedplotwidget.h"
37 #include "PlotJuggler/plotdata.h"
38 #include "qwt_plot_canvas.h"
40 #include "utils.h"
41 #include "svg_util.h"
42 #include "stylesheet.h"
43 
44 #include "ui_aboutdialog.h"
45 #include "ui_support_dialog.h"
46 #include "preferences_dialog.h"
47 #include "nlohmann_parsers.h"
49 
50 
51 MainWindow::MainWindow(const QCommandLineParser& commandline_parser, QWidget* parent)
52  : QMainWindow(parent)
53  , ui(new Ui::MainWindow)
54  , _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this)
55  , _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this)
56  , _fullscreen_shortcut(Qt::Key_F10, this)
57  , _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this)
58  , _playback_shotcut(Qt::Key_Space, this)
59  , _minimized(false)
60  , _active_streamer_plugin(nullptr)
61  , _disable_undo_logging(false)
62  , _tracker_time(0)
63  , _tracker_param(CurveTracker::VALUE)
64  , _labels_status(LabelStatus::RIGHT)
65  , _recent_data_files(new QMenu())
66  ,_recent_layout_files(new QMenu())
67 {
68  QLocale::setDefault(QLocale::c()); // set as default
69 
70  _test_option = commandline_parser.isSet("test");
71  _autostart_publishers = commandline_parser.isSet("publish");
72 
74 
75  ui->setupUi(this);
76 
77  ui->playbackLoop->setText("");
78  ui->pushButtonZoomOut->setText("");
79  ui->pushButtonPlay->setText("");
80  ui->pushButtonUseDateTime->setText("");
81  ui->pushButtonActivateGrid->setText("");
82  ui->pushButtonRatio->setText("");
83  ui->pushButtonLink->setText("");
84  ui->pushButtonTimeTracker->setText("");
85  ui->pushButtonLoadDatafile->setText("");
86  ui->pushButtonRemoveTimeOffset->setText("");
87  ui->pushButtonLegend->setText("");
88 
89  if (commandline_parser.isSet("buffer_size"))
90  {
91  int buffer_size = std::max(10, commandline_parser.value("buffer_size").toInt());
92  ui->streamingSpinBox->setMaximum(buffer_size);
93  }
94 
95  _animated_streaming_movie = new QMovie(":/resources/animated_radio.gif");
96  _animated_streaming_movie->setScaledSize(ui->labelStreamingAnimation->size());
97  _animated_streaming_movie->jumpToFrame( 0 );
98 
99  _animated_streaming_timer = new QTimer();
100  _animated_streaming_timer->setSingleShot(true);
101 
102  connect( _animated_streaming_timer, &QTimer::timeout,
103  this, [this]()
104  {
106  _animated_streaming_movie->jumpToFrame( 0 );
107  });
108 
109  ui->labelStreamingAnimation->setMovie(_animated_streaming_movie);
110  ui->labelStreamingAnimation->setHidden(true);
111 
113 
115 
117 
119 
121 
123 
125 
127 
128  connect(ui->playbackRate, &QDoubleSpinBox::editingFinished, this, [this]() { ui->playbackRate->clearFocus(); });
129 
130  connect(ui->playbackStep, &QDoubleSpinBox::editingFinished, this, [this]() { ui->playbackStep->clearFocus(); });
131 
132  _main_tabbed_widget = new TabbedPlotWidget("Main Window", this, _mapped_plot_data, this);
133 
135 
136  ui->plottingLayout->insertWidget(0, _main_tabbed_widget, 1);
137  ui->leftLayout->addWidget(_curvelist_widget,1);
138 
139  ui->mainSplitter->setCollapsible(0, true);
140  ui->mainSplitter->setStretchFactor(0, 2);
141  ui->mainSplitter->setStretchFactor(1, 6);
142 
143  ui->layoutTimescale->removeWidget( ui->widgetButtons );
144  _main_tabbed_widget->tabWidget()->setCornerWidget( ui->widgetButtons );
145 
146  connect(ui->mainSplitter, SIGNAL(splitterMoved(int, int)), SLOT(on_splitterMoved(int, int)));
147 
149 
150  QStringList loaded;
151  loaded += initializePlugins(QCoreApplication::applicationDirPath());
152  loaded += initializePlugins( QStandardPaths::writableLocation( QStandardPaths::GenericDataLocation) + "/PlotJuggler" );
153 
154  auto extra_folders = commandline_parser.value("extra-plugin-folders").split(";", QString::SkipEmptyParts);
155 
156  for(const auto& folder: extra_folders)
157  {
158  loaded += initializePlugins(folder);
159  }
160 
161  _undo_timer.start();
162 
163  // save initial state
165 
166  _replot_timer = new QTimer(this);
167  _replot_timer->setInterval(40);
168  connect(_replot_timer, &QTimer::timeout, this, [this]() { updateDataAndReplot(false); });
169 
170  _publish_timer = new QTimer(this);
171  _publish_timer->setInterval(20);
172  connect(_publish_timer, &QTimer::timeout, this, &MainWindow::onPlaybackLoop);
173 
174  ui->menuFile->setToolTipsVisible(true);
175 
176  this->setMenuBar(ui->menuBar);
177  ui->menuBar->setNativeMenuBar(false);
178 
179  if (_test_option)
180  {
181  buildDummyData();
182  }
183 
184  bool file_loaded = false;
185  if (commandline_parser.isSet("datafile"))
186  {
187  QStringList datafiles = commandline_parser.values("datafile");
188  file_loaded = loadDataFromFiles(datafiles);
189  }
190  if (commandline_parser.isSet("layout"))
191  {
192  loadLayoutFromFile(commandline_parser.value("layout"));
193  }
194 
195  QSettings settings;
196  restoreGeometry(settings.value("MainWindow.geometry").toByteArray());
197  restoreState(settings.value("MainWindow.state").toByteArray());
198 
199  //qDebug() << "restoreGeometry";
200 
201  bool activate_grid = settings.value("MainWindow.activateGrid", false).toBool();
202  ui->pushButtonActivateGrid->setChecked(activate_grid);
203 
204  bool zoom_link_active = settings.value("MainWindow.buttonLink",true).toBool();
205  ui->pushButtonLink->setChecked(zoom_link_active);
206 
207  bool ration_active = settings.value("MainWindow.buttonRatio",true).toBool();
208  ui->pushButtonRatio->setChecked(ration_active);
209 
210  int streaming_buffer_value = settings.value("MainWindow.streamingBufferValue", 5).toInt();
211  ui->streamingSpinBox->setValue(streaming_buffer_value);
212 
213  bool datetime_display = settings.value("MainWindow.dateTimeDisplay", false).toBool();
214  ui->pushButtonUseDateTime->setChecked(datetime_display);
215 
216  bool remove_time_offset = settings.value("MainWindow.removeTimeOffset", true).toBool();
217  ui->pushButtonRemoveTimeOffset->setChecked(remove_time_offset);
218 
219  // ui->widgetOptions->setVisible(ui->pushButtonOptions->isChecked());
220 
221  if( settings.value("MainWindow.hiddenFileFrame", false).toBool() )
222  {
223  ui->buttonHideFileFrame->setText("+");
224  ui->frameFile->setHidden(true);
225  }
226  if( settings.value("MainWindow.hiddenStreamingFrame", false).toBool() )
227  {
228  ui->buttonHideStreamingFrame->setText("+");
229  ui->frameStreaming->setHidden(true);
230  }
231  if( settings.value("MainWindow.hiddenPublishersFrame", false).toBool() )
232  {
233  ui->buttonHidePublishersFrame->setText("+");
234  ui->framePublishers->setHidden(true);
235  }
236 
237  //----------------------------------------------------------
238  QIcon trackerIconA, trackerIconB, trackerIconC;
239 
240  trackerIconA.addFile(QStringLiteral(":/style_light/line_tracker.png"), QSize(36, 36));
241  trackerIconB.addFile(QStringLiteral(":/style_light/line_tracker_1.png"), QSize(36, 36));
242  trackerIconC.addFile(QStringLiteral(":/style_light/line_tracker_a.png"), QSize(36, 36));
243 
247 
248  int tracker_setting = settings.value("MainWindow.timeTrackerSetting", (int)CurveTracker::VALUE).toInt();
249  _tracker_param = static_cast<CurveTracker::Parameter>(tracker_setting);
250 
251  ui->pushButtonTimeTracker->setIcon(_tracker_button_icons[_tracker_param]);
252 
253  forEachWidget([&](PlotWidget* plot) { plot->configureTracker(_tracker_param); });
254 
255  auto editor_layout = new QVBoxLayout();
256  editor_layout->setMargin(0);
257  ui->formulaPage->setLayout(editor_layout);
259  editor_layout->addWidget(_function_editor);
260 
262  this, [this]() {
263  ui->widgetStack->setCurrentIndex(0);
264  });
265 
266  connect(this, &MainWindow::stylesheetChanged,
268 
271 
272  QString theme = settings.value("Preferences::theme", "light").toString();
273  if( theme != "dark") {
274  theme = "light";
275  }
276  loadStyleSheet(tr(":/resources/stylesheet_%1.qss").arg(theme));
277 
278  // builtin messageParsers
279  _message_parser_factory.insert( {"JSON", std::make_shared<JSON_ParserCreator>() });
280  _message_parser_factory.insert( {"CBOR", std::make_shared<CBOR_ParserCreator>() });
281  _message_parser_factory.insert( {"BSON", std::make_shared<BSON_ParserCreator>() });
282  _message_parser_factory.insert( {"MessagePack", std::make_shared<MessagePack_ParserCreator>() });
283 
284 }
285 
287 {
288  delete ui;
289 }
290 
292 {
294  return;
295 
296  int elapsed_ms = _undo_timer.restart();
297 
298  // overwrite the previous
299  if (elapsed_ms < 100)
300  {
301  if (_undo_states.empty() == false)
302  _undo_states.pop_back();
303  }
304 
305  while (_undo_states.size() >= 100)
306  _undo_states.pop_front();
307  _undo_states.push_back(xmlSaveState());
308  _redo_states.clear();
309  //qDebug() << "undo " << _undo_states.size();
310 }
311 
313 {
314  _disable_undo_logging = true;
315  if (_redo_states.size() > 0)
316  {
317  QDomDocument state_document = _redo_states.back();
318  while (_undo_states.size() >= 100)
319  _undo_states.pop_front();
320  _undo_states.push_back(state_document);
321  _redo_states.pop_back();
322 
323  xmlLoadState(state_document);
324  }
325  //qDebug() << "undo " << _undo_states.size();
326  _disable_undo_logging = false;
327 }
328 
330 {
331  _disable_undo_logging = true;
332  if (_undo_states.size() > 1)
333  {
334  QDomDocument state_document = _undo_states.back();
335  while (_redo_states.size() >= 100)
336  _redo_states.pop_front();
337  _redo_states.push_back(state_document);
338  _undo_states.pop_back();
339  state_document = _undo_states.back();
340 
341  xmlLoadState(state_document);
342  }
343  //qDebug() << "undo " << _undo_states.size();
344  _disable_undo_logging = false;
345 }
346 
348 {
350 }
351 
352 void MainWindow::onTrackerMovedFromWidget(QPointF relative_pos)
353 {
354  _tracker_time = relative_pos.x() + _time_offset.get();
355 
356  auto prev = ui->timeSlider->blockSignals(true);
357  ui->timeSlider->setRealValue(_tracker_time);
358  ui->timeSlider->blockSignals(prev);
359 
361 }
362 
364 {
365  _tracker_time = abs_time;
367 }
368 
369 void MainWindow::onTrackerTimeUpdated(double absolute_time, bool do_replot)
370 {
373 
374  for (auto& it : _state_publisher)
375  {
376  it.second->updateState(absolute_time);
377  }
378 
379  forEachWidget([&](PlotWidget* plot) {
381  if (do_replot)
382  {
383  plot->replot();
384  }
385  });
386 }
387 
389 {
390  _undo_shortcut.setContext(Qt::ApplicationShortcut);
391  _redo_shortcut.setContext(Qt::ApplicationShortcut);
392  _fullscreen_shortcut.setContext(Qt::ApplicationShortcut);
393 
394  connect(&_undo_shortcut, &QShortcut::activated, this, &MainWindow::onUndoInvoked);
395  connect(&_redo_shortcut, &QShortcut::activated, this, &MainWindow::onRedoInvoked);
396  connect(&_streaming_shortcut, &QShortcut::activated, this, &MainWindow::on_streamingToggled);
397  connect(&_playback_shotcut, &QShortcut::activated, ui->pushButtonPlay, &QPushButton::toggle);
398  connect(&_fullscreen_shortcut, &QShortcut::activated, this, &MainWindow::onActionFullscreenTriggered);
399 
400  QShortcut* open_menu_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F), this);
401  connect(open_menu_shortcut, &QShortcut::activated,
402  [this]() { ui->menuFile->exec(ui->menuBar->mapToGlobal(QPoint(0, 25))); });
403 
404  QShortcut* open_help_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H), this);
405  connect(open_help_shortcut, &QShortcut::activated,
406  [this]() { ui->menuHelp->exec(ui->menuBar->mapToGlobal(QPoint(230, 25))); });
407 
408  //---------------------------------------------
409 
410  QSettings settings;
411  updateRecentDataMenu(settings.value("MainWindow.recentlyLoadedDatafile").toStringList());
412  updateRecentLayoutMenu(settings.value("MainWindow.recentlyLoadedLayout").toStringList());
413 }
414 
415 QStringList MainWindow::initializePlugins(QString directory_name)
416 {
417  static std::set<QString> loaded_plugins;
418  QStringList loaded_out;
419 
420  qDebug() << "Loading compatible plugins from directory: " << directory_name;
421  int loaded_count = 0;
422 
423  QDir pluginsDir(directory_name);
424 
425  for (const QString& filename : pluginsDir.entryList(QDir::Files))
426  {
427  QFileInfo fileinfo(filename);
428  if (fileinfo.suffix() != "so" && fileinfo.suffix() != "dll" && fileinfo.suffix() != "dylib")
429  {
430  continue;
431  }
432 
433  if (loaded_plugins.find(filename) != loaded_plugins.end())
434  {
435  continue;
436  }
437 
438  QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename), this);
439 
440 
441  QObject* plugin = pluginLoader.instance();
442  if (plugin)
443  {
444  auto class_name = pluginLoader.metaData().value("className").toString();
445  loaded_out.push_back( class_name );
446 
447  DataLoader* loader = qobject_cast<DataLoader*>(plugin);
448  StatePublisher* publisher = qobject_cast<StatePublisher*>(plugin);
449  DataStreamer* streamer = qobject_cast<DataStreamer*>(plugin);
450  MessageParserCreator* message_parser = qobject_cast<MessageParserCreator*>(plugin);
451 
452  QString plugin_name;
453 
454  if (loader){
455  plugin_name = loader->name();
456  }
457  else if (publisher){
458  plugin_name = publisher->name();
459  }
460  else if (streamer){
461  plugin_name = streamer->name();
462  }
463  else if (message_parser){
464  plugin_name = streamer->name();
465  }
466 
467  if (loaded_plugins.find(plugin_name) == loaded_plugins.end())
468  {
469  loaded_plugins.insert(plugin_name);
470  loaded_count++;
471  }
472  else
473  {
474  QMessageBox::warning(this, tr("Warning"),
475  tr("Trying to load twice a plugin with name [%1].\n"
476  "Only the first will be loaded.")
477  .arg(plugin_name));
478  continue;
479  }
480 
481  if (loader)
482  {
483  qDebug() << filename << ": is a DataLoader plugin";
484  if (!_test_option && loader->isDebugPlugin())
485  {
486  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
487  }
488  else
489  {
490  _data_loader.insert(std::make_pair(plugin_name, loader));
491  }
492  }
493  else if (publisher)
494  {
495  publisher->setDataMap(&_mapped_plot_data);
496  qDebug() << filename << ": is a StatePublisher plugin";
497  if (!_test_option && publisher->isDebugPlugin())
498  {
499  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
500  }
501  else
502  {
503  _state_publisher.insert(std::make_pair(plugin_name, publisher));
504 
505  ui->layoutPublishers->setColumnStretch(0, 1.0);
506 
507  int row = _state_publisher.size() -1;
508  auto label = new QLabel(plugin_name, ui->framePublishers);
509  ui->layoutPublishers->addWidget(label, row, 0);
510 
511  auto start_checkbox = new QCheckBox(ui->framePublishers);
512  ui->layoutPublishers->addWidget(start_checkbox, row, 1);
513  start_checkbox->setFocusPolicy( Qt::FocusPolicy::NoFocus );
514 
515  connect(start_checkbox, &QCheckBox::toggled, this,
516  [=](bool enable) { publisher->setEnabled(enable); });
517 
518  connect(publisher, &StatePublisher::closed, start_checkbox,
519  [=]() {start_checkbox->setChecked(false);} );
520 
521  if( publisher->availableActions().empty() )
522  {
523  QFrame* empty = new QFrame(ui->framePublishers);
524  empty->setFixedSize({22,22});
525  ui->layoutPublishers->addWidget(empty, row, 2);
526  }
527  else{
528  auto options_button = new QPushButton(ui->framePublishers);
529  options_button->setFlat(true);
530  options_button->setFixedSize({24,24});
531  ui->layoutPublishers->addWidget(options_button, row, 2);
532 
533  options_button->setIcon( LoadSvgIcon(":/resources/svg/settings_cog.svg", "light"));
534  options_button->setIconSize( {16,16} );
535 
536  auto optionsMenu = [=]()
537  {
538  PopupMenu* menu = new PopupMenu(options_button, this);
539  for(auto action: publisher->availableActions()) {
540  menu->addAction(action);
541  }
542  menu->exec();
543  };
544 
545  connect( options_button, &QPushButton::clicked,
546  options_button, optionsMenu);
547 
548  connect( this, &MainWindow::stylesheetChanged,
549  options_button, [=](QString style)
550  {
551  options_button->setIcon( LoadSvgIcon(":/resources/svg/settings_cog.svg", style));
552  });
553  }
554  }
555  }
556  else if (message_parser)
557  {
558  _message_parser_factory.insert( std::make_pair(plugin_name, message_parser ) );
559  }
560  else if (streamer)
561  {
562  qDebug() << filename << ": is a DataStreamer plugin";
563  if (!_test_option && streamer->isDebugPlugin())
564  {
565  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
566  }
567  else
568  {
569  _data_streamer.insert(std::make_pair(plugin_name, streamer));
570 
571  streamer->setAvailableParsers( &_message_parser_factory );
572 
573  connect(streamer, &DataStreamer::closed, this,
574  [this]() { this->stopStreamingPlugin(); } );
575 
576  connect(streamer, &DataStreamer::clearBuffers,
578 
579  connect(streamer, &DataStreamer::dataReceived,
580  _animated_streaming_movie, [this]()
581  {
582  _animated_streaming_movie->start();
583  _animated_streaming_timer->start(500);
584  });
585  }
586  }
587  }
588  else
589  {
590  if (pluginLoader.errorString().contains("is not an ELF object") == false)
591  {
592  qDebug() << filename << ": " << pluginLoader.errorString();
593  }
594  }
595  }
596  if( !_data_streamer.empty() )
597  {
598  QSignalBlocker block(ui->comboStreaming);
599  ui->comboStreaming->setEnabled(true);
600  ui->buttonStreamingStart->setEnabled(true);
601 
602  for(const auto& it: _data_streamer)
603  {
604  if( ui->comboStreaming->findText(it.first) == -1){
605  ui->comboStreaming->addItem(it.first);
606  }
607  }
608 
609  // remember the previous one
610  QSettings settings;
611  QString streaming_name = settings.value("MainWindow.previousStreamingPlugin",
612  ui->comboStreaming->itemText(0)).toString();
613 
614  auto streamer_it = _data_streamer.find(streaming_name);
615  if( streamer_it == _data_streamer.end() )
616  {
617  streamer_it = _data_streamer.begin();
618  streaming_name = streamer_it->first;
619  }
620 
621  ui->comboStreaming->setCurrentText(streaming_name);
622 
623  bool contains_options = !streamer_it->second->availableActions().empty();
624  ui->buttonStreamingOptions->setEnabled(contains_options);
625  }
626  qDebug() << "Number of plugins loaded: " << loaded_count << "\n";
627  return loaded_out;
628 }
629 
631 {
632  PlotDataMapRef datamap;
633 
634  static int count = 0;
635  size_t SIZE = 1000;
636  QElapsedTimer timer;
637  timer.start();
638  QStringList words_list;
639  words_list << "world/siam"
640  << "world/tre"
641  << "walk/piccoli"
642  << "walk/porcellin"
643  << "fly/high/mai"
644  << "fly/high/nessun"
645  << "fly/low/ci"
646  << "fly/low/dividera"
647  << "data_1"
648  << "data_2"
649  << "data_3"
650  << "data_10";
651 
652  for (int i = 0; i < 100; i++)
653  {
654  words_list.append(QString("data_vect/%1").arg(count++));
655  }
656 
657  for (const QString& name : words_list)
658  {
659  double A = 6 * ((double)qrand() / (double)RAND_MAX) - 3;
660  double B = 3 * ((double)qrand() / (double)RAND_MAX);
661  double C = 3 * ((double)qrand() / (double)RAND_MAX);
662  double D = 20 * ((double)qrand() / (double)RAND_MAX);
663 
664  auto it = datamap.addNumeric(name.toStdString());
665  PlotData& plot = it->second;
666 
667  double t = 0;
668  for (unsigned indx = 0; indx < SIZE; indx++)
669  {
670  t += 0.01;
671  plot.pushBack(PlotData::Point(t + 35, A * sin(B * t + C) + D * t * 0.02));
672  }
673  }
674 
675  PlotData& sin_plot = datamap.addNumeric("_sin")->second;
676  PlotData& cos_plot = datamap.addNumeric("_cos")->second;
677 
678  double t = 0;
679  for (unsigned indx = 0; indx < SIZE; indx++)
680  {
681  t += 0.01;
682  sin_plot.pushBack(PlotData::Point(t + 20, sin(t * 0.4)));
683  cos_plot.pushBack(PlotData::Point(t + 20, cos(t * 0.4)));
684  }
685 
686  importPlotDataMap(datamap, true);
687 }
688 
690 {
691  QList<int> sizes = ui->mainSplitter->sizes();
692  int max_left_size = _curvelist_widget->maximumWidth();
693  int totalWidth = sizes[0] + sizes[1];
694 
695  // this is needed only once to restore the old size
696  static bool first = true;
697  if (sizes[0] != 0 && first)
698  {
699  first = false;
700  QSettings settings;
701  int splitter_width = settings.value("MainWindow.splitterWidth", 200).toInt();
702  auto sizes = ui->mainSplitter->sizes();
703  int tot_splitter_width = sizes[0] + sizes[1];
704  sizes[0] = splitter_width;
705  sizes[1] = tot_splitter_width - splitter_width;
706  ui->mainSplitter->setSizes(sizes);
707  return;
708  }
709 
710  if (sizes[0] > max_left_size)
711  {
712  sizes[0] = max_left_size;
713  sizes[1] = totalWidth - max_left_size;
714  ui->mainSplitter->setSizes(sizes);
715  }
716 }
717 
718 void MainWindow::resizeEvent(QResizeEvent*)
719 {
720  on_splitterMoved(0, 0);
721 }
722 
724 {
726 
728 
729  // TODO connect(plot, &PlotWidget::swapWidgetsRequested, this, &MainWindow::onSwapPlots);
730 
732 
733  connect(plot, &PlotWidget::curveListChanged, this, [this]() {
736  });
737 
738  connect(&_time_offset, SIGNAL(valueChanged(double)), plot, SLOT(on_changeTimeOffset(double)));
739 
740  connect(ui->pushButtonUseDateTime, &QPushButton::toggled, plot, &PlotWidget::on_changeDateTimeScale);
741 
743 
744  connect(plot, &PlotWidget::legendSizeChanged, this, [=](int point_size)
745  {
746  auto visitor = [=](PlotWidget* p){
747  if(plot != p) p->setLegendSize(point_size);
748  };
749  this->forEachWidget(visitor);
750  });
751 
753 
755  plot->on_changeDateTimeScale(ui->pushButtonUseDateTime->isChecked());
756  plot->activateGrid(ui->pushButtonActivateGrid->isChecked());
759 }
760 
761 void MainWindow::onPlotZoomChanged(PlotWidget* modified_plot, QRectF new_range)
762 {
763  if (ui->pushButtonLink->isChecked())
764  {
765  auto visitor = [=](PlotWidget* plot){
766  if(plot != modified_plot &&
767  !plot->isEmpty() &&
768  !plot->isXYPlot())
769  {
770  QRectF bound_act = plot->canvasBoundingRect();
771  bound_act.setLeft(new_range.left());
772  bound_act.setRight(new_range.right());
773  plot->setZoomRectangle(bound_act, false);
774  plot->on_zoomOutVertical_triggered(false);
775  plot->replot();
776  }
777  };
778  this->forEachWidget(visitor);
779  }
780 
782 }
783 
785 {
786  connect(docker, &PlotDocker::plotWidgetAdded, this, &MainWindow::onPlotAdded);
787 
789 
790  // TODO connect(matrix, &PlotMatrix::undoableChange, this, &MainWindow::onUndoableChange);
791 }
792 
793 QDomDocument MainWindow::xmlSaveState() const
794 {
795  QDomDocument doc;
796  QDomProcessingInstruction instr = doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
797 
798  doc.appendChild(instr);
799 
800  QDomElement root = doc.createElement("root");
801 
802  for (auto& it : TabbedPlotWidget::instances())
803  {
804  QDomElement tabbed_area = it.second->xmlSaveState(doc);
805  root.appendChild(tabbed_area);
806  }
807 
808  doc.appendChild(root);
809 
810  QDomElement relative_time = doc.createElement("use_relative_time_offset");
811  relative_time.setAttribute("enabled", ui->pushButtonRemoveTimeOffset->isChecked());
812  root.appendChild(relative_time);
813 
814  return doc;
815 }
816 
817 void MainWindow::checkAllCurvesFromLayout(const QDomElement& root)
818 {
819  std::set<std::string> curves;
820 
821  for (QDomElement tw = root.firstChildElement("tabbed_widget"); !tw.isNull(); tw = tw.nextSiblingElement("tabbed_"
822  "widget"))
823  {
824  for (QDomElement pm = tw.firstChildElement("plotmatrix"); !pm.isNull(); pm = pm.nextSiblingElement("plotmatrix"))
825  {
826  for (QDomElement pl = pm.firstChildElement("plot"); !pl.isNull(); pl = pl.nextSiblingElement("plot"))
827  {
828  QDomElement tran_elem = pl.firstChildElement("transform");
829  std::string trans = tran_elem.attribute("value").toStdString();
830  bool is_XY_plot = (trans == "XYPlot");
831 
832  for (QDomElement cv = pl.firstChildElement("curve"); !cv.isNull(); cv = cv.nextSiblingElement("curve"))
833  {
834  if (is_XY_plot)
835  {
836  curves.insert(cv.attribute("curve_x").toStdString());
837  curves.insert(cv.attribute("curve_y").toStdString());
838  }
839  else
840  {
841  curves.insert(cv.attribute("name").toStdString());
842  }
843  }
844  }
845  }
846  }
847 
848  std::vector<std::string> missing_curves;
849 
850  for (auto& curve_name : curves)
851  {
852  if (_mapped_plot_data.numeric.count(curve_name) == 0)
853  {
854  missing_curves.push_back(curve_name);
855  }
856  }
857  if (missing_curves.size() > 0)
858  {
859  QMessageBox msgBox(this);
860  msgBox.setWindowTitle("Warning");
861  msgBox.setText(tr("One or more timeseries in the layout haven't been loaded yet\n"
862  "What do you want to do?"));
863 
864  QPushButton* buttonRemove = msgBox.addButton(tr("Remove curves from plots"), QMessageBox::RejectRole);
865  QPushButton* buttonPlaceholder = msgBox.addButton(tr("Create empty placeholders"), QMessageBox::YesRole);
866  msgBox.setDefaultButton(buttonPlaceholder);
867  msgBox.exec();
868  if (msgBox.clickedButton() == buttonPlaceholder)
869  {
870  for (auto& name : missing_curves)
871  {
872  _curvelist_widget->addCurve(QString::fromStdString(name));
874  }
876  }
877  }
878 }
879 
880 bool MainWindow::xmlLoadState(QDomDocument state_document)
881 {
882  QDomElement root = state_document.namedItem("root").toElement();
883  if (root.isNull())
884  {
885  qWarning() << "No <root> element found at the top-level of the XML file!";
886  return false;
887  }
888 
889  size_t num_floating = 0;
890  std::map<QString, QDomElement> tabbed_widgets_with_name;
891 
892  for (QDomElement tw = root.firstChildElement("tabbed_widget");
893  tw.isNull() == false;
894  tw = tw.nextSiblingElement("tabbed_widget"))
895  {
896  if (tw.attribute("parent") != ("main_window"))
897  {
898  num_floating++;
899  }
900  tabbed_widgets_with_name[tw.attribute("name")] = tw;
901  }
902 
903  // add if missing
904  for (const auto& it : tabbed_widgets_with_name)
905  {
906  if (TabbedPlotWidget::instance(it.first) == nullptr)
907  {
908  // TODO createTabbedDialog(it.first, nullptr);
909  }
910  }
911 
912  // remove those which don't share list of names
913  for (const auto& it : TabbedPlotWidget::instances())
914  {
915  if (tabbed_widgets_with_name.count(it.first) == 0)
916  {
917  it.second->deleteLater();
918  }
919  }
920 
921  //-----------------------------------------------------
923  //-----------------------------------------------------
924 
925  for (QDomElement tw = root.firstChildElement("tabbed_widget");
926  tw.isNull() == false;
927  tw = tw.nextSiblingElement("tabbed_widget"))
928  {
929  TabbedPlotWidget* tabwidget = TabbedPlotWidget::instance(tw.attribute("name"));
930  tabwidget->xmlLoadState(tw);
931  }
932 
933  QDomElement relative_time = root.firstChildElement("use_relative_time_offset");
934  if (!relative_time.isNull())
935  {
936  bool remove_offset = (relative_time.attribute("enabled") == QString("1"));
937  ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
938  }
939  return true;
940 }
941 
942 void MainWindow::onDeleteMultipleCurves(const std::vector<std::string>& curve_names)
943 {
944  for (const auto& curve_name : curve_names)
945  {
946  auto plot_curve = _mapped_plot_data.numeric.find(curve_name);
947  if (plot_curve == _mapped_plot_data.numeric.end())
948  {
949  continue;
950  }
951 
952  emit dataSourceRemoved(curve_name);
953  _mapped_plot_data.numeric.erase(plot_curve);
954 
955  auto custom_it = _custom_plots.find(curve_name);
956  if (custom_it != _custom_plots.end())
957  {
958  _custom_plots.erase(custom_it);
959  }
960 
961  _curvelist_widget->removeCurve(curve_name);
962  }
963 
964  forEachWidget([](PlotWidget* plot) { plot->replot(); });
965 }
966 
967 void MainWindow::updateRecentDataMenu(QStringList new_filenames)
968 {
969  QMenu* menu = _recent_data_files;
970 
971  QAction* separator = nullptr;
972  QStringList prev_filenames;
973  for (QAction* action : menu->actions())
974  {
975  if (action->isSeparator())
976  {
977  separator = action;
978  break;
979  }
980  if (new_filenames.contains(action->text()) == false)
981  {
982  prev_filenames.push_back(action->text());
983  }
984  menu->removeAction(action);
985  }
986 
987  new_filenames.append(prev_filenames);
988  while (new_filenames.size() > 10)
989  {
990  new_filenames.removeLast();
991  }
992 
993  for (const auto& filename : new_filenames)
994  {
995  QAction* action = new QAction(filename, nullptr);
996  connect(action, &QAction::triggered, this, [this, filename] { loadDataFromFiles({ filename }); });
997  menu->insertAction(separator, action);
998  }
999 
1000  QSettings settings;
1001  settings.setValue("MainWindow.recentlyLoadedDatafile", new_filenames);
1002  menu->setEnabled(new_filenames.size() > 0);
1003 }
1004 
1005 void MainWindow::updateRecentLayoutMenu(QStringList new_filenames)
1006 {
1007  QMenu* menu = _recent_layout_files;
1008 
1009  QAction* separator = nullptr;
1010  QStringList prev_filenames;
1011  for (QAction* action : menu->actions())
1012  {
1013  if (action->isSeparator())
1014  {
1015  separator = action;
1016  break;
1017  }
1018  if (new_filenames.contains(action->text()) == false)
1019  {
1020  prev_filenames.push_back(action->text());
1021  }
1022  menu->removeAction(action);
1023  }
1024 
1025  new_filenames.append(prev_filenames);
1026  while (new_filenames.size() > 10)
1027  {
1028  new_filenames.removeLast();
1029  }
1030 
1031  for (const auto& filename : new_filenames)
1032  {
1033  QAction* action = new QAction(filename, nullptr);
1034  connect(action, &QAction::triggered, this, [this, filename] {
1035  if (this->loadLayoutFromFile(filename))
1036  {
1037  updateRecentLayoutMenu({ filename });
1038  }
1039  });
1040  menu->insertAction(separator, action);
1041  }
1042 
1043  QSettings settings;
1044  settings.setValue("MainWindow.recentlyLoadedLayout", new_filenames);
1045  menu->setEnabled(new_filenames.size() > 0);
1046 }
1047 
1049 {
1050  forEachWidget([](PlotWidget* plot) { plot->removeAllCurves(); });
1051 
1052  _mapped_plot_data.numeric.clear();
1054  _custom_plots.clear();
1056  _loaded_datafiles.clear();
1057 
1058  bool stopped = false;
1059 
1060  for(int idx = 0; idx < ui->layoutPublishers->count(); idx++)
1061  {
1062  QLayoutItem * item = ui->layoutPublishers->itemAt(idx);
1063  if(dynamic_cast<QWidgetItem *>(item))
1064  {
1065  if( auto checkbox = dynamic_cast<QCheckBox *>(item->widget())) {
1066  if( checkbox->isChecked() ) {
1067  checkbox->setChecked(false);
1068  stopped = true;
1069  }
1070  }
1071  }
1072  }
1073 
1074  if (stopped)
1075  {
1076  QMessageBox::warning(this, "State publishers stopped",
1077  "All the state publishers have been stopped because old data has been deleted.");
1078  }
1079 }
1080 
1081 template <typename T>
1082 void importPlotDataMapHelper(std::unordered_map<std::string, T>& source,
1083  std::unordered_map<std::string, T>& destination, bool delete_older)
1084 {
1085  for (auto& it : source)
1086  {
1087  const std::string& name = it.first;
1088  T& source_plot = it.second;
1089  auto plot_with_same_name = destination.find(name);
1090 
1091  // this is a new plot
1092  if (plot_with_same_name == destination.end())
1093  {
1094  plot_with_same_name =
1095  destination.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(name)).first;
1096  }
1097  T& destination_plot = plot_with_same_name->second;
1098 
1099  if (delete_older)
1100  {
1101  double max_range_x = destination_plot.maximumRangeX();
1102  destination_plot.swapData(source_plot);
1103  destination_plot.setMaximumRangeX(max_range_x); // just in case
1104  }
1105  else
1106  {
1107  for (size_t i = 0; i < source_plot.size(); i++)
1108  {
1109  destination_plot.pushBack(source_plot.at(i));
1110  }
1111  }
1112  source_plot.clear();
1113  }
1114 }
1115 
1116 void MainWindow::importPlotDataMap(PlotDataMapRef& new_data, bool remove_old)
1117 {
1118  if (new_data.user_defined.empty() && new_data.numeric.empty())
1119  {
1120  return;
1121  }
1122 
1123  if (remove_old)
1124  {
1125  std::vector<std::string> old_plots_to_delete;
1126 
1127  for (auto& it : _mapped_plot_data.numeric)
1128  {
1129  // timeseries in old but not in new
1130  if (new_data.numeric.count(it.first) == 0)
1131  {
1132  old_plots_to_delete.push_back(it.first);
1133  }
1134  }
1135 
1136  if (!old_plots_to_delete.empty())
1137  {
1138  QMessageBox::StandardButton reply;
1139  reply = QMessageBox::question(this, tr("Warning"), tr("Do you want to remove the previously loaded data?\n"),
1140  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
1141  if (reply == QMessageBox::Yes)
1142  {
1143  onDeleteMultipleCurves(old_plots_to_delete);
1144  }
1145  }
1146  }
1147 
1148  bool curvelist_modified = false;
1149  for (auto& it : new_data.numeric)
1150  {
1151  const std::string& name = it.first;
1152  if (it.second.size() > 0 && _mapped_plot_data.numeric.count(name) == 0)
1153  {
1154  _curvelist_widget->addCurve(QString::fromStdString(name));
1155  curvelist_modified = true;
1156  }
1157  }
1158 
1161 
1162  if (curvelist_modified)
1163  {
1165  }
1166 
1167 }
1168 
1170 {
1171  return !ui->buttonStreamingPause->isChecked() &&
1173 }
1174 
1175 bool MainWindow::loadDataFromFiles(QStringList filenames)
1176 {
1177  if (filenames.size() > 1)
1178  {
1179  QMessageBox msgbox;
1180  msgbox.setWindowTitle("Loading multiple files");
1181  msgbox.setText("You are loading multiple files at once. A prefix will be automatically added to the name of the "
1182  "timeseries.\n\n"
1183  "This is an experimental feature. Publishers will not work as you may expect.");
1184  msgbox.addButton(QMessageBox::Ok);
1185  // QCheckBox *cb = new QCheckBox("Don't show this again");
1186  // msgbox.setCheckBox(cb);
1187  // connect(cb, &QCheckBox::stateChanged, this, [this, cb , &show_me]() { show_me = !cb->isChecked(); } );
1188  msgbox.exec();
1189  }
1190 
1191  char prefix_ch = 'A';
1192  QStringList loaded_filenames;
1193 
1194  for (int i = 0; i < filenames.size(); i++)
1195  {
1196  FileLoadInfo info;
1197  info.filename = filenames[i];
1198  if (filenames.size() > 1)
1199  {
1200  info.prefix = prefix_ch;
1201  }
1202 
1203  if (loadDataFromFile(info))
1204  {
1205  loaded_filenames.push_back(filenames[i]);
1206  prefix_ch++;
1207  }
1208  }
1209  if (loaded_filenames.size() > 0)
1210  {
1211  updateRecentDataMenu(loaded_filenames);
1212  return true;
1213  }
1214  return false;
1215 }
1216 
1218 {
1219  ui->pushButtonPlay->setChecked(false);
1220 
1221  const QString extension = QFileInfo(info.filename).suffix().toLower();
1222 
1223  typedef std::map<QString, DataLoaderPtr>::iterator MapIterator;
1224 
1225  std::vector<MapIterator> compatible_loaders;
1226 
1227  for (auto it = _data_loader.begin(); it != _data_loader.end(); ++it)
1228  {
1229  DataLoaderPtr data_loader = it->second;
1230  std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
1231 
1232  for (auto& ext : extensions)
1233  {
1234  if (extension == QString(ext).toLower())
1235  {
1236  compatible_loaders.push_back(it);
1237  break;
1238  }
1239  }
1240  }
1241 
1242  DataLoaderPtr dataloader;
1243 
1244  if (compatible_loaders.size() == 1)
1245  {
1246  dataloader = compatible_loaders.front()->second;
1247  }
1248  else
1249  {
1250  static QString last_plugin_name_used;
1251 
1252  QStringList names;
1253  for (auto& cl : compatible_loaders)
1254  {
1255  const auto& name = cl->first;
1256 
1257  if (name == last_plugin_name_used)
1258  {
1259  names.push_front(name);
1260  }
1261  else
1262  {
1263  names.push_back(name);
1264  }
1265  }
1266 
1267  bool ok;
1268  QString plugin_name = QInputDialog::getItem(this, tr("QInputDialog::getItem()"), tr("Select the loader to use:"),
1269  names, 0, false, &ok);
1270  if (ok && !plugin_name.isEmpty())
1271  {
1272  dataloader = _data_loader[plugin_name];
1273  last_plugin_name_used = plugin_name;
1274  }
1275  }
1276 
1277  if (dataloader)
1278  {
1279  QFile file(info.filename);
1280 
1281  if (!file.open(QFile::ReadOnly | QFile::Text))
1282  {
1283  QMessageBox::warning(this, tr("Datafile"),
1284  tr("Cannot read file %1:\n%2.").arg(info.filename).arg(file.errorString()));
1285  return false;
1286  }
1287  file.close();
1288 
1289  try
1290  {
1291  PlotDataMapRef mapped_data;
1292  FileLoadInfo new_info = info;
1293 
1294  if (dataloader->readDataFromFile(&new_info, mapped_data))
1295  {
1296  AddPrefixToPlotData(info.prefix.toStdString(), mapped_data.numeric);
1297 
1298  importPlotDataMap(mapped_data, true);
1299 
1300  QDomElement plugin_elem = dataloader->xmlSaveState(new_info.plugin_config);
1301  new_info.plugin_config.appendChild(plugin_elem);
1302 
1303  bool duplicate = false;
1304 
1305  // substitute an old item of _loaded_datafiles or push_back another item.
1306  for (auto& prev_loaded : _loaded_datafiles)
1307  {
1308  if (prev_loaded.filename == new_info.filename && prev_loaded.prefix == new_info.prefix)
1309  {
1310  prev_loaded = new_info;
1311  duplicate = true;
1312  break;
1313  }
1314  }
1315 
1316  if (!duplicate)
1317  {
1318  _loaded_datafiles.push_back(new_info);
1319  }
1320  }
1321  }
1322  catch (std::exception& ex)
1323  {
1324  QMessageBox::warning(
1325  this, tr("Exception from the plugin"),
1326  tr("The plugin [%1] thrown the following exception: \n\n %3\n").arg(dataloader->name()).arg(ex.what()));
1327  return false;
1328  }
1329  }
1330  else
1331  {
1332  QMessageBox::warning(this, tr("Error"),
1333  tr("Cannot read files with extension %1.\n No plugin can handle that!\n").arg(info.filename));
1334  }
1336  updateDataAndReplot(true);
1337  ui->timeSlider->setRealValue(ui->timeSlider->getMinimum());
1338 
1339  return true;
1340 }
1341 
1342 
1344 {
1346  {
1347  paused = true;
1348  }
1349 
1350  ui->pushButtonRemoveTimeOffset->setEnabled(paused);
1351  ui->widgetPlay->setEnabled(paused);
1352 
1353  if (!paused && ui->pushButtonPlay->isChecked())
1354  {
1355  ui->pushButtonPlay->setChecked(false);
1356  }
1357 
1358  forEachWidget([&](PlotWidget* plot) {
1359  plot->enableTracker(paused);
1360  plot->setZoomEnabled(paused);
1361  });
1362 
1363  if (!paused)
1364  {
1365  _replot_timer->start();
1366  updateTimeOffset();
1367  }
1368  else
1369  {
1370  _replot_timer->stop();
1371  updateDataAndReplot(true);
1372  onUndoableChange();
1373  }
1374 }
1375 
1377 {
1379  {
1380  bool prev_state = ui->buttonStreamingPause->isChecked();
1381  ui->buttonStreamingPause->setChecked(!prev_state);
1382  }
1383 }
1384 
1385 
1387 {
1388  ui->comboStreaming->setEnabled(true);
1389  ui->buttonStreamingStart->setText("Start");
1390  ui->buttonStreamingPause->setEnabled(false);
1391  ui->labelStreamingAnimation->setHidden(true);
1392 
1393  // force the cleanups typically done in on_buttonStreamingPause_toggled
1394  if( ui->buttonStreamingPause->isChecked() )
1395  {
1396  // Will call on_buttonStreamingPause_toggled
1397  ui->buttonStreamingPause->setChecked(false);
1398  }
1399  else{
1400  // call it manually
1402  }
1403 
1404  if( _active_streamer_plugin ) {
1405  _active_streamer_plugin->shutdown();
1406  _active_streamer_plugin = nullptr;
1407  }
1408 
1409  if (!_mapped_plot_data.numeric.empty())
1410  {
1411  ui->actionDeleteAllData->setToolTip("");
1412  }
1413 
1414  // reset max range.
1415  for (auto& it : _mapped_plot_data.numeric)
1416  {
1417  it.second.setMaximumRangeX(std::numeric_limits<double>::max());
1418  }
1419  for (auto& it : _mapped_plot_data.user_defined)
1420  {
1421  it.second.setMaximumRangeX(std::numeric_limits<double>::max());
1422  }
1423 }
1424 
1425 void MainWindow::startStreamingPlugin(QString streamer_name)
1426 {
1428  {
1429  _active_streamer_plugin->shutdown();
1430  _active_streamer_plugin = nullptr;
1431  }
1432 
1433  if (_data_streamer.empty())
1434  {
1435  qDebug() << "Error, no streamer loaded";
1436  return;
1437  }
1438 
1439  auto it = _data_streamer.find(streamer_name);
1440  if (it != _data_streamer.end())
1441  {
1442  _active_streamer_plugin = it->second;
1443  }
1444  else
1445  {
1446  qDebug() << "Error. The streamer " << streamer_name << " can't be loaded";
1447  _active_streamer_plugin = nullptr;
1448  return;
1449  }
1450 
1451  bool started = false;
1452  try
1453  {
1454  // TODO data sources (argument to _active_streamer_plugin->start()
1455  started = _active_streamer_plugin && _active_streamer_plugin->start(nullptr);
1456  }
1457  catch (std::runtime_error& err)
1458  {
1459  QMessageBox::warning(this, tr("Exception from the plugin"),
1460  tr("The plugin thrown the following exception: \n\n %1\n").arg(err.what()));
1461  _active_streamer_plugin = nullptr;
1462  return;
1463  }
1464  if (started)
1465  {
1466  {
1467  std::lock_guard<std::mutex> lock(_active_streamer_plugin->mutex());
1468  importPlotDataMap(_active_streamer_plugin->dataMap(), true);
1469  }
1470 
1471  ui->actionClearBuffer->setEnabled(true);
1472  ui->actionDeleteAllData->setToolTip("Stop streaming to be able to delete the data");
1473 
1474  ui->buttonStreamingStart->setText("Stop");
1475  ui->buttonStreamingPause->setEnabled(true);
1476  ui->buttonStreamingPause->setChecked(false);
1477  ui->comboStreaming->setEnabled(false);
1478  ui->labelStreamingAnimation->setHidden(false);
1479 
1480  // force start
1482  // this will force the update the max buffer size values
1483  on_streamingSpinBox_valueChanged(ui->streamingSpinBox->value());
1484  }
1485  else
1486  {
1487  QSignalBlocker block( ui->buttonStreamingStart );
1488  ui->buttonStreamingStart->setChecked(false);
1489  qDebug() << "Failed to launch the streamer";
1490  _active_streamer_plugin = nullptr;
1491  }
1492 }
1493 
1494 void MainWindow::loadStyleSheet(QString file_path)
1495 {
1496  QFile styleFile(file_path);
1497  styleFile.open(QFile::ReadOnly);
1498  try{
1499  QString theme = SetApplicationStyleSheet( styleFile.readAll() );
1500 
1501  forEachWidget([&](PlotWidget* plot) {
1502  plot->replot();
1503  });
1504 
1505  emit stylesheetChanged(theme);
1506  }
1507  catch( std::runtime_error& err )
1508  {
1509  QMessageBox::warning(this, tr("Error loading StyleSheet"), tr(err.what()));
1510  }
1511 }
1512 
1513 
1515 {
1516  ui->pushButtonLoadDatafile->setIcon(LoadSvgIcon(":/resources/svg/import.svg", theme));
1517  ui->buttonStreamingPause->setIcon(LoadSvgIcon(":/resources/svg/pause.svg", theme));
1518 
1519  ui->buttonRecentData->setIcon(LoadSvgIcon(":/resources/svg/right-arrow.svg", theme));
1520  ui->buttonRecentLayout->setIcon(LoadSvgIcon(":/resources/svg/right-arrow.svg", theme));
1521 
1522  ui->pushButtonZoomOut->setIcon(LoadSvgIcon(":/resources/svg/zoom_max.svg", theme));
1523  ui->playbackLoop->setIcon(LoadSvgIcon(":/resources/svg/loop.svg", theme));
1524  ui->pushButtonPlay->setIcon(LoadSvgIcon(":/resources/svg/play_arrow.svg", theme));
1525  ui->pushButtonUseDateTime->setIcon(LoadSvgIcon(":/resources/svg/datetime.svg", theme));
1526  ui->pushButtonActivateGrid->setIcon(LoadSvgIcon(":/resources/svg/grid.svg", theme));
1527  ui->pushButtonRatio->setIcon(LoadSvgIcon(":/resources/svg/ratio.svg", theme));
1528 
1529  ui->pushButtonLoadLayout->setIcon(LoadSvgIcon(":/resources/svg/import.svg", theme));
1530  ui->pushButtonSaveLayout->setIcon(LoadSvgIcon(":/resources/svg/export.svg", theme));
1531 
1532  ui->pushButtonLink->setIcon(LoadSvgIcon(":/resources/svg/link.svg", theme));
1533  ui->pushButtonRemoveTimeOffset->setIcon(LoadSvgIcon(":/resources/svg/t0.svg", theme));
1534  ui->pushButtonLegend->setIcon(LoadSvgIcon(":/resources/svg/legend.svg", theme));
1535 
1536  ui->buttonStreamingOptions->setIcon(LoadSvgIcon(":/resources/svg/settings_cog.svg", theme));
1537 }
1538 
1539 void MainWindow::loadPluginState(const QDomElement& root)
1540 {
1541  QDomElement plugins = root.firstChildElement("Plugins");
1542 
1543  for (QDomElement plugin_elem = plugins.firstChildElement(); plugin_elem.isNull() == false;
1544  plugin_elem = plugin_elem.nextSiblingElement())
1545  {
1546  const QString plugin_name = plugin_elem.attribute("ID");
1547 
1548  if (plugin_elem.nodeName() != "plugin" || plugin_name.isEmpty())
1549  {
1550  QMessageBox::warning(this, tr("Error loading Plugin State from Layout"),
1551  tr("The method xmlSaveState() must return a node like this <plugin ID=\"PluginName\" "));
1552  }
1553 
1554  if (_data_loader.find(plugin_name) != _data_loader.end())
1555  {
1556  _data_loader[plugin_name]->xmlLoadState(plugin_elem);
1557  }
1558  if (_data_streamer.find(plugin_name) != _data_streamer.end())
1559  {
1560  _data_streamer[plugin_name]->xmlLoadState(plugin_elem);
1561  }
1562  if (_state_publisher.find(plugin_name) != _state_publisher.end())
1563  {
1564  StatePublisherPtr publisher = _state_publisher[plugin_name];
1565  publisher->xmlLoadState(plugin_elem);
1566 
1567  if (_autostart_publishers && plugin_elem.attribute("status") == "active")
1568  {
1569  publisher->setEnabled(true);
1570  }
1571  }
1572  }
1573 }
1574 
1575 QDomElement MainWindow::savePluginState(QDomDocument& doc)
1576 {
1577  QDomElement list_plugins = doc.createElement("Plugins");
1578 
1579  auto CheckValidFormat = [this](const QString& expected_name, const QDomElement& elem) {
1580  if (elem.nodeName() != "plugin" || elem.attribute("ID") != expected_name)
1581  {
1582  QMessageBox::warning(this, tr("Error saving Plugin State to Layout"),
1583  tr("The method xmlSaveState() returned\n<plugin ID=\"%1\">\ninstead of\n<plugin ID=\"%2\">")
1584  .arg(elem.attribute("ID"))
1585  .arg(expected_name));
1586  }
1587  };
1588 
1589  for (auto& it : _data_loader)
1590  {
1591  const DataLoaderPtr dataloader = it.second;
1592  QDomElement plugin_elem = dataloader->xmlSaveState(doc);
1593  if (!plugin_elem.isNull())
1594  {
1595  list_plugins.appendChild(plugin_elem);
1596  CheckValidFormat(it.first, plugin_elem);
1597  }
1598  }
1599 
1600  for (auto& it : _data_streamer)
1601  {
1602  const DataStreamerPtr datastreamer = it.second;
1603  QDomElement plugin_elem = datastreamer->xmlSaveState(doc);
1604  if (!plugin_elem.isNull())
1605  {
1606  list_plugins.appendChild(plugin_elem);
1607  CheckValidFormat(it.first, plugin_elem);
1608  }
1609  }
1610 
1611  for (auto& it : _state_publisher)
1612  {
1613  const StatePublisherPtr state_publisher = it.second;
1614  QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
1615  if (!plugin_elem.isNull())
1616  {
1617  list_plugins.appendChild(plugin_elem);
1618  CheckValidFormat(it.first, plugin_elem);
1619  }
1620 
1621  plugin_elem.setAttribute("status", state_publisher->enabled() ? "active" : "idle");
1622  }
1623  return list_plugins;
1624 }
1625 
1626 std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
1627 {
1628  // find min max time
1629  double min_time = std::numeric_limits<double>::max();
1630  double max_time = -std::numeric_limits<double>::max();
1631  int max_steps = 0;
1632 
1633  forEachWidget([&](PlotWidget* widget) {
1634  for (auto& it : widget->curveList())
1635  {
1636  const auto& curve_name = it.src_name;
1637 
1638  auto plot_it = _mapped_plot_data.numeric.find(curve_name);
1639  if (plot_it == _mapped_plot_data.numeric.end())
1640  {
1641  continue; // FIXME?
1642  }
1643  const auto& data = plot_it->second;
1644  if (data.size() >= 1)
1645  {
1646  const double t0 = data.front().x;
1647  const double t1 = data.back().x;
1648  min_time = std::min(min_time, t0);
1649  max_time = std::max(max_time, t1);
1650  max_steps = std::max(max_steps, (int)data.size());
1651  }
1652  }
1653  });
1654 
1655  // needed if all the plots are empty
1656  if (max_steps == 0 || max_time < min_time)
1657  {
1658  for (const auto& it : _mapped_plot_data.numeric)
1659  {
1660  const PlotData& data = it.second;
1661  if (data.size() >= 1)
1662  {
1663  const double t0 = data.front().x;
1664  const double t1 = data.back().x;
1665  min_time = std::min(min_time, t0);
1666  max_time = std::max(max_time, t1);
1667  max_steps = std::max(max_steps, (int)data.size());
1668  }
1669  }
1670  }
1671 
1672  // last opportunity. Everything else failed
1673  if (max_steps == 0 || max_time < min_time)
1674  {
1675  min_time = 0.0;
1676  max_time = 1.0;
1677  max_steps = 1;
1678  }
1679  return std::tuple<double, double, int>(min_time, max_time, max_steps);
1680 }
1681 
1682 static const QString LAYOUT_VERSION = "2.3.8";
1683 
1684 bool MainWindow::loadLayoutFromFile(QString filename)
1685 {
1686  QSettings settings;
1687 
1688  QFile file(filename);
1689  if (!file.open(QFile::ReadOnly | QFile::Text))
1690  {
1691  QMessageBox::warning(this, tr("Layout"), tr("Cannot read file %1:\n%2.").arg(filename).arg(file.errorString()));
1692  return false;
1693  }
1694 
1695  QString errorStr;
1696  int errorLine, errorColumn;
1697 
1698  QDomDocument domDocument;
1699 
1700  if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn))
1701  {
1702  QMessageBox::information(window(), tr("XML Layout"),
1703  tr("Parse error at line %1:\n%2").arg(errorLine).arg(errorStr));
1704  return false;
1705  }
1706 
1707  //-------------------------------------------------
1708  // refresh plugins
1709  QDomElement root = domDocument.namedItem("root").toElement();
1710 
1711  if (!root.hasAttribute("version") || root.attribute("version") != LAYOUT_VERSION)
1712  {
1713  QMessageBox msgBox(this);
1714  msgBox.setWindowTitle("Obsolate Layout version");
1715  msgBox.setText(tr("This Layout ID is not supported [%1].\nThis version of PlotJuggler use Layout ID [%2]")
1716  .arg(root.attribute("version"))
1717  .arg(LAYOUT_VERSION));
1718 
1719  msgBox.setStandardButtons(QMessageBox::Abort);
1720  QPushButton* continueButton = msgBox.addButton(tr("Continue anyway"), QMessageBox::ActionRole);
1721  msgBox.setDefaultButton(continueButton);
1722 
1723  int ret = msgBox.exec();
1724  if (ret == QMessageBox::Abort)
1725  {
1726  return false;
1727  }
1728  }
1729 
1730  loadPluginState(root);
1731  //-------------------------------------------------
1732  QDomElement previously_loaded_datafile = root.firstChildElement("previouslyLoaded_Datafiles");
1733 
1734  QDomElement datafile_elem = previously_loaded_datafile.firstChildElement("fileInfo");
1735  while (!datafile_elem.isNull())
1736  {
1737  FileLoadInfo info;
1738  info.filename = datafile_elem.attribute("filename");
1739  info.prefix = datafile_elem.attribute("prefix");
1740 
1741  QDomElement datasources_elem = datafile_elem.firstChildElement("selected_datasources");
1742  QString topics_list = datasources_elem.attribute("value");
1743  info.selected_datasources = topics_list.split(";", QString::SkipEmptyParts);
1744 
1745  auto plugin_elem = datafile_elem.firstChildElement("plugin");
1746  info.plugin_config.appendChild(info.plugin_config.importNode(plugin_elem, true));
1747 
1748  loadDataFromFile(info);
1749  datafile_elem = datafile_elem.nextSiblingElement("fileInfo");
1750  }
1751 
1752  QDomElement previousl_streamer = root.firstChildElement("previouslyLoaded_Streamer");
1753  if (!previousl_streamer.isNull())
1754  {
1755  QString streamer_name = previousl_streamer.attribute("name");
1756 
1757  QMessageBox msgBox(this);
1758  msgBox.setWindowTitle("Start Streaming?");
1759  msgBox.setText(tr("Start the previously used streaming plugin?\n\n %1 \n\n").arg(streamer_name));
1760  QPushButton* yes = msgBox.addButton(tr("Yes"), QMessageBox::YesRole);
1761  QPushButton* no = msgBox.addButton(tr("No"), QMessageBox::RejectRole);
1762  msgBox.setDefaultButton(yes);
1763  msgBox.exec();
1764 
1765  if (msgBox.clickedButton() == yes)
1766  {
1767  if (_data_streamer.count(streamer_name) != 0)
1768  {
1769  startStreamingPlugin(streamer_name);
1770  }
1771  else
1772  {
1773  QMessageBox::warning(this, tr("Error Loading Streamer"),
1774  tr("The streamer named %1 can not be loaded.").arg(streamer_name));
1775  }
1776  }
1777  }
1778  //-------------------------------------------------
1779  // autostart_publishers
1780  QDomElement plugins = root.firstChildElement("Plugins");
1781 
1782  if (!plugins.isNull() && _autostart_publishers)
1783  {
1784  for (QDomElement plugin_elem = plugins.firstChildElement(); plugin_elem.isNull() == false;
1785  plugin_elem = plugin_elem.nextSiblingElement())
1786  {
1787  const QString plugin_name = plugin_elem.nodeName();
1788  if (_state_publisher.find(plugin_name) != _state_publisher.end())
1789  {
1790  StatePublisherPtr publisher = _state_publisher[plugin_name];
1791 
1792  if (plugin_elem.attribute("status") == "active")
1793  {
1794  publisher->setEnabled(true);
1795  }
1796  }
1797  }
1798  }
1799  //-------------------------------------------------
1800  auto custom_equations = root.firstChildElement("customMathEquations");
1801 
1802  try
1803  {
1804  if (!custom_equations.isNull())
1805  {
1806  for (QDomElement custom_eq = custom_equations.firstChildElement("snippet"); custom_eq.isNull() == false;
1807  custom_eq = custom_eq.nextSiblingElement("snippet"))
1808  {
1809  CustomPlotPtr new_custom_plot = CustomFunction::createFromXML(custom_eq);
1810  const auto& name = new_custom_plot->name();
1811  _custom_plots[name] = new_custom_plot;
1812  new_custom_plot->calculateAndAdd(_mapped_plot_data);
1813  _curvelist_widget->addCustom(QString::fromStdString(name));
1814  }
1816  }
1817  }
1818  catch (std::runtime_error& err)
1819  {
1820  QMessageBox::warning(this, tr("Exception"), tr("Failed to refresh a customMathEquation \n\n %1\n").arg(err.what()));
1821  }
1822 
1823  QByteArray snippets_saved_xml = settings.value("AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
1824 
1825  auto snippets_element = root.firstChildElement("snippets");
1826  if (!snippets_element.isNull())
1827  {
1828  auto snippets_previous = GetSnippetsFromXML(snippets_saved_xml);
1829  auto snippets_layout = GetSnippetsFromXML(snippets_element);
1830 
1831  bool snippets_are_different = false;
1832  for (const auto& snippet_it : snippets_layout)
1833  {
1834  auto prev_it = snippets_previous.find(snippet_it.first);
1835 
1836  if (prev_it == snippets_previous.end() ||
1837  prev_it->second.function != snippet_it.second.function ||
1838  prev_it->second.globalVars != snippet_it.second.globalVars)
1839  {
1840  snippets_are_different = true;
1841  break;
1842  }
1843  }
1844 
1845  if (snippets_are_different)
1846  {
1847  QMessageBox msgBox(this);
1848  msgBox.setWindowTitle("Overwrite custom transforms?");
1849  msgBox.setText("Your layour file contains a set of custom transforms different from "
1850  "the last one you used.\nant to load these transformations?");
1851  msgBox.addButton(QMessageBox::No);
1852  msgBox.addButton(QMessageBox::Yes);
1853  msgBox.setDefaultButton(QMessageBox::Yes);
1854 
1855  if (msgBox.exec() == QMessageBox::Yes)
1856  {
1857  for (const auto& snippet_it : snippets_layout)
1858  {
1859  snippets_previous[snippet_it.first] = snippet_it.second;
1860  }
1861  QDomDocument doc;
1862  auto snippets_root_element = ExportSnippets(snippets_previous, doc);
1863  doc.appendChild(snippets_root_element);
1864  settings.setValue("AddCustomPlotDialog.savedXML", doc.toByteArray(2));
1865  }
1866  }
1867  }
1868 
1870 
1871  xmlLoadState(domDocument);
1872 
1873  forEachWidget([&](PlotWidget* plot) { plot->zoomOut(false); });
1874 
1875  _undo_states.clear();
1876  _undo_states.push_back(domDocument);
1877  return true;
1878 }
1879 
1881 {
1882  this->setFocus();
1883 }
1884 
1885 
1886 void MainWindow::forEachWidget(std::function<void(PlotWidget*, PlotDocker*, int)> operation)
1887 {
1888  auto func = [&](QTabWidget* tabs) {
1889  for (int t = 0; t < tabs->count(); t++)
1890  {
1891  PlotDocker* matrix = dynamic_cast<PlotDocker*>(tabs->widget(t));
1892  if(!matrix){
1893  continue;
1894  }
1895 
1896  for (int index = 0; index < matrix->plotCount(); index++)
1897  {
1898  PlotWidget* plot = matrix->plotAt(index);
1899  operation(plot, matrix, index);
1900  }
1901  }
1902  };
1903 
1904  for (const auto& it : TabbedPlotWidget::instances())
1905  {
1906  func(it.second->tabWidget());
1907  }
1908 }
1909 
1910 void MainWindow::forEachWidget(std::function<void(PlotWidget*)> op)
1911 {
1912  forEachWidget([&](PlotWidget* plot, PlotDocker*, int) { op(plot); });
1913 }
1914 
1916 {
1917  auto range = calculateVisibleRangeX();
1918 
1919  ui->timeSlider->setLimits(std::get<0>(range), std::get<1>(range), std::get<2>(range));
1920 
1921  _tracker_time = std::max(_tracker_time, ui->timeSlider->getMinimum());
1922  _tracker_time = std::min(_tracker_time, ui->timeSlider->getMaximum());
1923 }
1924 
1926 {
1927  auto range = calculateVisibleRangeX();
1928  double min_time = std::get<0>(range);
1929 
1930  const bool remove_offset = ui->pushButtonRemoveTimeOffset->isChecked();
1931  if (remove_offset && min_time != std::numeric_limits<double>::max())
1932  {
1933  _time_offset.set(min_time);
1934  }
1935  else
1936  {
1937  _time_offset.set(0.0);
1938  }
1939 }
1940 
1941 void MainWindow::updateDataAndReplot(bool replot_hidden_tabs)
1942 {
1943  bool data_updated_by_streamer = false;
1944 
1946  {
1947  std::vector<QString> curvelist_added;
1948 
1949  {
1950  std::lock_guard<std::mutex> lock(_active_streamer_plugin->mutex());
1951  auto res = MoveData(_active_streamer_plugin->dataMap(), _mapped_plot_data);
1952  curvelist_added = res.first;
1953  data_updated_by_streamer = res.second;
1954  }
1955 
1956  for (const auto& str : curvelist_added)
1957  {
1959  }
1960 
1961  if (curvelist_added.size() > 0)
1962  {
1964  }
1965  }
1966 
1967  const bool is_streaming_active = isStreamingActive();
1968 
1969  for (auto& custom_it : _custom_plots)
1970  {
1971  auto* dst_plot = &_mapped_plot_data.numeric.at(custom_it.first);
1972  custom_it.second->calculate(_mapped_plot_data, dst_plot);
1973  }
1974 
1975  forEachWidget([](PlotWidget* plot) {
1976  plot->updateCurves();
1977  });
1978 
1979  if( data_updated_by_streamer )
1980  {
1981  _animated_streaming_movie->start();
1982  _animated_streaming_timer->start(300);
1983  }
1984 
1985  //--------------------------------
1986  // trigger again the execution of this callback if steaming == true
1987  if (is_streaming_active)
1988  {
1989  auto range = calculateVisibleRangeX();
1990  double max_time = std::get<1>(range);
1991  _tracker_time = max_time;
1992  onTrackerTimeUpdated(_tracker_time, false);
1993  }
1994  else
1995  {
1996  updateTimeOffset();
1997  updateTimeSlider();
1998  }
1999  //--------------------------------
2000  if( data_updated_by_streamer )
2001  {
2002  for (const auto& it : TabbedPlotWidget::instances())
2003  {
2004  if (replot_hidden_tabs)
2005  {
2006  QTabWidget* tabs = it.second->tabWidget();
2007  for (int index = 0; index < tabs->count(); index++)
2008  {
2009  PlotDocker* matrix = static_cast<PlotDocker*>(tabs->widget(index));
2010  matrix->zoomOut();
2011  }
2012  }
2013  else
2014  {
2015  PlotDocker* matrix = it.second->currentTab();
2016  matrix->zoomOut(); // includes replot
2017  }
2018  }
2019  }
2020  else{
2021  forEachWidget([](PlotWidget* plot) {
2022  plot->replot();
2023  });
2024  }
2025 }
2026 
2028 {
2029  double real_value = value;
2030 
2031  if (isStreamingActive() == false)
2032  {
2033  return;
2034  }
2035 
2036  for (auto& it : _mapped_plot_data.numeric)
2037  {
2038  it.second.setMaximumRangeX(real_value);
2039  }
2040 
2041  for (auto& it : _mapped_plot_data.user_defined)
2042  {
2043  it.second.setMaximumRangeX(real_value);
2044  }
2045 
2047  {
2048  _active_streamer_plugin->setMaximumRange(real_value);
2049  }
2050 }
2051 
2052 
2053 
2055 {
2056  this->close();
2057 }
2058 
2060 {
2061  updateTimeOffset();
2063 
2064  forEachWidget([](PlotWidget* plot) { plot->replot(); });
2065 
2066  if (this->signalsBlocked() == false){
2067  onUndoableChange();
2068  }
2069 }
2070 
2072 {
2073  QLineEdit* timeLine = ui->displayTime;
2074  const double relative_time = _tracker_time - _time_offset.get();
2075  if (ui->pushButtonUseDateTime->isChecked())
2076  {
2077  if (ui->pushButtonRemoveTimeOffset->isChecked())
2078  {
2079  QTime time = QTime::fromMSecsSinceStartOfDay(std::round(relative_time * 1000.0));
2080  timeLine->setText(time.toString("HH:mm::ss.zzz"));
2081  }
2082  else
2083  {
2084  QDateTime datetime = QDateTime::fromMSecsSinceEpoch(std::round(_tracker_time * 1000.0));
2085  timeLine->setText(datetime.toString("[yyyy MMM dd] HH:mm::ss.zzz"));
2086  }
2087  }
2088  else
2089  {
2090  timeLine->setText(QString::number(relative_time, 'f', 3));
2091  }
2092 
2093  QFontMetrics fm(timeLine->font());
2094  int width = fm.width(timeLine->text()) + 10;
2095  timeLine->setFixedWidth(std::max(100, width));
2096 }
2097 
2099 {
2100  forEachWidget([checked](PlotWidget* plot) {
2101  plot->activateGrid(checked);
2102  plot->replot();
2103  });
2104 }
2105 
2107 {
2108  forEachWidget([checked](PlotWidget* plot) {
2109  plot->setConstantRatioXY(checked);
2110  plot->replot();
2111  });
2112 }
2113 
2115 {
2116  if (checked)
2117  {
2118  _publish_timer->start();
2119  _prev_publish_time = QDateTime::currentDateTime();
2120  }
2121  else
2122  {
2123  _publish_timer->stop();
2124  }
2125 }
2126 
2128 {
2129  for (auto& it : _mapped_plot_data.numeric)
2130  {
2131  it.second.clear();
2132  }
2133 
2134  for (auto& it : _mapped_plot_data.user_defined)
2135  {
2136  it.second.clear();
2137  }
2138 
2139  for (auto& it : _custom_plots)
2140  {
2141  it.second->clear();
2142  }
2143 
2144  forEachWidget([](PlotWidget* plot) {
2145  plot->reloadPlotData();
2146  plot->replot();
2147  });
2148 }
2149 
2151 {
2153 }
2154 
2156 {
2158  {
2160  }
2161  else if (_tracker_param == CurveTracker::VALUE)
2162  {
2164  }
2166  {
2168  }
2169  ui->pushButtonTimeTracker->setIcon(_tracker_button_icons[_tracker_param]);
2170 
2171  forEachWidget([&](PlotWidget* plot) {
2172  plot->configureTracker(_tracker_param);
2173  plot->replot();
2174  });
2175 }
2176 
2177 void MainWindow::closeEvent(QCloseEvent* event)
2178 {
2179  _replot_timer->stop();
2180  _publish_timer->stop();
2181 
2183  {
2184  _active_streamer_plugin->shutdown();
2185  _active_streamer_plugin = nullptr;
2186  }
2187  QSettings settings;
2188  settings.setValue("MainWindow.geometry", saveGeometry());
2189  settings.setValue("MainWindow.state", saveState());
2190 
2191  settings.setValue("MainWindow.activateGrid", ui->pushButtonActivateGrid->isChecked());
2192  settings.setValue("MainWindow.removeTimeOffset", ui->pushButtonRemoveTimeOffset->isChecked());
2193  settings.setValue("MainWindow.dateTimeDisplay", ui->pushButtonUseDateTime->isChecked());
2194  settings.setValue("MainWindow.buttonLink", ui->pushButtonLink->isChecked());
2195  settings.setValue("MainWindow.buttonRatio", ui->pushButtonRatio->isChecked());
2196 
2197  settings.setValue("MainWindow.streamingBufferValue", ui->streamingSpinBox->value());
2198  settings.setValue("MainWindow.timeTrackerSetting", (int)_tracker_param);
2199  settings.setValue("MainWindow.splitterWidth", ui->mainSplitter->sizes()[0]);
2200 }
2201 
2202 void MainWindow::onAddCustomPlot(const std::string& plot_name)
2203 {
2204  ui->widgetStack->setCurrentIndex(1);
2205  _function_editor->setLinkedPlotName(QString::fromStdString(plot_name));
2207 }
2208 
2209 void MainWindow::onEditCustomPlot(const std::string& plot_name)
2210 {
2211  ui->widgetStack->setCurrentIndex(1);
2212  auto custom_it = _custom_plots.find(plot_name);
2213  if (custom_it == _custom_plots.end())
2214  {
2215  qWarning("failed to find custom equation");
2216  return;
2217  }
2218  _function_editor->editExistingPlot(custom_it->second);
2219 }
2220 
2221 void MainWindow::onRefreshCustomPlot(const std::string& plot_name)
2222 {
2223  try
2224  {
2225  auto custom_it = _custom_plots.find(plot_name);
2226  if (custom_it == _custom_plots.end())
2227  {
2228  qWarning("failed to find custom equation");
2229  return;
2230  }
2231  CustomPlotPtr ce = custom_it->second;
2232 
2233  ce->calculateAndAdd(_mapped_plot_data);
2234 
2236  updateDataAndReplot(true);
2237  }
2238  catch (const std::runtime_error& e)
2239  {
2240  QMessageBox::critical(this, "error", "Failed to refresh data : " + QString::fromStdString(e.what()));
2241  }
2242 }
2243 
2244 
2246 {
2247  qint64 delta_ms = (QDateTime::currentMSecsSinceEpoch() - _prev_publish_time.toMSecsSinceEpoch());
2248  _prev_publish_time = QDateTime::currentDateTime();
2249  delta_ms = std::max((qint64)_publish_timer->interval(), delta_ms);
2250 
2251  _tracker_time += delta_ms * 0.001 * ui->playbackRate->value();
2252  if (_tracker_time >= ui->timeSlider->getMaximum())
2253  {
2254  if (!ui->playbackLoop->isChecked())
2255  {
2256  ui->pushButtonPlay->setChecked(false);
2257  }
2258  _tracker_time = ui->timeSlider->getMinimum();
2259  }
2261  auto prev = ui->timeSlider->blockSignals(true);
2262  ui->timeSlider->setRealValue(_tracker_time);
2263  ui->timeSlider->blockSignals(prev);
2264 
2268 
2269  for (auto& it : _state_publisher)
2270  {
2271  it.second->play(_tracker_time);
2272  }
2273 
2274  forEachWidget([&](PlotWidget* plot) {
2275  plot->setTrackerPosition(_tracker_time);
2276  plot->replot();
2277  });
2278 }
2279 
2281 {
2282  const std::string& name = custom_plot->name();
2283 
2284  // clear already existing data first
2285  auto data_it = _mapped_plot_data.numeric.find(name);
2286  if (data_it != _mapped_plot_data.numeric.end())
2287  {
2288  data_it->second.clear();
2289  }
2290 
2291  try
2292  {
2293  custom_plot->calculateAndAdd(_mapped_plot_data);
2294  }
2295  catch (std::exception& ex)
2296  {
2297  QMessageBox::warning(this, tr("Warning"),
2298  tr("Failed to create the custom timeseries. Error:\n\n%1").arg(ex.what()));
2299 
2300  return;
2301  }
2302  ui->widgetStack->setCurrentIndex(0);
2304 
2305  // keep data for reference
2306  auto custom_it = _custom_plots.find(name);
2307  if (custom_it == _custom_plots.end())
2308  {
2309  _custom_plots.insert({ name, custom_plot });
2310  _curvelist_widget->addCustom( QString::fromStdString(name) );
2312  }
2313  else{
2314  custom_it->second = custom_plot;
2315  }
2316 
2318 
2319  // update plots
2320  forEachWidget([&](PlotWidget* plot) {
2321 
2322  const auto& curves = plot->curveList();
2323  auto it = std::find_if(curves.begin(), curves.end(),
2324  [&name](const PlotWidget::CurveInfo& info)
2325  {
2326  return info.src_name == name;
2327  });
2328 
2329  if (it != curves.end())
2330  {
2331  plot->updateCurves();
2332  plot->replot();
2333  }
2334  });
2335 }
2336 
2338 {
2339  QDesktopServices::openUrl(QUrl("https://github.com/facontidavide/PlotJuggler/issues"));
2340 }
2341 
2343 {
2344  QDesktopServices::openUrl(QUrl("https://twitter.com/intent/tweet?hashtags=PlotJuggler"));
2345 }
2346 
2348 {
2349  QDialog* dialog = new QDialog(this);
2350  auto ui = new Ui::AboutDialog();
2351  ui->setupUi(dialog);
2352 
2353  ui->label_version->setText(QApplication::applicationVersion());
2354  dialog->setAttribute(Qt::WA_DeleteOnClose);
2355 
2356  dialog->exec();
2357 }
2358 
2360 {
2361  QSettings settings;
2362 
2363  CheatsheetDialog *dialog = new CheatsheetDialog(this);
2364  dialog->restoreGeometry(settings.value("Cheatsheet.geometry").toByteArray());
2365  dialog->exec();
2366  settings.setValue("Cheatsheet.geometry", dialog->saveGeometry());
2367  dialog->deleteLater();
2368 }
2369 
2371 {
2372  QDialog* dialog = new QDialog(this);
2373  auto ui = new Ui::SupportDialog();
2374  ui->setupUi(dialog);
2375 
2376  dialog->setAttribute(Qt::WA_DeleteOnClose);
2377 
2378  dialog->exec();
2379 }
2380 
2381 /*
2382 void MainWindow::on_actionSaveAllPlotTabs_triggered()
2383 {
2384  QSettings settings;
2385  QString directory_path = settings.value("MainWindow.saveAllPlotTabs", QDir::currentPath()).toString();
2386  // Get destination folder
2387  QFileDialog saveDialog(this);
2388  saveDialog.setDirectory(directory_path);
2389  saveDialog.setFileMode(QFileDialog::FileMode::Directory);
2390  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
2391  saveDialog.exec();
2392 
2393  uint image_number = 1;
2394  if (saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
2395  {
2396  // Save Plots
2397  QString directory = saveDialog.selectedFiles().first();
2398  settings.setValue("MainWindow.saveAllPlotTabs", directory);
2399 
2400  QStringList file_names;
2401  QStringList existing_files;
2402  QDateTime current_date_time(QDateTime::currentDateTime());
2403  QString current_date_time_name(current_date_time.toString("yyyy-MM-dd_HH-mm-ss"));
2404  for (const auto& it : TabbedPlotWidget::instances())
2405  {
2406  auto tab_widget = it.second->tabWidget();
2407  for (int i = 0; i < tab_widget->count(); i++)
2408  {
2409  PlotDocker* matrix = static_cast<PlotDocker*>(tab_widget->widget(i));
2410  QString name = QString("%1/%2_%3_%4.png")
2411  .arg(directory)
2412  .arg(current_date_time_name)
2413  .arg(image_number, 2, 10, QLatin1Char('0'))
2414  .arg(matrix->name());
2415  file_names.push_back(name);
2416  image_number++;
2417 
2418  QFileInfo check_file(file_names.back());
2419  if (check_file.exists() && check_file.isFile())
2420  {
2421  existing_files.push_back(name);
2422  }
2423  }
2424  }
2425  if (existing_files.isEmpty() == false)
2426  {
2427  QMessageBox msgBox;
2428  msgBox.setText("One or more files will be overwritten. ant to continue?");
2429  QString all_files;
2430  for (const auto& str : existing_files)
2431  {
2432  all_files.push_back("\n");
2433  all_files.append(str);
2434  }
2435  msgBox.setInformativeText(all_files);
2436  msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok);
2437  msgBox.setDefaultButton(QMessageBox::Ok);
2438 
2439  if (msgBox.exec() != QMessageBox::Ok)
2440  {
2441  return;
2442  }
2443  }
2444 
2445  image_number = 0;
2446  for (const auto& it : TabbedPlotWidget::instances())
2447  {
2448  auto tab_widget = it.second->tabWidget();
2449  for (int i = 0; i < tab_widget->count(); i++)
2450  {
2451  PlotDocker* matrix = static_cast<PlotDocker*>(tab_widget->widget(i));
2452  TabbedPlotWidget::saveTabImage(file_names[image_number], matrix);
2453  image_number++;
2454  }
2455  }
2456  }
2457 }*/
2458 
2460 {
2461  if (_data_loader.empty())
2462  {
2463  QMessageBox::warning(this, tr("Warning"), tr("No plugin was loaded to process a data file\n"));
2464  return;
2465  }
2466 
2467  QSettings settings;
2468 
2469  QString file_extension_filter;
2470 
2471  std::set<QString> extensions;
2472 
2473  for (auto& it : _data_loader)
2474  {
2475  DataLoaderPtr loader = it.second;
2476  for (QString extension : loader->compatibleFileExtensions())
2477  {
2478  extensions.insert(extension.toLower());
2479  }
2480  }
2481 
2482  for (const auto& it : extensions)
2483  {
2484  file_extension_filter.append(QString(" *.") + it);
2485  }
2486 
2487  QString directory_path = settings.value("MainWindow.lastDatafileDirectory", QDir::currentPath()).toString();
2488 
2489  QFileDialog loadDialog(this);
2490  loadDialog.setFileMode(QFileDialog::ExistingFiles);
2491  loadDialog.setViewMode(QFileDialog::Detail);
2492  loadDialog.setNameFilter(file_extension_filter);
2493  loadDialog.setDirectory(directory_path);
2494 
2495  QStringList fileNames;
2496  if (loadDialog.exec())
2497  {
2498  fileNames = loadDialog.selectedFiles();
2499  }
2500 
2501  if (fileNames.isEmpty())
2502  {
2503  return;
2504  }
2505 
2506  directory_path = QFileInfo(fileNames[0]).absolutePath();
2507  settings.setValue("MainWindow.lastDatafileDirectory", directory_path);
2508 
2509  if (loadDataFromFiles(fileNames))
2510  {
2511  updateRecentDataMenu(fileNames);
2512  }
2513 }
2514 
2516 {
2517  QSettings settings;
2518 
2519  QString directory_path = settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
2520  QString filename = QFileDialog::getOpenFileName(this, "Open Layout", directory_path, "*.xml");
2521  if (filename.isEmpty())
2522  {
2523  return;
2524  }
2525 
2526  if (loadLayoutFromFile(filename))
2527  {
2528  updateRecentLayoutMenu({ filename });
2529  }
2530 
2531  directory_path = QFileInfo(filename).absolutePath();
2532  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
2533 }
2534 
2536 {
2537  QDomDocument doc = xmlSaveState();
2538 
2539  QSettings settings;
2540 
2541  QString directory_path = settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
2542 
2543  QFileDialog saveDialog(this);
2544  saveDialog.setOption(QFileDialog::DontUseNativeDialog, true);
2545 
2546  QGridLayout* save_layout = static_cast<QGridLayout*>(saveDialog.layout());
2547 
2548  QFrame* frame = new QFrame;
2549  frame->setFrameStyle(QFrame::Box | QFrame::Plain);
2550  frame->setLineWidth(1);
2551 
2552  QVBoxLayout* vbox = new QVBoxLayout;
2553  QLabel* title = new QLabel("Save Layout options");
2554  QFrame* separator = new QFrame;
2555  separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
2556 
2557  auto checkbox_datasource = new QCheckBox("Save data source");
2558  checkbox_datasource->setToolTip("ant the layout to remember the source of your data,\n"
2559  "i.e. the Datafile used or the Streaming Plugin loaded ?");
2560  checkbox_datasource->setFocusPolicy(Qt::NoFocus);
2561  checkbox_datasource->setChecked(settings.value("MainWindow.saveLayoutDataSource", true).toBool());
2562 
2563  auto checkbox_snippets = new QCheckBox("Save custom transformations");
2564  checkbox_snippets->setToolTip("Do you want the layout to save the custom transformations?");
2565  checkbox_snippets->setFocusPolicy(Qt::NoFocus);
2566  checkbox_snippets->setChecked(settings.value("MainWindow.saveLayoutSnippets", true).toBool());
2567 
2568  vbox->addWidget(title);
2569  vbox->addWidget(separator);
2570  vbox->addWidget(checkbox_datasource);
2571  vbox->addWidget(checkbox_snippets);
2572  frame->setLayout(vbox);
2573 
2574  int rows = save_layout->rowCount();
2575  int col = save_layout->columnCount();
2576  save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
2577 
2578  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
2579  saveDialog.setDefaultSuffix("xml");
2580  saveDialog.setNameFilter("XML (*.xml)");
2581  saveDialog.setDirectory(directory_path);
2582  saveDialog.exec();
2583 
2584  if (saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
2585  {
2586  return;
2587  }
2588 
2589  QString fileName = saveDialog.selectedFiles().first();
2590 
2591  if (fileName.isEmpty())
2592  {
2593  return;
2594  }
2595 
2596  directory_path = QFileInfo(fileName).absolutePath();
2597  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
2598  settings.setValue("MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked());
2599  settings.setValue("MainWindow.saveLayoutSnippets", checkbox_snippets->isChecked());
2600 
2601  QDomElement root = doc.namedItem("root").toElement();
2602  root.setAttribute("version", LAYOUT_VERSION);
2603 
2604  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
2605 
2606  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
2607 
2608  root.appendChild(savePluginState(doc));
2609 
2610  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
2611 
2612  if (checkbox_datasource->isChecked())
2613  {
2614  QDomElement loaded_list = doc.createElement("previouslyLoaded_Datafiles");
2615 
2616  for (const auto& loaded : _loaded_datafiles)
2617  {
2618  QDomElement file_elem = doc.createElement("fileInfo");
2619  file_elem.setAttribute("filename", loaded.filename);
2620  file_elem.setAttribute("prefix", loaded.prefix);
2621 
2622  QDomElement datasources_elem = doc.createElement("selected_datasources");
2623  QString topics_list = loaded.selected_datasources.join(";");
2624  datasources_elem.setAttribute("value", topics_list);
2625  file_elem.appendChild(datasources_elem);
2626 
2627  file_elem.appendChild(loaded.plugin_config.firstChild());
2628  loaded_list.appendChild(file_elem);
2629  }
2630  root.appendChild(loaded_list);
2631 
2633  {
2634  QDomElement loaded_streamer = doc.createElement("previouslyLoaded_Streamer");
2635  QString streamer_name = _active_streamer_plugin->name();
2636  loaded_streamer.setAttribute("name", streamer_name);
2637  root.appendChild(loaded_streamer);
2638  }
2639  }
2640  //-----------------------------------
2641  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
2642  if (checkbox_snippets->isChecked())
2643  {
2644  QDomElement custom_equations = doc.createElement("customMathEquations");
2645  for (const auto& custom_it : _custom_plots)
2646  {
2647  const auto& custom_plot = custom_it.second;
2648  custom_equations.appendChild(custom_plot->xmlSaveState(doc));
2649  }
2650  root.appendChild(custom_equations);
2651 
2652  QByteArray snippets_xml_text = settings.value("AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
2653  auto snipped_saved = GetSnippetsFromXML(snippets_xml_text);
2654  auto snippets_root = ExportSnippets(snipped_saved, doc);
2655  root.appendChild(snippets_root);
2656  }
2657  root.appendChild(doc.createComment(" - - - - - - - - - - - - - - "));
2658  //------------------------------------
2659  QFile file(fileName);
2660  if (file.open(QIODevice::WriteOnly))
2661  {
2662  QTextStream stream(&file);
2663  stream << doc.toString() << endl;
2664  }
2665 }
2666 
2668 {
2669  static bool first_call = true;
2670  if (first_call && !_minimized)
2671  {
2672  first_call = false;
2673  QMessageBox::information(this, "Remember!", "Press F10 to switch back to the normal view");
2674  }
2675 
2677 
2678  ui->leftMainWindowFrame->setVisible(!_minimized);
2679  // ui->widgetOptions->setVisible(!_minimized && ui->pushButtonOptions->isChecked());
2680  ui->widgetTimescale->setVisible(!_minimized);
2681  ui->menuBar->setVisible(!_minimized);
2682 
2683  for (auto& it : TabbedPlotWidget::instances())
2684  {
2685  it.second->setControlsVisible(!_minimized);
2686  }
2687 }
2688 
2690 {
2691  QMenu* menu = _recent_data_files;
2692  for (QAction* action : menu->actions())
2693  {
2694  if (action->isSeparator())
2695  {
2696  break;
2697  }
2698  menu->removeAction(action);
2699  }
2700  menu->setEnabled(false);
2701  QSettings settings;
2702  settings.setValue("MainWindow.recentlyLoadedDatafile", {});
2703 }
2704 
2706 {
2707  QMenu* menu = _recent_layout_files;
2708  for (QAction* action : menu->actions())
2709  {
2710  if (action->isSeparator())
2711  {
2712  break;
2713  }
2714  menu->removeAction(action);
2715  }
2716  menu->setEnabled(false);
2717  QSettings settings;
2718  settings.setValue("MainWindow.recentlyLoadedLayout", {});
2719 }
2720 
2722 {
2723  QMessageBox msgBox(this);
2724  msgBox.setWindowTitle("Warning. Can't be undone.");
2725  msgBox.setText(tr("Do you want to remove the previously loaded data?\n"));
2726  msgBox.addButton(QMessageBox::No);
2727  msgBox.addButton(QMessageBox::Yes);
2728  msgBox.setDefaultButton(QMessageBox::Yes);
2729  // QPushButton* buttonPlaceholder = msgBox.addButton(tr("Keep empty placeholders"), QMessageBox::NoRole);
2730  auto reply = msgBox.exec();
2731 
2732  if (reply == QMessageBox::No)
2733  {
2734  return;
2735  }
2736 
2737  // if( msgBox.clickedButton() == buttonPlaceholder )
2738  // {
2739  // for( auto& it: _mapped_plot_data.numeric )
2740  // {
2741  // it.second.clear();
2742  // }
2743  // for( auto& it: _mapped_plot_data.user_defined )
2744  // {
2745  // it.second.clear();
2746  // }
2747 
2748  // for(const auto& it: TabbedPlotWidget::instances())
2749  // {
2750  // PlotDocker* matrix = it.second->currentTab() ;
2751  // matrix->maximumZoomOut(); // includes replot
2752  // }
2753  // }
2754  // else
2755  {
2756  deleteAllData();
2757  }
2758 }
2759 
2761 {
2762  QSettings settings;
2763  QString prev_style = settings.value("Preferences::theme", "light").toString();
2764 
2765  PreferencesDialog dialog;
2766  dialog.exec();
2767 
2768  QString theme = settings.value("Preferences::theme").toString();
2769 
2770  if (theme != prev_style)
2771  {
2772  loadStyleSheet(tr(":/resources/stylesheet_%1.qss").arg(theme));
2773  }
2774 }
2775 
2777 {
2778  ui->timeSlider->setFocus();
2779  ui->timeSlider->setRealStepValue(step);
2780 }
2781 
2783 {
2784  QSettings settings;
2785  QString directory_path = settings.value("MainWindow.loadStyleSheetDirectory", QDir::currentPath()).toString();
2786 
2787  QString fileName = QFileDialog::getOpenFileName(this, tr("Load StyleSheet"), directory_path, tr("(*.qss)"));
2788  if (fileName.isEmpty())
2789  {
2790  return;
2791  }
2792 
2793  loadStyleSheet(fileName);
2794 
2795  directory_path = QFileInfo(fileName).absolutePath();
2796  settings.setValue("MainWindow.loadStyleSheetDirectory", directory_path);
2797 }
2798 
2800 {
2801  switch (_labels_status)
2802  {
2803  case LabelStatus::LEFT:
2804  _labels_status = LabelStatus::HIDDEN;
2805  break;
2806  case LabelStatus::RIGHT:
2808  break;
2809  case LabelStatus::HIDDEN:
2811  break;
2812  }
2813 
2814  auto visitor = [=](PlotWidget* plot)
2815  {
2816  plot->activateLegend(_labels_status != LabelStatus::HIDDEN);
2817 
2819  {
2820  plot->setLegendAlignment(Qt::AlignLeft);
2821  }
2822  else if (_labels_status == LabelStatus::RIGHT)
2823  {
2824  plot->setLegendAlignment(Qt::AlignRight);
2825  }
2826  plot->replot();
2827  };
2828 
2829  this->forEachWidget(visitor);
2830 }
2831 
2833 {
2834  auto visitor = [=](PlotWidget* plot)
2835  {
2836  plot->zoomOut(false);
2837  };
2838  this->forEachWidget(visitor);
2839  onUndoableChange();
2840 }
2841 
2842 void MainWindow::on_comboStreaming_currentIndexChanged(const QString &current_text)
2843 {
2844  QSettings settings;
2845  settings.setValue("MainWindow.previousStreamingPlugin", current_text );
2846  auto streamer = _data_streamer.at( current_text );
2847  ui->buttonStreamingOptions->setEnabled( !streamer->availableActions().empty() );
2848 }
2849 
2851 {
2852  ui->buttonStreamingStart->setEnabled(false);
2853  if( ui->buttonStreamingStart->text() == "Start")
2854  {
2855  startStreamingPlugin( ui->comboStreaming->currentText() );
2856  }
2857  else {
2859  }
2860  ui->buttonStreamingStart->setEnabled(true);
2861 }
2862 
2863 
2864 PopupMenu::PopupMenu(QWidget *relative_widget, QWidget *parent):
2865  QMenu(parent), _w(relative_widget)
2866 {
2867 }
2868 
2869 void PopupMenu::showEvent(QShowEvent *)
2870 {
2871  QPoint p = _w->mapToGlobal( {} );
2872  QRect geo = _w->geometry();
2873  this->move( p.x() + geo.width(), p.y() );
2874 }
2875 
2877 {
2878  close();
2879 }
2880 
2881 void PopupMenu::closeEvent(QCloseEvent *)
2882 {
2883  _w->setAttribute(Qt::WA_UnderMouse, false);
2884 }
2885 
2887 {
2888  PopupMenu* menu = new PopupMenu(ui->buttonRecentData, this);
2889 
2890  for(auto action: _recent_data_files->actions()) {
2891  menu->addAction(action);
2892  }
2893  menu->exec();
2894 }
2895 
2897 {
2898  auto streamer = _data_streamer.at( ui->comboStreaming->currentText() );
2899 
2900  PopupMenu* menu = new PopupMenu(ui->buttonStreamingOptions, this);
2901  for( auto action: streamer->availableActions() ) {
2902  menu->addAction(action);
2903  }
2904  menu->show();
2905 }
2906 
2907 
2909 {
2910  bool hidden = !ui->frameFile->isHidden();
2911  ui->buttonHideFileFrame->setText( hidden ? "+" : " -");
2912  ui->frameFile->setHidden( hidden );
2913 
2914  QSettings settings;
2915  settings.setValue("MainWindow.hiddenFileFrame", hidden);
2916 }
2917 
2919 {
2920  bool hidden = !ui->frameStreaming->isHidden();
2921  ui->buttonHideStreamingFrame->setText( hidden ? "+" : " -");
2922  ui->frameStreaming->setHidden( hidden );
2923 
2924  QSettings settings;
2925  settings.setValue("MainWindow.hiddenStreamingFrame", hidden);
2926 }
2927 
2929 {
2930  bool hidden = !ui->framePublishers->isHidden();
2931  ui->buttonHidePublishersFrame->setText( hidden ? "+" : " -");
2932  ui->framePublishers->setHidden( hidden );
2933 
2934  QSettings settings;
2935  settings.setValue("MainWindow.hiddenPublishersFrame", hidden);
2936 }
2937 
2939 {
2940  PopupMenu* menu = new PopupMenu(ui->buttonRecentLayout, this);
2941 
2942  for(auto action: _recent_layout_files->actions()) {
2943  menu->addAction(action);
2944  }
2945  menu->exec();
2946 }
2947 
void on_actionExit_triggered()
const Point & back() const
Definition: plotdata.h:139
void setConstantRatioXY(bool active)
void onRedoInvoked()
Definition: mainwindow.cpp:312
QTimer * _animated_streaming_timer
Definition: mainwindow.h:144
void on_comboStreaming_currentIndexChanged(const QString &current_text)
void on_actionClearBuffer_triggered()
void onActionFullscreenTriggered()
enum MQTTPropertyCodes value
void setLinkedPlotName(const QString &linkedPlotName)
QShortcut _streaming_shortcut
Definition: mainwindow.h:101
QTimer * _replot_timer
Definition: mainwindow.h:136
void on_actionSupportPlotJuggler_triggered()
Ui::MainWindow * ui
Definition: mainwindow.h:94
void dataSourceRemoved(const std::string &name)
void on_pushButtonLoadDatafile_clicked()
void removeAllCurves()
Definition: plotwidget.cpp:743
void on_stylesheetChanged(QString style_dir)
void on_actionShare_the_love_triggered()
QShortcut _fullscreen_shortcut
Definition: mainwindow.h:100
PlotWidget * plotAt(int index)
void replot() override
void onEditCustomPlot(const std::string &plot_name)
void on_buttonStreamingStart_clicked()
void onPlotTabAdded(PlotDocker *docker)
Definition: mainwindow.cpp:784
#define nullptr
Definition: backward.hpp:386
void on_stylesheetChanged(QString theme)
void editMathPlot(const std::string &plot_name)
void on_actionPreferences_triggered()
const Point & front() const
Definition: plotdata.h:134
void plotWidgetAdded(PlotWidget *)
void rectChanged(PlotWidget *self, QRectF rect)
QIcon LoadSvgIcon(QString filename, QString style_name="light")
Definition: svg_util.h:17
CurveListPanel * _curvelist_widget
Definition: mainwindow.h:106
void on_stylesheetChanged(QString style_name)
void updatedDisplayTime()
bool _test_option
Definition: mainwindow.h:123
void on_changeDateTimeScale(bool enable)
std::vector< FileLoadInfo > _loaded_datafiles
Definition: mainwindow.h:129
void onCustomPlotCreated(CustomPlotPtr plot)
QTimer * _publish_timer
Definition: mainwindow.h:137
void deleteAllData()
void resizeEvent(QResizeEvent *)
Definition: mainwindow.cpp:718
void undoableChange()
bool loadDataFromFiles(QStringList filenames)
void legendSizeChanged(int new_size)
void checkAllCurvesFromLayout(const QDomElement &root)
Definition: mainwindow.cpp:817
PlotDataMapRef _mapped_plot_data
Definition: mainwindow.h:108
void closeEvent(QCloseEvent *event)
std::unordered_map< std::string, PlotData > numeric
Definition: plotdata.h:495
std::map< CurveTracker::Parameter, QIcon > _tracker_button_icons
Definition: mainwindow.h:132
bool xmlLoadState(QDomDocument state_document)
Definition: mainwindow.cpp:880
bool loadLayoutFromFile(QString filename)
void forEachWidget(std::function< void(PlotWidget *, PlotDocker *, int)> op)
void on_actionReportBug_triggered()
The DataStreamer base class to create your own plugin.
std::map< QString, std::shared_ptr< MessageParserCreator > > _message_parser_factory
Definition: mainwindow.h:114
QStringList selected_datasources
void on_actionClearRecentLayout_triggered()
#define RIGHT
Definition: Tree.c:96
void on_actionCheatsheet_triggered()
void onTrackerMovedFromWidget(QPointF pos)
Definition: mainwindow.cpp:352
void on_pushButtonZoomOut_clicked()
QMovie * _animated_streaming_movie
Definition: mainwindow.h:143
void on_actionClearRecentData_triggered()
std::deque< QDomDocument > _redo_states
Definition: mainwindow.h:119
bool isEmpty() const
Definition: plotwidget.cpp:575
void onPlotAdded(PlotWidget *plot)
Definition: mainwindow.cpp:723
void updateDataAndReplot(bool replot_hidden_tabs)
void refreshMathPlot(const std::string &curve_name)
int plotCount() const
void onTimeSlider_valueChanged(double abs_time)
Definition: mainwindow.cpp:363
void createMathPlot(const std::string &linked_plot)
void zoomOut()
QStringList initializePlugins(QString subdir_name)
Definition: mainwindow.cpp:415
std::map< QString, DataStreamerPtr > _data_streamer
Definition: mainwindow.h:113
void startStreamingPlugin(QString streamer_name)
MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent=nullptr)
Definition: mainwindow.cpp:51
void trackerMoved(QPointF pos)
void initializeActions()
Definition: mainwindow.cpp:388
std::map< QString, StatePublisherPtr > _state_publisher
Definition: mainwindow.h:112
QDomElement ExportSnippets(const SnippetsMap &snippets, QDomDocument &doc)
void leaveEvent(QEvent *) override
void editExistingPlot(CustomPlotPtr data)
double _tracker_time
Definition: mainwindow.h:127
void addCurve(const QString &item_name)
void reloadPlotData()
void on_pushButtonPlay_toggled(bool checked)
constexpr size_t count()
Definition: core.h:960
void buildDummyData()
Definition: mainwindow.cpp:630
void onPlotZoomChanged(PlotWidget *modified_plot, QRectF new_range)
Definition: mainwindow.cpp:761
void closeEvent(QCloseEvent *) override
QElapsedTimer _undo_timer
Definition: mainwindow.h:120
bool loadDataFromFile(const FileLoadInfo &info)
void updateCurves()
void realValueChanged(double)
void zoomOut(bool emit_signal)
void importPlotDataMapHelper(std::unordered_map< std::string, T > &source, std::unordered_map< std::string, T > &destination, bool delete_older)
void on_pushButtonLegend_clicked()
void on_pushButtonLoadLayout_clicked()
void onTrackerTimeUpdated(double absolute_time, bool do_replot)
Definition: mainwindow.cpp:369
TabbedPlotWidget * _main_tabbed_widget
Definition: mainwindow.h:96
void curvesDropped()
#define min(A, B)
Definition: Log.c:64
#define max(A, B)
Definition: Socket.h:88
void updateRecentLayoutMenu(QStringList new_filenames)
QShortcut _playback_shotcut
Definition: mainwindow.h:102
void onPlaybackLoop()
void on_buttonHidePublishersFrame_clicked()
void loadPluginState(const QDomElement &root)
void loadStyleSheet(QString file_path)
bool xmlLoadState(QDomElement &tabbed_area)
std::shared_ptr< CustomFunction > CustomPlotPtr
void deleteCurves(const std::vector< std::string > &curve_names)
static void block(LexState *ls)
Definition: lparser.c:1295
void on_actionLoadStyleSheet_triggered()
void on_buttonHideFileFrame_clicked()
void on_stylesheetChanged(QString theme)
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void on_tabbedAreaDestroyed(QObject *object)
static const QString LAYOUT_VERSION
LabelStatus _labels_status
Definition: mainwindow.h:153
QDateTime _prev_publish_time
Definition: mainwindow.h:139
std::shared_ptr< DataStreamer > DataStreamerPtr
CurveTracker::Parameter _tracker_param
Definition: mainwindow.h:130
const std::list< CurveInfo > & curveList() const
Definition: plotwidget.cpp:580
void updateTimeSlider()
void on_pushButtonRatio_toggled(bool checked)
void on_streamingSpinBox_valueChanged(int value)
QMenu * _recent_data_files
Definition: mainwindow.h:155
void onUndoInvoked()
Definition: mainwindow.cpp:329
void configureTracker(CurveTracker::Parameter val)
void stopStreamingPlugin()
void stylesheetChanged(QString style_name)
void onAddCustomPlot(const std::string &plot_name)
double get() const
Definition: utils.h:27
QDomElement savePluginState(QDomDocument &doc)
QTabWidget * tabWidget()
QShortcut _redo_shortcut
Definition: mainwindow.h:99
bool isStreamingActive() const
static TabbedPlotWidget * instance(const QString &key)
bool _minimized
Definition: mainwindow.h:104
void on_actionDeleteAllData_triggered()
void on_buttonRecentData_clicked()
const char * name
void on_buttonHideStreamingFrame_clicked()
CustomPlotMap _custom_plots
Definition: mainwindow.h:109
virtual bool isDebugPlugin()
Definition: pj_plugin.h:21
virtual size_t size() const
Definition: plotdata.h:92
FunctionEditorWidget * _function_editor
Definition: mainwindow.h:141
void onDeleteMultipleCurves(const std::vector< std::string > &curve_names)
Definition: mainwindow.cpp:942
detail::named_arg< Char, T > arg(const Char *name, const T &arg)
Definition: core.h:1656
void onSourceDataRemoved(const std::string &src_name)
Definition: plotwidget.cpp:544
void curveListChanged()
const T & move(const T &v)
Definition: backward.hpp:394
void addCustom(const QString &item_name)
bool _autostart_publishers
Definition: mainwindow.h:125
std::pair< std::vector< QString >, bool > MoveData(PlotDataMapRef &source, PlotDataMapRef &destination)
Definition: utils.cpp:55
MonitoredValue _time_offset
Definition: mainwindow.h:134
static CustomPlotPtr createFromXML(QDomElement &element)
void on_actionAbout_triggered()
std::deque< QDomDocument > _undo_states
Definition: mainwindow.h:118
void set(double newValue)
Definition: utils.h:17
std::shared_ptr< StatePublisher > StatePublisherPtr
MQTTClient c
Definition: test10.c:1656
std::map< QString, DataLoaderPtr > _data_loader
Definition: mainwindow.h:111
float time
Definition: mqtt_test.py:17
#define LEFT
Definition: Tree.c:95
void setTrackerPosition(double abs_time)
void on_buttonRecentLayout_clicked()
void setZoomEnabled(bool enabled)
void on_streamingToggled()
std::shared_ptr< DataStreamer > _active_streamer_plugin
Definition: mainwindow.h:116
PopupMenu(QWidget *relative_widget, QWidget *parent=nullptr)
void on_stylesheetChanged(QString theme)
void on_changeTimeOffset(double offset)
std::enable_if_t< all< Args... >::value, enable_t > enable
Definition: sol.hpp:1726
std::unordered_map< std::string, PlotDataAny > user_defined
Definition: plotdata.h:496
void hiddenItemsChanged()
void on_pushButtonUseDateTime_toggled(bool checked)
void pushBack(const Point &p)
Definition: plotdata.h:330
void updateTimeOffset()
void onRefreshCustomPlot(const std::string &plot_name)
void accept(CustomPlotPtr plot)
void showEvent(QShowEvent *) override
void on_buttonStreamingPause_toggled(bool paused)
virtual const char * name() const =0
QShortcut _undo_shortcut
Definition: mainwindow.h:98
void AddPrefixToPlotData(const std::string &prefix, std::unordered_map< std::string, Value > &data)
Definition: plotdata.h:513
QWidget * _w
Definition: mainwindow.h:259
std::unordered_map< std::string, PlotData >::iterator addNumeric(const std::string &name)
Definition: plotdata.h:498
void on_pushButtonActivateGrid_toggled(bool checked)
void importPlotDataMap(PlotDataMapRef &new_data, bool remove_old)
void on_buttonStreamingOptions_clicked()
void on_playbackStep_valueChanged(double arg1)
QDomDocument plugin_config
void on_pushButtonTimeTracker_pressed()
bool _disable_undo_logging
Definition: mainwindow.h:121
void onUndoableChange()
Definition: mainwindow.cpp:291
QMenu * _recent_layout_files
Definition: mainwindow.h:156
void update2ndColumnValues(double time, std::unordered_map< std::string, PlotData > *numeric_data)
dictionary data
Definition: mqtt_test.py:22
QDomDocument xmlSaveState() const
Definition: mainwindow.cpp:793
QString SetApplicationStyleSheet(QString style)
Definition: stylesheet.h:10
void activateGrid(bool activate)
void on_splitterMoved(int, int)
Definition: mainwindow.cpp:689
void enableTracker(bool enable)
void on_pushButtonRemoveTimeOffset_toggled(bool checked)
typename PlotDataBase< Value >::Point Point
Definition: plotdata.h:290
std::shared_ptr< DataLoader > DataLoaderPtr
void on_pushButtonSaveLayout_clicked()
void updateRecentDataMenu(QStringList new_filenames)
Definition: mainwindow.cpp:967
void onUpdateLeftTableValues()
Definition: mainwindow.cpp:347
void removeCurve(const std::string &name)
std::tuple< double, double, int > calculateVisibleRangeX()
static const std::map< QString, TabbedPlotWidget * > & instances()


plotjuggler
Author(s): Davide Faconti
autogenerated on Sun Dec 6 2020 03:48:09