mainwindow.cpp
Go to the documentation of this file.
1 #include <functional>
2 #include <stdio.h>
3 #include <numeric>
4 
5 #include <QActionGroup>
6 #include <QCheckBox>
7 #include <QCommandLineParser>
8 #include <QDebug>
9 #include <QDesktopServices>
10 #include <QDomDocument>
11 #include <QDoubleSpinBox>
12 #include <QElapsedTimer>
13 #include <QFileDialog>
14 #include <QInputDialog>
15 #include <QMenu>
16 #include <QGroupBox>
17 #include <QMessageBox>
18 #include <QMimeData>
19 #include <QMouseEvent>
20 #include <QPluginLoader>
21 #include <QPushButton>
22 #include <QKeySequence>
23 #include <QScrollBar>
24 #include <QSettings>
25 #include <QStringListModel>
26 #include <QStringRef>
27 #include <QThread>
28 #include <QTextStream>
29 #include <QWindow>
30 #include <QHeaderView>
31 
32 #include "mainwindow.h"
33 #include "curvelist_panel.h"
34 #include "tabbedplotwidget.h"
35 #include "selectlistdialog.h"
36 #include "PlotJuggler/plotdata.h"
37 #include "qwt_plot_canvas.h"
39 #include "utils.h"
40 
41 #include "ui_aboutdialog.h"
42 #include "ui_support_dialog.h"
44 
45 MainWindow::MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent) :
46  QMainWindow(parent),
47  ui(new Ui::MainWindow),
48  _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this),
49  _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this),
50  _fullscreen_shortcut(Qt::Key_F10, this),
51  _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this),
52  _playback_shotcut(Qt::Key_Space, this),
53  _minimized(false),
54  _current_streamer(nullptr),
55  _disable_undo_logging(false),
56  _tracker_time(0),
57  _tracker_param( CurveTracker::VALUE )
58 {
59  QLocale::setDefault(QLocale::c()); // set as default
60 
61  _test_option = commandline_parser.isSet("test");
62  _autostart_publishers = commandline_parser.isSet("publish");
63 
65 
66  ui->setupUi(this);
67 
68  if( commandline_parser.isSet("buffer_size"))
69  {
70  int buffer_size = std::max(10, commandline_parser.value("buffer_size").toInt() );
71  ui->streamingSpinBox->setMaximum(buffer_size);
72  }
73 
74  {
75  QIcon icon(":/icons/resources/light/office_chart_line_stacked.png");
76  if (!icon.isNull())
77  {
78  this->setWindowIcon(icon);
79  QApplication::setWindowIcon(icon);
80  }
81  }
82 
83  connect( _curvelist_widget->getTableView()->verticalScrollBar(), &QScrollBar::sliderMoved,
85 
88 
91 
94 
97 
100 
101  connect(_curvelist_widget->getTableView()->verticalScrollBar(), &QScrollBar::valueChanged,
103 
104  connect( ui->timeSlider, &RealSlider::realValueChanged,
106 
107  connect( ui->playbackRate, &QDoubleSpinBox::editingFinished, this, [this]()
108  {
109  ui->playbackRate->clearFocus();
110  });
111 
112 
113  _main_tabbed_widget = new TabbedPlotWidget("Main Window", this, nullptr, _mapped_plot_data, this);
114 
115  ui->plottingLayout->insertWidget(0, _main_tabbed_widget, 1);
116  ui->leftLayout->addWidget( _curvelist_widget );
117 
118  ui->splitter->setCollapsible(0,true);
119  ui->splitter->setStretchFactor(0,2);
120  ui->splitter->setStretchFactor(1,6);
121 
122  connect( ui->splitter, SIGNAL(splitterMoved(int,int)), SLOT(on_splitterMoved(int,int)) );
123 
125  initializePlugins( QCoreApplication::applicationDirPath() );
126  initializePlugins("/usr/local/PlotJuggler/plugins");
127 
128  _undo_timer.start();
129 
130  // save initial state
132 
133  _replot_timer = new QTimer(this);
134  _replot_timer->setInterval(40);
135  connect(_replot_timer, &QTimer::timeout, this, [this](){ updateDataAndReplot(false); } );
136 
137  _publish_timer = new QTimer(this);
138  _publish_timer->setInterval(20);
139  connect(_publish_timer, &QTimer::timeout, this, &MainWindow::onPlaybackLoop );
140 
141  ui->menuFile->setToolTipsVisible(true);
142  ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
143  ui->streamingLabel->setHidden(true);
144  ui->streamingSpinBox->setHidden(true);
145 
146  this->setMenuBar(ui->menuBar);
147  ui->menuBar->setNativeMenuBar(false);
148 
149  if( _test_option )
150  {
151  connect( ui->actionLoadDummyData, &QAction::triggered,
153  buildDummyData();
154  }
155  else{
156  ui->actionLoadDummyData->setVisible(false);
157  }
158 
159  bool file_loaded = false;
160  if( commandline_parser.isSet("datafile") )
161  {
162  QStringList datafiles = commandline_parser.values("datafile");
163  file_loaded = loadDataFromFiles( datafiles );
164  }
165  if( commandline_parser.isSet("layout"))
166  {
167  loadLayoutFromFile( commandline_parser.value("layout"));
168  }
169 
170  QSettings settings;
171  restoreGeometry(settings.value("MainWindow.geometry").toByteArray());
172 
173  bool activate_grid = settings.value("MainWindow.activateGrid", false).toBool();
174  ui->pushButtonActivateGrid->setChecked(activate_grid);
175 
176  int streaming_buffer_value = settings.value("MainWindow.streamingBufferValue", 5).toInt();
177  ui->streamingSpinBox->setValue(streaming_buffer_value);
178 
179  bool datetime_display = settings.value("MainWindow.dateTimeDisplay", false).toBool();
180  ui->pushButtonUseDateTime->setChecked( datetime_display );
181 
182  bool remove_time_offset = settings.value("MainWindow.removeTimeOffset", true).toBool();
183  ui->pushButtonRemoveTimeOffset->setChecked(remove_time_offset);
184 
185  ui->widgetOptions->setVisible( ui->pushButtonOptions->isChecked() );
186  ui->line->setVisible( ui->pushButtonOptions->isChecked() );
187 
188  //----------------------------------------------------------
189  QIcon trackerIconA, trackerIconB, trackerIconC;
190 
191  trackerIconA.addFile(QStringLiteral(":/icons/resources/light/line_tracker.png"), QSize(36, 36));
192  trackerIconB.addFile(QStringLiteral(":/icons/resources/light/line_tracker_1.png"), QSize(36, 36));
193  trackerIconC.addFile(QStringLiteral(":/icons/resources/light/line_tracker_a.png"), QSize(36, 36));
194 
198 
199  int tracker_setting = settings.value("MainWindow.timeTrackerSetting", (int)CurveTracker::VALUE ).toInt();
200  _tracker_param = static_cast<CurveTracker::Parameter>(tracker_setting);
201 
202  ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[_tracker_param] );
203 
204  forEachWidget( [&](PlotWidget* plot) {
205  plot->configureTracker(_tracker_param);
206  });
207 }
208 
210 {
211  delete ui;
212 }
213 
215 {
216  if(_disable_undo_logging) return;
217 
218  int elapsed_ms = _undo_timer.restart();
219 
220  // overwrite the previous
221  if( elapsed_ms < 100)
222  {
223  if( _undo_states.empty() == false)
224  _undo_states.pop_back();
225  }
226 
227  while( _undo_states.size() >= 100 ) _undo_states.pop_front();
228  _undo_states.push_back( xmlSaveState() );
229  _redo_states.clear();
230  // qDebug() << "undo " << _undo_states.size();
231 }
232 
233 
235 {
236  _disable_undo_logging = true;
237  if( _redo_states.size() > 0)
238  {
239  QDomDocument state_document = _redo_states.back();
240  while( _undo_states.size() >= 100 ) _undo_states.pop_front();
241  _undo_states.push_back( state_document );
242  _redo_states.pop_back();
243 
244  xmlLoadState( state_document );
245  }
246  // qDebug() << "undo " << _undo_states.size();
247  _disable_undo_logging = false;
248 }
249 
251 {
252  _disable_undo_logging = true;
253  if( _undo_states.size() > 1)
254  {
255  QDomDocument state_document = _undo_states.back();
256  while( _redo_states.size() >= 100 ) _redo_states.pop_front();
257  _redo_states.push_back( state_document );
258  _undo_states.pop_back();
259  state_document = _undo_states.back();
260 
261  xmlLoadState( state_document );
262  }
263  // qDebug() << "undo " << _undo_states.size();
264  _disable_undo_logging = false;
265 }
266 
268 {
269  auto table_model = _curvelist_widget->getTableModel();
270 
271  for(auto table_view: { _curvelist_widget->getTableView(), _curvelist_widget->getCustomView() } )
272  {
274  {
275  continue;
276  }
277 
278  const int vertical_height = table_view->visibleRegion().boundingRect().height();
279 
280  for (int row = 0; row < _curvelist_widget->rowCount(); row++)
281  {
282  int vertical_pos = table_view->rowViewportPosition(row);
283  if( vertical_pos < 0 || table_view->isRowHidden(row) ){ continue; }
284  if( vertical_pos > vertical_height){ break; }
285 
286  const std::string& name = table_model->item(row,0)->text().toStdString();
287  auto it = _mapped_plot_data.numeric.find(name);
288  if( it != _mapped_plot_data.numeric.end())
289  {
290  auto& data = it->second;
291 
292  double num = 0.0;
293  bool valid = false;
294 
295  if( _tracker_time < std::numeric_limits<double>::max())
296  {
297  auto value = data.getYfromX( _tracker_time );
298  if(value){
299  valid = true;
300  num = value.value();
301  }
302  }
303  else if( data.size() > 0)
304  {
305  valid = true;
306  num = data.back().y;
307  }
308  if( valid )
309  {
310  QString num_text = QString::number( num, 'f', 3);
311  if(num_text.contains('.'))
312  {
313  int idx = num_text.length() -1;
314  while( num_text[idx] == '0' )
315  {
316  num_text[idx] = ' ';
317  idx--;
318  }
319  if( num_text[idx] == '.') num_text[idx] = ' ';
320  }
321  table_model->item(row,1)->setText(num_text + ' ');
322  }
323  }
324  }
325  }
326 }
327 
328 
329 void MainWindow::onTrackerMovedFromWidget(QPointF relative_pos)
330 {
331  _tracker_time = relative_pos.x() + _time_offset.get();
332 
333  auto prev = ui->timeSlider->blockSignals(true);
334  ui->timeSlider->setRealValue( _tracker_time );
335  ui->timeSlider->blockSignals(prev);
336 
338 }
339 
341 {
342  _tracker_time = abs_time;
344 }
345 
346 void MainWindow::onTrackerTimeUpdated(double absolute_time, bool do_replot)
347 {
350 
351  for ( auto& it: _state_publisher)
352  {
353  it.second->updateState( absolute_time);
354  }
355 
356  forEachWidget( [&](PlotWidget* plot)
357  {
359  if(do_replot)
360  {
361  plot->replot();
362  }
363  } );
364 }
365 
366 void MainWindow::createTabbedDialog(QString suggest_win_name, PlotMatrix* first_tab)
367 {
368  if( suggest_win_name.isEmpty())
369  {
370  for (size_t i=0; i<= TabbedPlotWidget::instances().size(); i++)
371  {
372  suggest_win_name = QString("Window%1").arg(i);
373  TabbedPlotWidget* tw = TabbedPlotWidget::instance(suggest_win_name);
374  if( tw == nullptr )
375  {
376  break;
377  }
378  }
379  }
380 
381  SubWindow* window = new SubWindow(suggest_win_name, first_tab, _mapped_plot_data, this );
382 
383  connect( window, SIGNAL(destroyed(QObject*)), this, SLOT(onFloatingWindowDestroyed(QObject*)) );
384  connect( window, SIGNAL(destroyed(QObject*)), this, SLOT(onUndoableChange()) );
385 
387 
388  window->setAttribute( Qt::WA_DeleteOnClose, true );
389  window->show();
390  window->activateWindow();
391  window->raise();
392 
393  if (this->signalsBlocked() == false) onUndoableChange();
394 }
395 
396 
398 {
399  _undo_shortcut.setContext(Qt::ApplicationShortcut);
400  _redo_shortcut.setContext(Qt::ApplicationShortcut);
401  _fullscreen_shortcut.setContext(Qt::ApplicationShortcut);
402 
403  connect( &_undo_shortcut, &QShortcut::activated, this, &MainWindow::onUndoInvoked );
404  connect( &_redo_shortcut, &QShortcut::activated, this, &MainWindow::onRedoInvoked );
405  connect( &_streaming_shortcut, &QShortcut::activated, this, &MainWindow::on_streamingToggled );
406  connect( &_playback_shotcut, &QShortcut::activated, ui->pushButtonPlay, &QPushButton::toggle );
407  connect( &_fullscreen_shortcut, &QShortcut::activated, this, &MainWindow::on_actionFullscreen_triggered);
408 
409  QShortcut* open_menu_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F), this);
410  connect( open_menu_shortcut, &QShortcut::activated, [this](){
411  ui->menuFile->exec( ui->menuBar->mapToGlobal(QPoint(0,25)));
412  } );
413 
414  QShortcut* open_streaming_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_S), this);
415  connect( open_streaming_shortcut, &QShortcut::activated, [this](){
416  ui->menuStreaming->exec( ui->menuBar->mapToGlobal(QPoint(50,25)));
417  } );
418 
419  QShortcut* open_publish_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_P), this);
420  connect( open_publish_shortcut, &QShortcut::activated, [this](){
421  ui->menuPublishers->exec( ui->menuBar->mapToGlobal(QPoint(140,25)));
422  } );
423 
424  QShortcut* open_help_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H), this);
425  connect( open_help_shortcut, &QShortcut::activated, [this](){
426  ui->menuHelp->exec( ui->menuBar->mapToGlobal(QPoint(230,25)));
427  } );
428 
429  //---------------------------------------------
430 
431  QSettings settings;
432  updateRecentDataMenu( settings.value("MainWindow.recentlyLoadedDatafile").toStringList() );
433  updateRecentLayoutMenu( settings.value("MainWindow.recentlyLoadedLayout").toStringList() );
434 }
435 
436 void MainWindow::initializePlugins(QString directory_name)
437 {
438  static std::set<QString> loaded_plugins;
439 
440  QDir pluginsDir( directory_name );
441 
442  for (const QString& filename: pluginsDir.entryList(QDir::Files))
443  {
444  QFileInfo fileinfo(filename);
445  if( fileinfo.suffix() != "so" && fileinfo.suffix() != "dll" && fileinfo.suffix() != "dylib"){
446  continue;
447  }
448 
449  if( loaded_plugins.find( filename ) != loaded_plugins.end())
450  {
451  continue;
452  }
453 
454  QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename), this);
455 
456  QObject *plugin = pluginLoader.instance();
457  if (plugin)
458  {
459  DataLoader *loader = qobject_cast<DataLoader *>(plugin);
460  StatePublisher *publisher = qobject_cast<StatePublisher *>(plugin);
461  DataStreamer *streamer = qobject_cast<DataStreamer *>(plugin);
462 
463  QString plugin_name;
464  if( loader ) plugin_name = loader->name();
465  if( publisher ) plugin_name = publisher->name();
466  if( streamer ) plugin_name = streamer->name();
467  plugin_name.replace(" ", "_");
468 
469  if( loaded_plugins.find(plugin_name) == loaded_plugins.end())
470  {
471  loaded_plugins.insert( plugin_name );
472  }
473  else{
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.").arg(plugin_name) );
477  continue;
478  }
479 
480  if (loader)
481  {
482  qDebug() << filename << ": is a DataLoader plugin";
483  if( !_test_option && loader->isDebugPlugin())
484  {
485  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
486  }
487  else{
488  _data_loader.insert( std::make_pair( plugin_name, loader) );
489  }
490  }
491  else if (publisher)
492  {
493  publisher->setDataMap( &_mapped_plot_data );
494  qDebug() << filename << ": is a StatePublisher plugin";
495  if( !_test_option && publisher->isDebugPlugin())
496  {
497  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
498  }
499  else
500  {
501  ui->menuPublishers->setEnabled(true);
502 
503  _state_publisher.insert( std::make_pair(plugin_name, publisher) );
504  QAction* activatePublisher = new QAction(tr("Start: ") + plugin_name , this);
505  activatePublisher->setProperty("starter_button", true);
506  activatePublisher->setCheckable(true);
507  activatePublisher->setChecked(false);
508 
509  ui->menuPublishers->addSeparator();
510  ui->menuPublishers->addSection(plugin_name);
511  ui->menuPublishers->addAction(activatePublisher);
512  publisher->setParentMenu( ui->menuPublishers, activatePublisher );
513 
514  connect(activatePublisher, &QAction::toggled,
515  [=](bool enable)
516  {
517  publisher->setEnabled( enable );
518  } );
519  }
520  }
521  else if (streamer)
522  {
523  qDebug() << filename << ": is a DataStreamer plugin";
524  if( !_test_option && streamer->isDebugPlugin())
525  {
526  qDebug() << filename << "...but will be ignored unless the argument -t is used.";
527  }
528  else{
529  _data_streamer.insert( std::make_pair(plugin_name , streamer ) );
530 
531  QAction* startStreamer = new QAction(QString("Start: ") + plugin_name, this);
532  ui->menuStreaming->setEnabled(true);
533  ui->menuStreaming->addAction(startStreamer);
534 
535  streamer->addActionsToParentMenu( ui->menuStreaming );
536  ui->menuStreaming->addSeparator();
537 
538  connect(startStreamer, &QAction::triggered, this, [this, plugin_name]()
539  {
540  on_actionStartStreaming(plugin_name);
541  });
542 
543 
544  connect(streamer, &DataStreamer::connectionClosed,
545  ui->actionStopStreaming, &QAction::trigger );
546 
547  connect(streamer, &DataStreamer::clearBuffers,
549  }
550  }
551  }
552  else{
553  if( pluginLoader.errorString().contains("is not an ELF object") == false)
554  {
555  qDebug() << filename << ": " << pluginLoader.errorString();
556  }
557  }
558  }
559 }
560 
562 {
563  PlotDataMapRef datamap;
564 
565  static int count = 0;
566  size_t SIZE = 10000;
567  QElapsedTimer timer;
568  timer.start();
569  QStringList words_list;
570  words_list << "world/siam" << "world/tre" << "walk/piccoli" << "walk/porcellin"
571  << "fly/high/mai" << "fly/high/nessun" << "fly/low/ci" << "fly/low/dividera"
572  << "data_1" << "data_2" << "data_3" << "data_10";
573 
574  for( int i=0; i<10; i++)
575  {
576  words_list.append(QString("data_vect/%1").arg(count++));
577  }
578 
579  for( const QString& name: words_list)
580  {
581  double A = 6* ((double)qrand()/(double)RAND_MAX) - 3;
582  double B = 3* ((double)qrand()/(double)RAND_MAX) ;
583  double C = 3* ((double)qrand()/(double)RAND_MAX) ;
584  double D = 20* ((double)qrand()/(double)RAND_MAX) ;
585 
586  auto it = datamap.addNumeric( name.toStdString() );
587  PlotData& plot = it->second;
588 
589  double t = 0;
590  for (unsigned indx=0; indx<SIZE; indx++)
591  {
592  t += 0.01;
593  plot.pushBack( PlotData::Point( t+35, A*sin(B*t + C) + D*t*0.02 ) ) ;
594  }
595  }
596 
597  PlotData& sin_plot = datamap.addNumeric( "_sin" )->second;
598  PlotData& cos_plot = datamap.addNumeric( "_cos" )->second;
599 
600  double t = 0;
601  for (unsigned indx=0; indx<SIZE; indx++)
602  {
603  t += 0.01;
604  sin_plot.pushBack( PlotData::Point( t+20, sin(t*0.4) ) ) ;
605  cos_plot.pushBack( PlotData::Point( t+20, cos(t*0.4) ) ) ;
606  }
607 
608  importPlotDataMap(datamap, true);
609 }
610 
612 {
613  QList<int> sizes = ui->splitter->sizes();
614  int maxLeftWidth = _curvelist_widget->maximumWidth();
615  int totalWidth = sizes[0] + sizes[1];
616 
617  if( sizes[0] > maxLeftWidth)
618  {
619  sizes[0] = maxLeftWidth;
620  sizes[1] = totalWidth - maxLeftWidth;
621  ui->splitter->setSizes(sizes);
622  }
623 }
624 
625 void MainWindow::resizeEvent(QResizeEvent *)
626 {
627  on_splitterMoved( 0, 0 );
628 }
629 
630 
632 {
633  connect( plot, &PlotWidget::undoableChange,
635 
636  connect( plot, &PlotWidget::trackerMoved,
638 
639  connect( plot, &PlotWidget::swapWidgetsRequested,
640  this, &MainWindow::onSwapPlots);
641 
642  connect( this, &MainWindow::requestRemoveCurveByName,
643  plot, &PlotWidget::removeCurve) ;
644 
645  connect( plot, &PlotWidget::curveListChanged,
646  this, [this]()
647  {
650  });
651 
652  connect( &_time_offset, SIGNAL( valueChanged(double)),
653  plot, SLOT(on_changeTimeOffset(double)) );
654 
655  connect( ui->pushButtonUseDateTime, &QPushButton::toggled,
657 
658  connect( plot, &PlotWidget::curvesDropped,
660 
662  plot->on_changeDateTimeScale( ui->pushButtonUseDateTime->isChecked() );
663  plot->activateGrid( ui->pushButtonActivateGrid->isChecked() );
664  plot->enableTracker( !isStreamingActive() );
666 }
667 
669 {
670  connect( matrix, &PlotMatrix::plotAdded, this, &MainWindow:: onPlotAdded);
671  connect( matrix, &PlotMatrix::undoableChange, this, &MainWindow:: onUndoableChange );
672 }
673 
674 QDomDocument MainWindow::xmlSaveState() const
675 {
676  QDomDocument doc;
677  QDomProcessingInstruction instr =
678  doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
679 
680  doc.appendChild(instr);
681 
682  QDomElement root = doc.createElement( "root" );
683 
684  for (auto& it: TabbedPlotWidget::instances() )
685  {
686  QDomElement tabbed_area = it.second->xmlSaveState(doc);
687  root.appendChild( tabbed_area );
688  }
689 
690  doc.appendChild(root);
691 
692  QDomElement relative_time = doc.createElement( "use_relative_time_offset" );
693  relative_time.setAttribute("enabled", ui->pushButtonRemoveTimeOffset->isChecked() );
694  root.appendChild( relative_time );
695 
696  return doc;
697 }
698 
699 void MainWindow::checkAllCurvesFromLayout(const QDomElement& root)
700 {
701  std::set<std::string> curves;
702  // ugly code, I am sorry
703  for ( QDomElement tw = root.firstChildElement( "tabbed_widget" ) ;
704  !tw.isNull(); tw = tw.nextSiblingElement( "tabbed_widget" ) )
705  {
706  for ( QDomElement pm = tw.firstChildElement( "plotmatrix" ) ;
707  !pm.isNull(); pm = pm.nextSiblingElement( "plotmatrix" ) )
708  {
709  for ( QDomElement pl = pm.firstChildElement( "plot" ) ;
710  !pl.isNull(); pl = pl.nextSiblingElement( "plot" ) )
711  {
712  for ( QDomElement cv = pl.firstChildElement( "curve" ) ;
713  !cv.isNull(); cv = cv.nextSiblingElement( "curve" ) )
714  {
715  curves.insert( cv.attribute("name").toStdString() );
716  }
717  }
718  }
719  }
720 
721  std::vector<std::string> missing_curves;
722 
723  for (auto& curve_name: curves)
724  {
725  if( _mapped_plot_data.numeric.count( curve_name ) == 0)
726  {
727  missing_curves.push_back(curve_name);
728  }
729  }
730  if( missing_curves.size() > 0 )
731  {
732  QMessageBox msgBox(this);
733  msgBox.setWindowTitle("Warning");
734  msgBox.setText(tr("One or more timeseries in the layout haven't been loaded yet\n"
735  "What do you want to do?"));
736 
737  QPushButton* buttonRemove = msgBox.addButton(tr("Remove curves from plots"), QMessageBox::RejectRole);
738  QPushButton* buttonPlaceholder = msgBox.addButton(tr("Create empty placeholders"), QMessageBox::YesRole);
739  msgBox.setDefaultButton(buttonPlaceholder);
740  msgBox.exec();
741  if( msgBox.clickedButton() == buttonPlaceholder )
742  {
743  for(auto& name: missing_curves )
744  {
745  _curvelist_widget->addItem( QString::fromStdString( name ) );
747  }
749  }
750  }
751 }
752 
753 bool MainWindow::xmlLoadState(QDomDocument state_document)
754 {
755  QDomElement root = state_document.namedItem("root").toElement();
756  if ( root.isNull() ) {
757  qWarning() << "No <root> element found at the top-level of the XML file!";
758  return false;
759  }
760 
761  size_t num_floating = 0;
762  std::map<QString,QDomElement> tabbed_widgets_with_name;
763 
764  for (QDomElement tw = root.firstChildElement( "tabbed_widget" ) ;
765  tw.isNull() == false;
766  tw = tw.nextSiblingElement( "tabbed_widget" ) )
767  {
768  if( tw.attribute("parent") != ("main_window") )
769  {
770  num_floating++;
771  }
772  tabbed_widgets_with_name[ tw.attribute("name") ] = tw;
773  }
774 
775  // add if missing
776  for(const auto& it: tabbed_widgets_with_name)
777  {
778  if( TabbedPlotWidget::instance( it.first ) == nullptr)
779  {
780  createTabbedDialog( it.first, nullptr );
781  }
782  }
783 
784  // remove those which don't share list of names
785  for(const auto& it: TabbedPlotWidget::instances())
786  {
787  if( tabbed_widgets_with_name.count( it.first ) == 0)
788  {
789  it.second->deleteLater();
790  }
791  }
792 
793  //-----------------------------------------------------
795  //-----------------------------------------------------
796 
797  for ( QDomElement tw = root.firstChildElement( "tabbed_widget" ) ;
798  tw.isNull() == false;
799  tw = tw.nextSiblingElement( "tabbed_widget" ) )
800  {
801  TabbedPlotWidget* tabwidget = TabbedPlotWidget::instance( tw.attribute("name"));
802  tabwidget->xmlLoadState( tw );
803  }
804 
805  QDomElement relative_time = root.firstChildElement( "use_relative_time_offset" );
806  if( !relative_time.isNull())
807  {
808  bool remove_offset = (relative_time.attribute("enabled") == QString("1"));
809  ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
810  }
811  return true;
812 }
813 
814 
815 void MainWindow::onDeleteMultipleCurves(const std::vector<std::string> &curve_names)
816 {
817  for( const auto& curve_name: curve_names )
818  {
819  auto plot_curve = _mapped_plot_data.numeric.find( curve_name );
820  if( plot_curve == _mapped_plot_data.numeric.end())
821  {
822  continue;
823  }
824 
825  emit requestRemoveCurveByName( curve_name );
826  _mapped_plot_data.numeric.erase( plot_curve );
827 
828  auto custom_it = _custom_plots.find( curve_name );
829  if( custom_it != _custom_plots.end())
830  {
831  _custom_plots.erase( custom_it );
832  }
833 
834  int row = _curvelist_widget->findRowByName( curve_name );
835  if( row != -1 )
836  {
838  }
839  }
840 
841  forEachWidget( [](PlotWidget* plot) {
842  plot->replot();
843  } );
844 }
845 
846 void MainWindow::updateRecentDataMenu(QStringList new_filenames)
847 {
848  QMenu* menu = ui->menuRecentData;
849 
850  QAction* separator = nullptr;
851  QStringList prev_filenames;
852  for (QAction *action: menu->actions())
853  {
854  if ( action->isSeparator() )
855  {
856  separator = action;
857  break;
858  }
859  if(new_filenames.contains( action->text() ) == false)
860  {
861  prev_filenames.push_back( action->text() );
862  }
863  menu->removeAction( action );
864  }
865 
866  new_filenames.append( prev_filenames );
867  while( new_filenames.size() > 10 )
868  {
869  new_filenames.removeLast();
870  }
871 
872  for (const auto& filename: new_filenames)
873  {
874  QAction* action = new QAction(filename, nullptr);
875  connect( action, &QAction::triggered, this, [this, filename]
876  {
878  } );
879  menu->insertAction(separator, action );
880  }
881 
882  QSettings settings;
883  settings.setValue("MainWindow.recentlyLoadedDatafile", new_filenames );
884  menu->setEnabled( new_filenames.size() > 0 );
885 }
886 
887 void MainWindow::updateRecentLayoutMenu(QStringList new_filenames)
888 {
889  QMenu* menu = ui->menuRecentLayout;
890 
891  QAction* separator = nullptr;
892  QStringList prev_filenames;
893  for (QAction *action: menu->actions())
894  {
895  if ( action->isSeparator() )
896  {
897  separator = action;
898  break;
899  }
900  if(new_filenames.contains( action->text() ) == false)
901  {
902  prev_filenames.push_back( action->text() );
903  }
904  menu->removeAction( action );
905  }
906 
907  new_filenames.append( prev_filenames );
908  while( new_filenames.size() > 10 )
909  {
910  new_filenames.removeLast();
911  }
912 
913  for (const auto& filename: new_filenames)
914  {
915  QAction* action = new QAction(filename, nullptr);
916  connect( action, &QAction::triggered, this, [this, filename]
917  {
918  if ( this->loadLayoutFromFile(filename) )
919  {
921  }
922  } );
923  menu->insertAction(separator, action );
924  }
925 
926  QSettings settings;
927  settings.setValue("MainWindow.recentlyLoadedLayout", new_filenames );
928  menu->setEnabled( new_filenames.size() > 0 );
929 }
930 
931 
933 {
934  forEachWidget( [](PlotWidget* plot) {
935  plot->detachAllCurves();
936  } );
937 
938  _mapped_plot_data.numeric.clear();
940  _custom_plots.clear();
942  _loaded_datafiles.clear();
943 
944  bool stopped = false;
945  for (QAction* action: ui->menuPublishers->actions())
946  {
947  auto is_start_button = action->property("starter_button");
948  if( is_start_button.isValid() && is_start_button.toBool() && action->isChecked() )
949  {
950  action->setChecked( false );
951  stopped = true;
952  }
953  }
954 
955  if( stopped )
956  {
957  QMessageBox::warning(this, "State publishers stopped",
958  "All the state publishers have been stopped because old data has been deleted.");
959  }
960 }
961 
962 
963 template <typename T>
964 void importPlotDataMapHelper(std::unordered_map<std::string,T>& source,
965  std::unordered_map<std::string,T>& destination,
966  bool delete_older)
967 {
968  for (auto& it: source)
969  {
970  const std::string& name = it.first;
971  T& source_plot = it.second;
972  auto plot_with_same_name = destination.find(name);
973 
974  // this is a new plot
975  if( plot_with_same_name == destination.end() )
976  {
977  plot_with_same_name = destination.emplace( std::piecewise_construct,
978  std::forward_as_tuple(name),
979  std::forward_as_tuple(name)
980  ).first;
981  }
982  T& destination_plot = plot_with_same_name->second;
983 
984  if( delete_older )
985  {
986  double max_range_x = destination_plot.maximumRangeX();
987  destination_plot.swapData(source_plot);
988  destination_plot.setMaximumRangeX(max_range_x); // just in case
989  }
990  else
991  {
992  for (size_t i=0; i< source_plot.size(); i++)
993  {
994  destination_plot.pushBack( source_plot.at(i) );
995  }
996  }
997  source_plot.clear();
998  }
999 }
1000 
1001 void MainWindow::importPlotDataMap(PlotDataMapRef& new_data, bool remove_old)
1002 {
1003  if( new_data.user_defined.empty() && new_data.numeric.empty() )
1004  {
1005  return;
1006  }
1007 
1008  if( remove_old )
1009  {
1010  std::vector<std::string> old_plots_to_delete;
1011 
1012  for (auto& it: _mapped_plot_data.numeric)
1013  {
1014  // timeseries in old but not in new
1015  if( new_data.numeric.count( it.first ) == 0 )
1016  {
1017  old_plots_to_delete.push_back( it.first );
1018  }
1019  }
1020 
1021  if( !old_plots_to_delete.empty() )
1022  {
1023  QMessageBox::StandardButton reply;
1024  reply = QMessageBox::question(this, tr("Warning"),
1025  tr("Do you want to remove the previously loaded data?\n"),
1026  QMessageBox::Yes | QMessageBox::No,
1027  QMessageBox::Yes );
1028  if( reply == QMessageBox::Yes )
1029  {
1030  onDeleteMultipleCurves(old_plots_to_delete);
1031  }
1032  }
1033  }
1034 
1035  bool curvelist_modified = false;
1036  for (auto& it: new_data.numeric)
1037  {
1038  const std::string& name = it.first;
1039  if( it.second.size()>0 && _mapped_plot_data.numeric.count(name) == 0)
1040  {
1041  _curvelist_widget->addItem( QString::fromStdString( name ) );
1042  curvelist_modified = true;
1043  }
1044  }
1045 
1046  importPlotDataMapHelper( new_data.numeric, _mapped_plot_data.numeric, remove_old );
1048 
1049  if( curvelist_modified )
1050  {
1052  }
1053 }
1054 
1056 {
1057  return ui->pushButtonStreaming->isChecked() && _current_streamer;
1058 }
1059 
1060 bool MainWindow::loadDataFromFiles( QStringList filenames )
1061 {
1062  if( filenames.size() > 1 )
1063  {
1064  static bool show_me = true;
1065 
1066  QMessageBox msgbox;
1067  msgbox.setWindowTitle("Loading multiple files");
1068  msgbox.setText("You are loading multiple files at once. A prefix will be automatically added to the name of the timeseries.\n\n"
1069  "This is an experimental feature. Publishers will not work as you may expect.");
1070  msgbox.addButton(QMessageBox::Ok);
1071  // QCheckBox *cb = new QCheckBox("Don't show this again");
1072  // msgbox.setCheckBox(cb);
1073  // connect(cb, &QCheckBox::stateChanged, this, [this, cb , &show_me]() { show_me = !cb->isChecked(); } );
1074  msgbox.exec();
1075  }
1076 
1077  char prefix_ch = 'A';
1078  QStringList loaded_filenames;
1079 
1080  for( const auto& filename: filenames)
1081  {
1082  FileLoadInfo info;
1083  info.filename = filename;
1084  if( filenames.size() > 1 )
1085  {
1086  info.prefix = prefix_ch;
1087  }
1088 
1089  if( loadDataFromFile(info) )
1090  {
1091  loaded_filenames.push_back(filename);
1092  prefix_ch++;
1093  }
1094  }
1095  if( loaded_filenames.size() > 0 )
1096  {
1097  updateRecentDataMenu(loaded_filenames);
1098  return true;
1099  }
1100  return false;
1101 }
1102 
1104 {
1105  const QString extension = QFileInfo(info.filename).suffix().toLower();
1106 
1107  typedef std::map<QString,DataLoader*>::iterator MapIterator;
1108 
1109  std::vector<MapIterator> compatible_loaders;
1110 
1111  for (MapIterator it = _data_loader.begin(); it != _data_loader.end(); ++it)
1112  {
1113  DataLoader* data_loader = it->second;
1114  std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
1115 
1116  for(auto& ext: extensions){
1117 
1118  if( extension == QString(ext).toLower()){
1119  compatible_loaders.push_back( it );
1120  break;
1121  }
1122  }
1123  }
1124 
1125  DataLoader* dataloader = nullptr;
1126 
1127  if( compatible_loaders.size() == 1)
1128  {
1129  dataloader = compatible_loaders.front()->second;
1130  }
1131  else{
1132  static QString last_plugin_name_used;
1133 
1134  QStringList names;
1135  for (auto& cl: compatible_loaders)
1136  {
1137  const auto& name = cl->first;
1138 
1139  if( name == last_plugin_name_used ){
1140  names.push_front( name );
1141  }
1142  else{
1143  names.push_back( name );
1144  }
1145  }
1146 
1147  bool ok;
1148  QString plugin_name = QInputDialog::getItem(this, tr("QInputDialog::getItem()"),
1149  tr("Select the loader to use:"),
1150  names, 0, false, &ok);
1151  if (ok && !plugin_name.isEmpty())
1152  {
1153  dataloader = _data_loader[ plugin_name ];
1154  last_plugin_name_used = plugin_name;
1155  }
1156  }
1157 
1158  if( dataloader )
1159  {
1160  QFile file(info.filename);
1161 
1162  if (!file.open(QFile::ReadOnly | QFile::Text)) {
1163  QMessageBox::warning(this, tr("Datafile"),
1164  tr("Cannot read file %1:\n%2.")
1165  .arg(info.filename)
1166  .arg(file.errorString()));
1167  return false;
1168  }
1169  file.close();
1170 
1171  try{
1172  PlotDataMapRef mapped_data;
1173  FileLoadInfo new_info = info;
1174 
1175  if( dataloader->readDataFromFile( &new_info, mapped_data ) )
1176  {
1177  AddPrefixToPlotData( info.prefix.toStdString(), mapped_data.numeric );
1178 
1179  importPlotDataMap(mapped_data, true);
1180 
1181  QDomElement plugin_elem = dataloader->xmlSaveState(new_info.plugin_config);
1182  new_info.plugin_config.appendChild( plugin_elem );
1183 
1184  _loaded_datafiles.push_back(new_info);
1185  }
1186  }
1187  catch(std::exception &ex)
1188  {
1189  QMessageBox::warning(this, tr("Exception from the plugin"),
1190  tr("The plugin [%1] thrown the following exception: \n\n %3\n")
1191  .arg(dataloader->name()).arg(ex.what()) );
1192  return false;
1193  }
1194  }
1195  else{
1196  QMessageBox::warning(this, tr("Error"),
1197  tr("Cannot read files with extension %1.\n No plugin can handle that!\n")
1198  .arg(info.filename) );
1199  }
1201  updateDataAndReplot( true );
1202  ui->timeSlider->setRealValue( ui->timeSlider->getMinimum() );
1203 
1204  return true;
1205 }
1206 
1207 
1208 void MainWindow::on_actionStartStreaming(QString streamer_name)
1209 {
1210  if( _current_streamer )
1211  {
1213  _current_streamer = nullptr;
1214  }
1215 
1216  if( _data_streamer.empty())
1217  {
1218  qDebug() << "Error, no streamer loaded";
1219  return;
1220  }
1221 
1222  if( _data_streamer.size() == 1)
1223  {
1224  _current_streamer = _data_streamer.begin()->second;
1225  }
1226  else if( _data_streamer.size() > 1)
1227  {
1228  auto it = _data_streamer.find(streamer_name);
1229  if( it != _data_streamer.end())
1230  {
1231  _current_streamer = it->second;
1232  }
1233  else{
1234  qDebug() << "Error. The streamer " << streamer_name <<
1235  " can't be loaded";
1236  return;
1237  }
1238  }
1239 
1240  bool started = false;
1241  try{
1242  // TODO data sources
1243  started = _current_streamer && _current_streamer->start( nullptr );
1244  }
1245  catch(std::runtime_error& err)
1246  {
1247  QMessageBox::warning(this, tr("Exception from the plugin"),
1248  tr("The plugin thrown the following exception: \n\n %1\n")
1249  .arg(err.what()) );
1250  return;
1251  }
1252  if( started )
1253  {
1254  {
1255  std::lock_guard<std::mutex> lock( _current_streamer->mutex() );
1257  }
1258 
1259  for(auto& action: ui->menuStreaming->actions()) {
1260  action->setEnabled(false);
1261  }
1262  ui->actionClearBuffer->setEnabled(true);
1263 
1264  ui->actionStopStreaming->setEnabled(true);
1265  ui->actionDeleteAllData->setToolTip("Stop streaming to be able to delete the data");
1266 
1267  ui->pushButtonStreaming->setEnabled(true);
1268  ui->pushButtonStreaming->setChecked(true);
1269  ui->pushButtonRemoveTimeOffset->setEnabled( false );
1270 
1271  on_streamingSpinBox_valueChanged( ui->streamingSpinBox->value() );
1272  }
1273  else{
1274  qDebug() << "Failed to launch the streamer";
1275  }
1276 }
1277 
1278 void MainWindow::loadPluginState(const QDomElement& root)
1279 {
1280  QDomElement plugins = root.firstChildElement("Plugins");
1281 
1282  for ( QDomElement plugin_elem = plugins.firstChildElement() ;
1283  plugin_elem.isNull() == false;
1284  plugin_elem = plugin_elem.nextSiblingElement() )
1285  {
1286  const QString plugin_name = plugin_elem.attribute("ID");
1287 
1288  if( plugin_elem.nodeName() != "plugin" || plugin_name.isEmpty() )
1289  {
1290  QMessageBox::warning(this, tr("Error loading Plugin State from Layout"),
1291  tr("The method xmlSaveState() must return a node line this <plugin ID=\"PluginName\" ") );
1292  }
1293 
1294  if( _data_loader.find(plugin_name) != _data_loader.end() )
1295  {
1296  _data_loader[plugin_name]->xmlLoadState( plugin_elem );
1297  }
1298  if( _data_streamer.find(plugin_name) != _data_streamer.end() )
1299  {
1300  _data_streamer[plugin_name]->xmlLoadState( plugin_elem );
1301  }
1302  if( _state_publisher.find(plugin_name) != _state_publisher.end() )
1303  {
1304  StatePublisher* publisher = _state_publisher[plugin_name];
1305  publisher->xmlLoadState( plugin_elem );
1306 
1307  if( _autostart_publishers && plugin_elem.attribute("status") == "active" )
1308  {
1309  publisher->setEnabled(true);
1310  }
1311  }
1312  }
1313 }
1314 
1315 QDomElement MainWindow::savePluginState(QDomDocument& doc)
1316 {
1317  QDomElement list_plugins = doc.createElement( "Plugins" );
1318 
1319  auto CheckValidFormat = [this](const QString& expected_name, const QDomElement& elem)
1320  {
1321  if( elem.nodeName() != "plugin" || elem.attribute("ID") != expected_name )
1322  {
1323  QMessageBox::warning(this, tr("Error saving Plugin State to Layout"),
1324  tr("[%1] The method xmlSaveState() must return a node line this <plugin ID=\"PluginName\">")
1325  .arg(expected_name) );
1326  }
1327  };
1328 
1329  for (auto& it: _data_loader)
1330  {
1331  const DataLoader* dataloader = it.second;
1332  QDomElement plugin_elem = dataloader->xmlSaveState(doc);
1333  if( !plugin_elem.isNull() )
1334  {
1335  list_plugins.appendChild( plugin_elem );
1336  CheckValidFormat( it.first, plugin_elem );
1337  }
1338  }
1339 
1340  for (auto& it: _data_streamer)
1341  {
1342  const DataStreamer* datastreamer = it.second;
1343  QDomElement plugin_elem = datastreamer->xmlSaveState(doc);
1344  if( !plugin_elem.isNull() )
1345  {
1346  list_plugins.appendChild( plugin_elem );
1347  CheckValidFormat( it.first, plugin_elem );
1348  }
1349  }
1350 
1351  for (auto& it: _state_publisher)
1352  {
1353  const StatePublisher* state_publisher = it.second;
1354  QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
1355  if( !plugin_elem.isNull() )
1356  {
1357  list_plugins.appendChild( plugin_elem );
1358  CheckValidFormat( it.first, plugin_elem );
1359  }
1360 
1361  plugin_elem.setAttribute("status", state_publisher->enabled() ? "active" : "idle");
1362 
1363  }
1364  return list_plugins;
1365 }
1366 
1367 std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
1368 {
1369  // find min max time
1370  double min_time = std::numeric_limits<double>::max();
1371  double max_time = -std::numeric_limits<double>::max();
1372  int max_steps = 0;
1373 
1374  forEachWidget([&](PlotWidget* widget)
1375  {
1376  for (auto& it: widget->curveList())
1377  {
1378  const auto& curve_name = it.first;
1379 
1380  const auto& data = _mapped_plot_data.numeric.find(curve_name)->second;
1381  if(data.size() >=1)
1382  {
1383  const double t0 = data.front().x;
1384  const double t1 = data.back().x;
1385  min_time = std::min( min_time, t0);
1386  max_time = std::max( max_time, t1);
1387  max_steps = std::max( max_steps, (int)data.size());
1388  }
1389  }
1390  });
1391 
1392  // needed if all the plots are empty
1393  if( max_steps == 0 || max_time < min_time)
1394  {
1395  for (const auto& it: _mapped_plot_data.numeric)
1396  {
1397  const PlotData& data = it.second;
1398  if(data.size() >=1)
1399  {
1400  const double t0 = data.front().x;
1401  const double t1 = data.back().x;
1402  min_time = std::min( min_time, t0);
1403  max_time = std::max( max_time, t1);
1404  max_steps = std::max( max_steps, (int)data.size());
1405  }
1406  }
1407  }
1408 
1409  // last opportunity. Everything else failed
1410  if( max_steps == 0 || max_time < min_time)
1411  {
1412  min_time = 0.0;
1413  max_time = 1.0;
1414  max_steps = 1;
1415  }
1416  return std::tuple<double,double,int>( min_time, max_time, max_steps );
1417 }
1418 
1419 static const QString LAYOUT_VERSION = "2.2.1";
1420 
1421 bool MainWindow::loadLayoutFromFile(QString filename)
1422 {
1423  QSettings settings;
1424 
1425  QFile file(filename);
1426  if (!file.open(QFile::ReadOnly | QFile::Text)) {
1427  QMessageBox::warning(this, tr("Layout"),
1428  tr("Cannot read file %1:\n%2.")
1429  .arg(filename)
1430  .arg(file.errorString()));
1431  return false;
1432  }
1433 
1434  QString errorStr;
1435  int errorLine, errorColumn;
1436 
1437  QDomDocument domDocument;
1438 
1439  if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
1440  QMessageBox::information(window(), tr("XML Layout"),
1441  tr("Parse error at line %1:\n%2")
1442  .arg(errorLine)
1443  .arg(errorStr));
1444  return false;
1445  }
1446 
1447  //-------------------------------------------------
1448  // refresh plugins
1449  QDomElement root = domDocument.namedItem("root").toElement();
1450 
1451  if( !root.hasAttribute("version") && root.attribute("version") != LAYOUT_VERSION )
1452  {
1453  QMessageBox::warning(this, tr("Wrong Layout version"),
1454  tr("This Layout ID is not supported [%1].\nThis version of PlotJuggler use Layout ID [%2]")
1455  .arg(root.attribute("version"))
1456  .arg(LAYOUT_VERSION) );
1457  return false;
1458  }
1459 
1460  loadPluginState(root);
1461  //-------------------------------------------------
1462  QDomElement previously_loaded_datafile = root.firstChildElement( "previouslyLoaded_Datafiles" );
1463 
1464  QDomElement datafile_elem = previously_loaded_datafile.firstChildElement( "fileInfo" );
1465  while( !datafile_elem.isNull() )
1466  {
1467  FileLoadInfo info;
1468  info.filename = datafile_elem.attribute("filename");
1469  info.prefix = datafile_elem.attribute("prefix");
1470 
1471  QDomElement datasources_elem = datafile_elem.firstChildElement( "selected_datasources" );
1472  QString topics_list = datasources_elem.attribute("value");
1473  info.selected_datasources = topics_list.split(";", QString::SkipEmptyParts);
1474 
1475  auto plugin_elem = datafile_elem.firstChildElement( "plugin" );
1476  info.plugin_config.appendChild( info.plugin_config.importNode( plugin_elem, true ) );
1477 
1478  loadDataFromFile( info );
1479  datafile_elem = datafile_elem.nextSiblingElement( "fileInfo" );
1480  }
1481 
1482  QDomElement previousl_streamer = root.firstChildElement( "previouslyLoaded_Streamer" );
1483  if( !previousl_streamer.isNull() )
1484  {
1485  QString streamer_name = previousl_streamer.attribute("name");
1486 
1487  QMessageBox msgBox(this);
1488  msgBox.setWindowTitle("Start Streaming?");
1489  msgBox.setText(tr("Start the previously used streaming plugin?\n\n %1 \n\n").arg(streamer_name));
1490  QPushButton* yes = msgBox.addButton(tr("Yes"), QMessageBox::YesRole);
1491  QPushButton* no = msgBox.addButton(tr("No"), QMessageBox::RejectRole);
1492  msgBox.setDefaultButton(yes);
1493  msgBox.exec();
1494 
1495  if( msgBox.clickedButton() == yes )
1496  {
1497  if( _data_streamer.count(streamer_name) != 0 )
1498  {
1499  on_actionStartStreaming( streamer_name );
1500  }
1501  else{
1502  QMessageBox::warning(this, tr("Error Loading Streamer"),
1503  tr("The streamer named %1 can not be loaded.").arg(streamer_name));
1504  }
1505  }
1506  }
1507  //-------------------------------------------------
1508  // autostart_publishers
1509  QDomElement plugins = root.firstChildElement("Plugins");
1510 
1511  if( ! plugins.isNull() && _autostart_publishers )
1512  {
1513  for ( QDomElement plugin_elem = plugins.firstChildElement() ;
1514  plugin_elem.isNull() == false;
1515  plugin_elem = plugin_elem.nextSiblingElement() )
1516  {
1517  const QString plugin_name = plugin_elem.nodeName();
1518  if( _state_publisher.find(plugin_name) != _state_publisher.end() )
1519  {
1520  StatePublisher* publisher = _state_publisher[plugin_name];
1521 
1522  if( plugin_elem.attribute("status") == "active" )
1523  {
1524  publisher->setEnabled(true);
1525  }
1526  }
1527  }
1528  }
1529  //-------------------------------------------------
1530  auto custom_equations = root.firstChildElement( "customMathEquations" );
1531 
1532  try{
1533  if( !custom_equations.isNull() )
1534  {
1535  for (QDomElement custom_eq = custom_equations.firstChildElement( "snippet" ) ;
1536  custom_eq.isNull() == false;
1537  custom_eq = custom_eq.nextSiblingElement( "snippet" ) )
1538  {
1539  CustomPlotPtr new_custom_plot = CustomFunction::createFromXML(custom_eq);
1540  const auto& name = new_custom_plot->name();
1541  _custom_plots[name] = new_custom_plot;
1542  new_custom_plot->calculateAndAdd( _mapped_plot_data );
1543  _curvelist_widget->addItem( QString::fromStdString( name ) );
1544  }
1546  }
1547  }
1548  catch( std::runtime_error& err)
1549  {
1550  QMessageBox::warning(this, tr("Exception"),
1551  tr("Failed to refresh a customMathEquation \n\n %1\n")
1552  .arg(err.what()) );
1553  }
1554 
1555  QByteArray snippets_saved_xml = settings.value("AddCustomPlotDialog.savedXML",
1556  QByteArray() ).toByteArray();
1557 
1558  auto snippets_element = root.firstChildElement("snippets");
1559  if( !snippets_element.isNull() )
1560  {
1561  auto snippets_previous = GetSnippetsFromXML(snippets_saved_xml);
1562  auto snippets_layout = GetSnippetsFromXML(snippets_element);
1563 
1564  bool snippets_are_different = false;
1565  for(const auto& snippet_it : snippets_layout)
1566  {
1567  auto prev_it = snippets_previous.find( snippet_it.first );
1568 
1569  if( prev_it == snippets_previous.end() ||
1570  prev_it->second.equation != snippet_it.second.equation ||
1571  prev_it->second.globalVars != snippet_it.second.globalVars)
1572  {
1573  snippets_are_different = true;
1574  break;
1575  }
1576  }
1577 
1578  if( snippets_are_different )
1579  {
1580  QMessageBox msgBox(this);
1581  msgBox.setWindowTitle("Overwrite custom transforms?");
1582  msgBox.setText("Your layour file contains a set of custom transforms different from "
1583  "the last one you used.\nant to load these transformations?");
1584  msgBox.addButton(QMessageBox::No);
1585  msgBox.addButton(QMessageBox::Yes);
1586  msgBox.setDefaultButton(QMessageBox::Yes);
1587 
1588  if( msgBox.exec() == QMessageBox::Yes )
1589  {
1590  for(const auto& snippet_it : snippets_layout)
1591  {
1592  snippets_previous[snippet_it.first] = snippet_it.second;
1593  }
1594  QDomDocument doc;
1595  auto snippets_root_element = ExportSnippets( snippets_previous, doc);
1596  doc.appendChild( snippets_root_element );
1597  settings.setValue("AddCustomPlotDialog.savedXML", doc.toByteArray(2));
1598  }
1599  }
1600  }
1601 
1603 
1604  xmlLoadState( domDocument );
1605 
1606  forEachWidget([&](PlotWidget* plot)
1607  {
1608  plot->zoomOut(false);
1609  } );
1610 
1611  _undo_states.clear();
1612  _undo_states.push_back( domDocument );
1613  return true;
1614 }
1615 
1616 
1618 {
1619  this->setFocus();
1620 }
1621 
1623 {
1624  // for (size_t i=0; i< SubWindow::instances().size(); i++)
1625  // {
1626  // if( SubWindow::instances()[i] == object)
1627  // {
1628  // SubWindow::instances().erase( SubWindow::instances().begin() + i);
1629  // break;
1630  // }
1631  // }
1632 }
1633 
1635 {
1636  createTabbedDialog( QString(), first_tab );
1637 }
1638 
1639 void MainWindow::forEachWidget(std::function<void (PlotWidget*, PlotMatrix*, int,int )> operation)
1640 {
1641  auto func = [&](QTabWidget * tabs)
1642  {
1643  for (int t=0; t < tabs->count(); t++)
1644  {
1645  PlotMatrix* matrix = static_cast<PlotMatrix*>(tabs->widget(t));
1646 
1647  for(unsigned row=0; row< matrix->rowsCount(); row++)
1648  {
1649  for(unsigned col=0; col< matrix->colsCount(); col++)
1650  {
1651  PlotWidget* plot = matrix->plotAt(row, col);
1652  operation(plot, matrix, row, col);
1653  }
1654  }
1655  }
1656  };
1657 
1658  for(const auto& it: TabbedPlotWidget::instances())
1659  {
1660  func( it.second->tabWidget() );
1661  }
1662 }
1663 
1664 void MainWindow::forEachWidget(std::function<void (PlotWidget *)> op)
1665 {
1666  forEachWidget( [&](PlotWidget*plot, PlotMatrix*, int,int) { op(plot); } );
1667 }
1668 
1670 {
1671  auto range = calculateVisibleRangeX();
1672 
1673  ui->timeSlider->setLimits(std::get<0>(range),
1674  std::get<1>(range),
1675  std::get<2>(range));
1676 
1677  _tracker_time = std::max( _tracker_time, ui->timeSlider->getMinimum() );
1678  _tracker_time = std::min( _tracker_time, ui->timeSlider->getMaximum() );
1679 }
1680 
1682 {
1683  auto range = calculateVisibleRangeX();
1684  double min_time = std::get<0>(range);
1685 
1686  const bool remove_offset = ui->pushButtonRemoveTimeOffset->isChecked();
1687  if( remove_offset && min_time != std::numeric_limits<double>::max())
1688  {
1689  _time_offset.set( min_time );
1690  }
1691  else{
1692  _time_offset.set( 0.0 );
1693  }
1694 }
1695 
1696 void MainWindow::onSwapPlots(PlotWidget *source, PlotWidget *destination)
1697 {
1698  if( !source || !destination ) return;
1699 
1700  PlotMatrix* src_matrix = nullptr;
1701  PlotMatrix* dst_matrix = nullptr;
1702  QPoint src_pos;
1703  QPoint dst_pos;
1704 
1705  forEachWidget( [&](PlotWidget* plot, PlotMatrix* matrix, int row,int col)
1706  {
1707  if( plot == source ) {
1708  src_matrix = matrix;
1709  src_pos.setX( row );
1710  src_pos.setY( col );
1711  }
1712  else if( plot == destination )
1713  {
1714  dst_matrix = matrix;
1715  dst_pos.setX( row );
1716  dst_pos.setY( col );
1717  }
1718  });
1719 
1720  if(src_matrix && dst_matrix)
1721  {
1722  src_matrix->gridLayout()->removeWidget( source );
1723  dst_matrix->gridLayout()->removeWidget( destination );
1724 
1725  src_matrix->gridLayout()->addWidget( destination, src_pos.x(), src_pos.y() );
1726  dst_matrix->gridLayout()->addWidget( source, dst_pos.x(), dst_pos.y() );
1727 
1728  src_matrix->updateLayout();
1729  if( src_matrix != dst_matrix){
1730  dst_matrix->updateLayout();
1731  }
1732  source->changeBackgroundColor( Qt::white );
1733  destination->changeBackgroundColor( Qt::white );
1734  }
1735  onUndoableChange();
1736 }
1737 
1739 {
1740  if( !_current_streamer )
1741  {
1742  streaming = false;
1743  }
1744 
1745  ui->pushButtonRemoveTimeOffset->setEnabled( !streaming );
1746 
1747  if( streaming )
1748  {
1749  ui->horizontalSpacer->changeSize(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed);
1750  ui->pushButtonStreaming->setText("Streaming ON");
1751  }
1752  else{
1753  _replot_timer->stop( );
1754  ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
1755  ui->pushButtonStreaming->setText("Streaming OFF");
1756  }
1757  ui->streamingLabel->setHidden( !streaming );
1758  ui->streamingSpinBox->setHidden( !streaming );
1759  ui->timeSlider->setHidden( streaming );
1760  ui->pushButtonPlay->setHidden( streaming );
1761 
1762  if( streaming && ui->pushButtonPlay->isChecked() )
1763  {
1764  ui->pushButtonPlay->setChecked(false);
1765  }
1766 
1767  forEachWidget([&](PlotWidget* plot)
1768  {
1769  plot->enableTracker( !streaming );
1770  } );
1771 
1772  emit activateStreamingMode( streaming );
1773 
1774  if( _current_streamer && streaming)
1775  {
1776  _replot_timer->start();
1777  updateTimeOffset();
1778  }
1779  else{
1780  updateDataAndReplot( true );
1781  onUndoableChange();
1782  }
1783 }
1784 
1786 {
1787  if( ui->pushButtonStreaming->isEnabled() )
1788  {
1789  bool streaming = ui->pushButtonStreaming->isChecked();
1790  ui->pushButtonStreaming->setChecked( !streaming );
1791  }
1792  else {
1793  if( ui->pushButtonPlay->isEnabled() )
1794  {
1795  bool playing = ui->pushButtonPlay->isChecked();
1796  ui->pushButtonPlay->setChecked( !playing );
1797  }
1798  }
1799 }
1800 
1801 void MainWindow::updateDataAndReplot(bool replot_hidden_tabs)
1802 {
1803  if( _current_streamer )
1804  {
1805  std::lock_guard<std::mutex> lock( _current_streamer->mutex() );
1806 
1807  auto curvelist_added = _current_streamer->appendData( _mapped_plot_data );
1808 
1809  for(const auto& str: curvelist_added)
1810  {
1811  _curvelist_widget->addItem(str);
1812  }
1813 
1814  if( curvelist_added.size() > 0 )
1815  {
1817  }
1818 
1819  for( auto& custom_it: _custom_plots)
1820  {
1821  auto* dst_plot = &_mapped_plot_data.numeric.at(custom_it.first);
1822  custom_it.second->calculate(_mapped_plot_data, dst_plot);
1823  }
1824  }
1825 
1826  const bool is_streaming_active = isStreamingActive();
1827 
1828  forEachWidget( [is_streaming_active](PlotWidget* plot)
1829  {
1830  plot->updateCurves();
1831  plot->setZoomEnabled( !is_streaming_active );
1832  } );
1833 
1834  //--------------------------------
1835  // trigger again the execution of this callback if steaming == true
1836  if( is_streaming_active )
1837  {
1838  auto range = calculateVisibleRangeX();
1839  double max_time = std::get<1>(range);
1840  _tracker_time = max_time;
1841 
1842  onTrackerTimeUpdated(_tracker_time, false);
1843  }
1844  else{
1845  updateTimeOffset();
1846  updateTimeSlider();
1847  }
1848  //--------------------------------
1849  for(const auto& it: TabbedPlotWidget::instances())
1850  {
1851  if( replot_hidden_tabs )
1852  {
1853  QTabWidget* tabs = it.second->tabWidget();
1854  for (int index=0; index < tabs->count(); index++)
1855  {
1856  PlotMatrix* matrix = static_cast<PlotMatrix*>( tabs->widget(index) );
1857  matrix->maximumZoomOut();
1858  }
1859  }
1860  else{
1861  PlotMatrix* matrix = it.second->currentTab() ;
1862  matrix->maximumZoomOut(); // includes replot
1863  }
1864  }
1865 }
1866 
1868 {
1869  double real_value = value;
1870  if ( value == ui->streamingSpinBox->maximum())
1871  {
1872  real_value = std::numeric_limits<double>::max();
1873  ui->streamingSpinBox->setStyleSheet("QSpinBox { color: red; }");
1874  ui->streamingSpinBox->setSuffix("=inf");
1875  }
1876  else{
1877  ui->streamingSpinBox->setStyleSheet("QSpinBox { color: black; }");
1878  ui->streamingSpinBox->setSuffix(" sec");
1879  }
1880 
1881  if( isStreamingActive() == false)
1882  {
1883  return;
1884  }
1885 
1886  for (auto& it : _mapped_plot_data.numeric )
1887  {
1888  it.second.setMaximumRangeX( real_value );
1889  }
1890 
1891  for (auto& it: _mapped_plot_data.user_defined)
1892  {
1893  it.second.setMaximumRangeX( real_value );
1894  }
1895 
1896  if( _current_streamer )
1897  {
1898  _current_streamer->setMaximumRange( real_value );
1899  }
1900 }
1901 
1903 {
1904  ui->pushButtonStreaming->setChecked(false);
1905  ui->pushButtonStreaming->setEnabled(false);
1906  _replot_timer->stop();
1908  _current_streamer = nullptr;
1909 
1910  for(auto& action: ui->menuStreaming->actions()) {
1911  action->setEnabled(true);
1912  }
1913  ui->actionStopStreaming->setEnabled(false);
1914 
1915  if( !_mapped_plot_data.numeric.empty()){
1916  ui->actionDeleteAllData->setToolTip("");
1917  }
1918 
1919  // reset this.
1920  for(auto& it: _mapped_plot_data.numeric)
1921  {
1922  it.second.setMaximumRangeX( std::numeric_limits<double>::max() );
1923  }
1924  for(auto& it: _mapped_plot_data.user_defined)
1925  {
1926  it.second.setMaximumRangeX( std::numeric_limits<double>::max() );
1927  }
1928 }
1929 
1930 
1932 {
1933  this->close();
1934 }
1935 
1937 {
1938  updateTimeOffset();
1940 
1941  forEachWidget( [](PlotWidget* plot)
1942  {
1943  plot->replot();
1944  } );
1945 
1946  if (this->signalsBlocked() == false) onUndoableChange();
1947 }
1948 
1949 
1951 {
1952  ui->widgetOptions->setVisible( checked );
1953  ui->line->setVisible( checked );
1954 }
1955 
1957 {
1958  QLineEdit* timeLine = ui->displayTime;
1959  const double relative_time = _tracker_time - _time_offset.get();
1960  if( ui->pushButtonUseDateTime->isChecked() )
1961  {
1962  if( ui->pushButtonRemoveTimeOffset->isChecked() )
1963  {
1964  QTime time = QTime::fromMSecsSinceStartOfDay( std::round(relative_time*1000.0));
1965  timeLine->setText( time.toString("HH:mm::ss.zzz") );
1966  }
1967  else{
1968  QDateTime datetime = QDateTime::fromMSecsSinceEpoch( std::round(_tracker_time*1000.0) );
1969  timeLine->setText( datetime.toString("[yyyy MMM dd] HH:mm::ss.zzz") );
1970  }
1971  }
1972  else{
1973  timeLine->setText( QString::number(relative_time, 'f', 3));
1974  }
1975 
1976  QFontMetrics fm( timeLine->font() );
1977  int width = fm.width( timeLine->text()) + 10;
1978  timeLine->setFixedWidth( std::max( 100, width ) );
1979 }
1980 
1982 {
1983  forEachWidget( [checked](PlotWidget* plot) {
1984  plot->activateGrid( checked );
1985  plot->replot();
1986  });
1987 }
1988 
1990 {
1991  forEachWidget( [checked](PlotWidget* plot) {
1992  plot->setConstantRatioXY( checked );
1993  plot->replot();
1994  });
1995 }
1996 
1998 {
1999  if( checked )
2000  {
2001  _publish_timer->start();
2002  _prev_publish_time = QDateTime::currentDateTime();
2003  }
2004  else{
2005  _publish_timer->stop();
2006  }
2007 }
2008 
2010 {
2011  for (auto& it: _mapped_plot_data.numeric )
2012  {
2013  it.second.clear();
2014  }
2015 
2016  for (auto& it: _mapped_plot_data.user_defined )
2017  {
2018  it.second.clear();
2019  }
2020 
2021  forEachWidget( [](PlotWidget* plot) {
2022  plot->reloadPlotData();
2023  plot->replot();
2024  });
2025 }
2026 
2028 {
2030 }
2031 
2033 {
2035  {
2037  }
2038  else if( _tracker_param == CurveTracker::VALUE)
2039  {
2041  }
2043  {
2045  }
2046  ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[ _tracker_param ] );
2047 
2048  forEachWidget( [&](PlotWidget* plot) {
2049  plot->configureTracker(_tracker_param);
2050  plot->replot();
2051  });
2052 }
2053 
2054 void MainWindow::closeEvent(QCloseEvent *event)
2055 {
2056  _replot_timer->stop();
2057  _publish_timer->stop();
2058 
2059  if( _current_streamer )
2060  {
2062  _current_streamer = nullptr;
2063  }
2064  QSettings settings;
2065  settings.setValue("MainWindow.geometry", saveGeometry());
2066  settings.setValue("MainWindow.activateGrid", ui->pushButtonActivateGrid->isChecked() );
2067  settings.setValue("MainWindow.streamingBufferValue", ui->streamingSpinBox->value() );
2068  settings.setValue("MainWindow.removeTimeOffset",ui->pushButtonRemoveTimeOffset->isChecked() );
2069  settings.setValue("MainWindow.dateTimeDisplay", ui->pushButtonUseDateTime->isChecked() );
2070  settings.setValue("MainWindow.timeTrackerSetting", (int)_tracker_param );
2071 
2072  // clean up all the plugins
2073  for(auto& it : _data_loader ) { delete it.second; }
2074  for(auto& it : _state_publisher ) { delete it.second; }
2075  for(auto& it : _data_streamer ) { delete it.second; }
2076 }
2077 
2078 void MainWindow::on_addMathPlot(const std::string& linked_name)
2079 {
2080  addOrEditMathPlot(linked_name, false);
2081 }
2082 
2083 void MainWindow::on_editMathPlot(const std::string &plot_name)
2084 {
2085  addOrEditMathPlot(plot_name, true);
2086 }
2087 
2088 void MainWindow::on_refreshMathPlot(const std::string &plot_name)
2089 {
2090  try{
2091  auto custom_it = _custom_plots.find(plot_name);
2092  if(custom_it == _custom_plots.end())
2093  {
2094  qWarning("failed to find custom equation");
2095  return;
2096  }
2097  CustomPlotPtr ce = custom_it->second;
2098 
2099  ce->calculateAndAdd(_mapped_plot_data);
2100 
2102  updateDataAndReplot( true );
2103  }
2104  catch(const std::runtime_error &e)
2105  {
2106  QMessageBox::critical(this, "error", "Failed to refresh data : " + QString::fromStdString(e.what()));
2107  }
2108 }
2109 
2110 void MainWindow::addOrEditMathPlot(const std::string &name, bool modifying)
2111 {
2113 
2114  if(!modifying)
2115  {
2116  dialog.setLinkedPlotName(QString::fromStdString(name));
2118  }
2119  else
2120  {
2122 
2123  auto custom_it = _custom_plots.find(name);
2124  if(custom_it == _custom_plots.end())
2125  {
2126  qWarning("failed to find custom equation");
2127  return;
2128  }
2129 
2130  // clear already existing data first
2131  auto data_it = _mapped_plot_data.numeric.find( name );
2132  if( data_it != _mapped_plot_data.numeric.end())
2133  {
2134  data_it->second.clear();
2135  }
2136  dialog.editExistingPlot(custom_it->second);
2137  }
2138 
2139  if(dialog.exec() == QDialog::Accepted)
2140  {
2141  const QString& qplot_name = dialog.getName();
2142  std::string plot_name = qplot_name.toStdString();
2143  CustomPlotPtr eq = dialog.getCustomPlotData();
2144 
2145  try {
2146  eq->calculateAndAdd(_mapped_plot_data);
2147  }
2148  catch(std::exception& ex)
2149  {
2150  QMessageBox::warning(this, tr("Warning"),
2151  tr("Failed to create the custom timeseries. Error:\n\n%1")
2152  .arg( ex.what() ) );
2153 
2154  return;
2155  }
2156 
2157  // keep data for reference
2158  auto custom_it = _custom_plots.find(plot_name);
2159  if( custom_it == _custom_plots.end() )
2160  {
2161  _custom_plots.insert( {plot_name, eq} );
2162  }
2163  else{
2164  custom_it->second = eq;
2165  modifying = true;
2166  }
2167 
2168  if(!modifying)
2169  {
2170  _curvelist_widget->addItem(qplot_name);
2171  }
2173 
2174  if(modifying)
2175  {
2176  updateDataAndReplot( true );
2177  }
2178  }
2179 }
2180 
2182 {
2183  qint64 delta_ms = (QDateTime::currentMSecsSinceEpoch() - _prev_publish_time.toMSecsSinceEpoch());
2184  _prev_publish_time = QDateTime::currentDateTime();
2185  delta_ms = std::max( (qint64)_publish_timer->interval(), delta_ms);
2186 
2187  _tracker_time += delta_ms*0.001*ui->playbackRate->value();
2188  if( _tracker_time >= ui->timeSlider->getMaximum())
2189  {
2190  if( !ui->playbackLoop->isChecked() )
2191  {
2192  ui->pushButtonPlay->setChecked(false);
2193  }
2194  _tracker_time = ui->timeSlider->getMinimum();
2195  }
2197  auto prev = ui->timeSlider->blockSignals(true);
2198  ui->timeSlider->setRealValue( _tracker_time );
2199  ui->timeSlider->blockSignals(prev);
2200 
2204 
2205  for ( auto& it: _state_publisher)
2206  {
2207  it.second->play( _tracker_time );
2208  }
2209 
2210  forEachWidget( [&](PlotWidget* plot)
2211  {
2212  plot->setTrackerPosition( _tracker_time );
2213  plot->replot();
2214  } );
2215 }
2216 
2218 {
2219  QDesktopServices::openUrl( QUrl( "https://github.com/facontidavide/PlotJuggler/issues" ));
2220 }
2221 
2223 {
2224  QDialog* dialog = new QDialog(this);
2225  auto ui = new Ui::AboutDialog();
2226  ui->setupUi(dialog);
2227 
2228  ui->label_version->setText( QApplication::applicationVersion() );
2229  dialog->setAttribute(Qt::WA_DeleteOnClose);
2230 
2231  dialog->exec();
2232 }
2233 
2235 {
2236  QSettings settings;
2237 
2238  HelpVideo* dialog = new HelpVideo(this);
2239  dialog->restoreGeometry(settings.value("Cheatsheet.geometry").toByteArray());
2240  dialog->setAttribute(Qt::WA_DeleteOnClose);
2241  dialog->show();
2242 
2243  connect(dialog, &QDialog::finished, this, [this, dialog]()
2244  {
2245  QSettings settings;
2246  settings.setValue("Cheatsheet.geometry", dialog->saveGeometry());
2247  } );
2248 }
2249 
2251 {
2252  QDialog* dialog = new QDialog(this);
2253  auto ui = new Ui::SupportDialog();
2254  ui->setupUi(dialog);
2255 
2256  dialog->setAttribute(Qt::WA_DeleteOnClose);
2257 
2258  dialog->exec();
2259 }
2260 
2262 {
2263  QSettings settings;
2264  QString directory_path = settings.value("MainWindow.saveAllPlotTabs",
2265  QDir::currentPath() ).toString();
2266  // Get destination folder
2267  QFileDialog saveDialog;
2268  saveDialog.setDirectory(directory_path);
2269  saveDialog.setFileMode(QFileDialog::FileMode::Directory);
2270  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
2271  saveDialog.exec();
2272 
2273  uint image_number = 1;
2274  if(saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
2275  {
2276  // Save Plots
2277  QString directory = saveDialog.selectedFiles().first();
2278  settings.setValue("MainWindow.saveAllPlotTabs", directory);
2279 
2280  QStringList file_names;
2281  QStringList existing_files;
2282  QDateTime current_date_time(QDateTime::currentDateTime());
2283  QString current_date_time_name(current_date_time.toString("yyyy-MM-dd_HH-mm-ss"));
2284  for(const auto& it: TabbedPlotWidget::instances())
2285  {
2286  auto tab_widget = it.second->tabWidget();
2287  for(int i=0; i< tab_widget->count(); i++)
2288  {
2289  PlotMatrix* matrix = static_cast<PlotMatrix*>( tab_widget->widget(i) );
2290  QString name = QString("%1/%2_%3_%4.png")
2291  .arg(directory)
2292  .arg(current_date_time_name)
2293  .arg(image_number, 2, 10, QLatin1Char('0')).arg(matrix->name());
2294  file_names.push_back( name );
2295  image_number++;
2296 
2297  QFileInfo check_file( file_names.back() );
2298  if( check_file.exists() && check_file.isFile() )
2299  {
2300  existing_files.push_back( name );
2301  }
2302  }
2303  }
2304  if( existing_files.isEmpty() == false)
2305  {
2306  QMessageBox msgBox;
2307  msgBox.setText("One or more files will be overwritten. ant to continue?");
2308  QString all_files;
2309  for(const auto& str: existing_files)
2310  {
2311  all_files.push_back("\n");
2312  all_files.append(str);
2313  }
2314  msgBox.setInformativeText(all_files);
2315  msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok );
2316  msgBox.setDefaultButton(QMessageBox::Ok);
2317 
2318  if( msgBox.exec() != QMessageBox::Ok)
2319  {
2320  return;
2321  }
2322  }
2323 
2324  image_number = 0;
2325  for(const auto& it: TabbedPlotWidget::instances())
2326  {
2327  auto tab_widget = it.second->tabWidget();
2328  for(int i=0; i< tab_widget->count(); i++)
2329  {
2330  PlotMatrix* matrix = static_cast<PlotMatrix*>( tab_widget->widget(i) );
2331  TabbedPlotWidget::saveTabImage( file_names[image_number], matrix);
2332  image_number++;
2333  }
2334  }
2335  }
2336 }
2337 
2339 {
2340  if( _data_loader.empty())
2341  {
2342  QMessageBox::warning(this, tr("Warning"),
2343  tr("No plugin was loaded to process a data file\n") );
2344  return;
2345  }
2346 
2347  QSettings settings;
2348 
2349  QString file_extension_filter;
2350 
2351  std::set<QString> extensions;
2352 
2353  for (auto& it: _data_loader)
2354  {
2355  DataLoader* loader = it.second;
2356  for (QString extension: loader->compatibleFileExtensions() )
2357  {
2358  extensions.insert( extension.toLower() );
2359  }
2360  }
2361 
2362  for (const auto& it: extensions)
2363  {
2364  file_extension_filter.append( QString(" *.") + it );
2365  }
2366 
2367  QString directory_path = settings.value("MainWindow.lastDatafileDirectory", QDir::currentPath() ).toString();
2368 
2369  QFileDialog loadDialog( this );
2370  loadDialog.setFileMode(QFileDialog::ExistingFiles);
2371  loadDialog.setViewMode(QFileDialog::Detail);
2372  loadDialog.setNameFilter(file_extension_filter);
2373  loadDialog.setDirectory(directory_path);
2374 
2375  QStringList fileNames;
2376  if (loadDialog.exec())
2377  {
2378  fileNames = loadDialog.selectedFiles();
2379  }
2380 
2381  if (fileNames.isEmpty()) {
2382  return;
2383  }
2384 
2385  directory_path = QFileInfo(fileNames[0]).absolutePath();
2386  settings.setValue("MainWindow.lastDatafileDirectory", directory_path);
2387 
2388  if( loadDataFromFiles(fileNames) )
2389  {
2390  updateRecentDataMenu(fileNames);
2391  }
2392 }
2393 
2395 {
2396  QSettings settings;
2397 
2398  QString directory_path = settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
2399  QString filename = QFileDialog::getOpenFileName(this, "Open Layout",
2400  directory_path, "*.xml");
2401  if (filename.isEmpty()){
2402  return;
2403  }
2404 
2405  if( loadLayoutFromFile(filename) )
2406  {
2407  updateRecentLayoutMenu( {filename} );
2408  }
2409 
2410  directory_path = QFileInfo(filename).absolutePath();
2411  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
2412 }
2413 
2415 
2416 {
2417  QDomDocument doc = xmlSaveState();
2418 
2419  QSettings settings;
2420 
2421  QString directory_path = settings.value("MainWindow.lastLayoutDirectory",
2422  QDir::currentPath() ).toString();
2423 
2424  QFileDialog saveDialog;
2425  saveDialog.setOption(QFileDialog::DontUseNativeDialog, true);
2426 
2427  QGridLayout *save_layout = static_cast<QGridLayout*>(saveDialog.layout());
2428 
2429  QFrame* frame = new QFrame;
2430  frame->setFrameStyle(QFrame::Box | QFrame::Plain);
2431  frame->setLineWidth(1);
2432 
2433  QVBoxLayout *vbox = new QVBoxLayout;
2434  QLabel* title = new QLabel("Save Layout options");
2435  QFrame* separator = new QFrame;
2436  separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
2437 
2438  auto checkbox_datasource = new QCheckBox("Save data source");
2439  checkbox_datasource->setToolTip("ant the layout to remember the source of your data,\n"
2440  "i.e. the Datafile used or the Streaming Plugin loaded ?");
2441  checkbox_datasource->setFocusPolicy( Qt::NoFocus );
2442  checkbox_datasource->setChecked( settings.value("MainWindow.saveLayoutDataSource", true).toBool() );
2443 
2444  auto checkbox_snippets = new QCheckBox("Save custom transformations");
2445  checkbox_snippets->setToolTip("Do you want the layout to save the custom transformations?");
2446  checkbox_snippets->setFocusPolicy( Qt::NoFocus );
2447  checkbox_snippets->setChecked( settings.value("MainWindow.saveLayoutSnippets", true).toBool() );
2448 
2449  vbox->addWidget(title);
2450  vbox->addWidget(separator);
2451  vbox->addWidget(checkbox_datasource);
2452  vbox->addWidget(checkbox_snippets);
2453  frame->setLayout(vbox);
2454 
2455  int rows = save_layout->rowCount();
2456  int col = save_layout->columnCount();
2457  save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
2458 
2459  saveDialog.setAcceptMode(QFileDialog::AcceptSave);
2460  saveDialog.setDefaultSuffix("xml");
2461  saveDialog.setNameFilter("XML (*.xml)");
2462  saveDialog.setDirectory(directory_path);
2463  saveDialog.exec();
2464 
2465  if(saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
2466  {
2467  return;
2468  }
2469 
2470  QString fileName = saveDialog.selectedFiles().first();
2471 
2472  if (fileName.isEmpty()){
2473  return;
2474  }
2475 
2476  directory_path = QFileInfo(fileName).absolutePath();
2477  settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
2478  settings.setValue("MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked() );
2479  settings.setValue("MainWindow.saveLayoutSnippets", checkbox_snippets->isChecked() );
2480 
2481  QDomElement root = doc.namedItem("root").toElement();
2482  root.setAttribute("version", LAYOUT_VERSION);
2483 
2484  root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
2485 
2486  root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
2487 
2488  root.appendChild( savePluginState(doc) );
2489 
2490  root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
2491 
2492  if( checkbox_datasource->isChecked() )
2493  {
2494  QDomElement loaded_list = doc.createElement( "previouslyLoaded_Datafiles" );
2495 
2496  for(const auto& loaded: _loaded_datafiles)
2497  {
2498  QDomElement file_elem = doc.createElement( "fileInfo" );
2499  file_elem.setAttribute("filename", loaded.filename );
2500  file_elem.setAttribute("prefix", loaded.prefix );
2501 
2502  QDomElement datasources_elem = doc.createElement( "selected_datasources" );
2503  QString topics_list = loaded.selected_datasources.join(";");
2504  datasources_elem.setAttribute("value", topics_list);
2505  file_elem.appendChild( datasources_elem );
2506 
2507  file_elem.appendChild( loaded.plugin_config.firstChild() );
2508  loaded_list.appendChild( file_elem );
2509  }
2510  root.appendChild( loaded_list );
2511 
2512  if( _current_streamer )
2513  {
2514  QDomElement loaded_streamer = doc.createElement( "previouslyLoaded_Streamer" );
2515  QString streamer_name = _current_streamer->name();
2516  streamer_name.replace(" ", "_");
2517  loaded_streamer.setAttribute("name", streamer_name );
2518  root.appendChild( loaded_streamer );
2519  }
2520  }
2521  //-----------------------------------
2522  root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
2523  if( checkbox_snippets->isChecked() )
2524  {
2525  QDomElement custom_equations = doc.createElement("customMathEquations");
2526  for (const auto& custom_it: _custom_plots)
2527  {
2528  const auto& custom_plot = custom_it.second;
2529  custom_equations.appendChild( custom_plot->xmlSaveState(doc) );
2530  }
2531  root.appendChild(custom_equations);
2532 
2533  QByteArray snippets_xml_text = settings.value("AddCustomPlotDialog.savedXML",
2534  QByteArray() ).toByteArray();
2535  auto snipped_saved = GetSnippetsFromXML(snippets_xml_text);
2536  auto snippets_root = ExportSnippets( snipped_saved, doc);
2537  root.appendChild(snippets_root);
2538  }
2539  root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
2540  //------------------------------------
2541  QFile file(fileName);
2542  if (file.open(QIODevice::WriteOnly)) {
2543  QTextStream stream(&file);
2544  stream << doc.toString() << endl;
2545  }
2546 }
2547 
2549 {
2550  static bool first_call = true;
2551  if( first_call && !_minimized )
2552  {
2553  first_call = false;
2554  QMessageBox::information(this, "Remember!", "Press F10 to switch back to the normal view");
2555  }
2556 
2558 
2559  ui->leftFrame->setVisible(!_minimized);
2560  ui->widgetOptions->setVisible( !_minimized && ui->pushButtonOptions->isChecked() );
2561  ui->widgetTimescale->setVisible(!_minimized);
2562  ui->menuBar->setVisible(!_minimized);
2563 
2564  for (auto& it: TabbedPlotWidget::instances() )
2565  {
2566  it.second->setControlsVisible( !_minimized );
2567  }
2568 }
2569 
2571 {
2572  buildDummyData();
2573 }
2574 
2576 {
2579  dialog.exec();
2580 }
2581 
2583 {
2584  QMenu* menu = ui->menuRecentData;
2585  for (QAction *action: menu->actions())
2586  {
2587  if ( action->isSeparator() ){
2588  break;
2589  }
2590  menu->removeAction( action );
2591  }
2592  menu->setEnabled(false);
2593  QSettings settings;
2594  settings.setValue("MainWindow.recentlyLoadedDatafile", {} );
2595 }
2596 
2598 {
2599  QMenu* menu = ui->menuRecentLayout;
2600  for (QAction *action: menu->actions())
2601  {
2602  if ( action->isSeparator() ){
2603  break;
2604  }
2605  menu->removeAction( action );
2606  }
2607  menu->setEnabled(false);
2608  QSettings settings;
2609  settings.setValue("MainWindow.recentlyLoadedLayout", {} );
2610 }
2611 
2613 {
2614  QMessageBox msgBox(this);
2615  msgBox.setWindowTitle("Warning. Can't be undone.");
2616  msgBox.setText(tr("Do you want to remove the previously loaded data?\n"));
2617  msgBox.addButton(QMessageBox::No);
2618  msgBox.addButton(QMessageBox::Yes);
2619  msgBox.setDefaultButton(QMessageBox::Yes);
2620  // QPushButton* buttonPlaceholder = msgBox.addButton(tr("Keep empty placeholders"), QMessageBox::NoRole);
2621  auto reply = msgBox.exec();
2622 
2623  if( reply == QMessageBox::No ) {
2624  return;
2625  }
2626 
2627 // if( msgBox.clickedButton() == buttonPlaceholder )
2628 // {
2629 // for( auto& it: _mapped_plot_data.numeric )
2630 // {
2631 // it.second.clear();
2632 // }
2633 // for( auto& it: _mapped_plot_data.user_defined )
2634 // {
2635 // it.second.clear();
2636 // }
2637 
2638 // for(const auto& it: TabbedPlotWidget::instances())
2639 // {
2640 // PlotMatrix* matrix = it.second->currentTab() ;
2641 // matrix->maximumZoomOut(); // includes replot
2642 // }
2643 // }
2644 // else
2645  {
2646  deleteAllData();
2647  }
2648 }
2649 
2650 
2651 
2652 
void on_actionExit_triggered()
void setConstantRatioXY(bool active)
Definition: plotwidget.cpp:936
bool is2ndColumnHidden() const
void onRedoInvoked()
Definition: mainwindow.cpp:234
void on_actionSaveAllPlotTabs_triggered()
void addItem(const QString &item_name)
void on_actionClearBuffer_triggered()
int findRowByName(const std::string &text) const
virtual bool xmlLoadState(const QDomElement &parent_element)
DataStreamer * _current_streamer
Definition: mainwindow.h:106
QGridLayout * gridLayout()
Definition: plotmatrix.cpp:369
std::unordered_map< std::string, PlotData > numeric
Definition: plotdata.h:144
void on_actionStopStreaming_triggered()
filename
QShortcut _streaming_shortcut
Definition: mainwindow.h:93
QTimer * _replot_timer
Definition: mainwindow.h:126
void on_actionSupportPlotJuggler_triggered()
PlotDataMapRef & dataMap()
Ui::MainWindow * ui
Definition: mainwindow.h:86
void removeRow(int row)
void initializePlugins(QString subdir_name)
Definition: mainwindow.cpp:436
void on_actionSaveLayout_triggered()
std::mutex & mutex()
QShortcut _fullscreen_shortcut
Definition: mainwindow.h:92
void replot() override
void connectionClosed()
void on_actionFunctionEditor_triggered()
QString filename
void onPlotMatrixAdded(PlotMatrix *matrix)
Definition: mainwindow.cpp:668
void editMathPlot(const std::string &plot_name)
static void saveTabImage(QString fileName, PlotMatrix *matrix)
const std::map< std::string, QwtPlotCurve * > & curveList() const
Definition: plotwidget.cpp:478
CurveListPanel * _curvelist_widget
Definition: mainwindow.h:98
void pushBack(Point p)
Definition: plotdata.h:214
void updatedDisplayTime()
bool _test_option
Definition: mainwindow.h:113
void on_changeDateTimeScale(bool enable)
std::vector< FileLoadInfo > _loaded_datafiles
Definition: mainwindow.h:119
QTimer * _publish_timer
Definition: mainwindow.h:127
void clearBuffers()
void deleteAllData()
Definition: mainwindow.cpp:932
void resizeEvent(QResizeEvent *)
Definition: mainwindow.cpp:625
void undoableChange()
bool loadDataFromFiles(QStringList filenames)
void setLinkedPlotName(const QString &linkedPlotName)
void checkAllCurvesFromLayout(const QDomElement &root)
Definition: mainwindow.cpp:699
void detachAllCurves()
Definition: plotwidget.cpp:593
PlotDataMapRef _mapped_plot_data
Definition: mainwindow.h:100
void closeEvent(QCloseEvent *event)
std::map< CurveTracker::Parameter, QIcon > _tracker_button_icons
Definition: mainwindow.h:122
bool xmlLoadState(QDomDocument state_document)
Definition: mainwindow.cpp:753
bool loadLayoutFromFile(QString filename)
QStandardItemModel * getTableModel() const
void on_actionReportBug_triggered()
virtual const std::vector< const char * > & compatibleFileExtensions() const =0
void on_actionClearRecentLayout_triggered()
void swapWidgetsRequested(PlotWidget *source, PlotWidget *destination)
Op && op
void on_actionCheatsheet_triggered()
void setEditorMode(EditorMode mode)
void onTrackerMovedFromWidget(QPointF pos)
Definition: mainwindow.cpp:329
void on_actionClearRecentData_triggered()
std::deque< QDomDocument > _redo_states
Definition: mainwindow.h:109
QTableView * getTableView() const
void onPlotAdded(PlotWidget *plot)
Definition: mainwindow.cpp:631
void updateDataAndReplot(bool replot_hidden_tabs)
void refreshMathPlot(const std::string &curve_name)
void on_actionStartStreaming(QString streamer_name)
void onTimeSlider_valueChanged(double abs_time)
Definition: mainwindow.cpp:340
void createMathPlot(const std::string &linked_plot)
virtual void shutdown()=0
PlotWidget * plotAt(unsigned row, unsigned column)
Definition: plotmatrix.cpp:223
void addOrEditMathPlot(const std::string &name, bool edit)
const QString & name() const
Definition: plotmatrix.cpp:364
MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent=nullptr)
Definition: mainwindow.cpp:45
std::unordered_map< std::string, PlotDataAny > user_defined
Definition: plotdata.h:145
void on_pushButtonOptions_toggled(bool checked)
void trackerMoved(QPointF pos)
void initializeActions()
Definition: mainwindow.cpp:397
QDomElement ExportSnippets(const SnippetsMap &snippets, QDomDocument &doc)
double _tracker_time
Definition: mainwindow.h:117
void onSwapPlots(PlotWidget *source, PlotWidget *destination)
void reloadPlotData()
Definition: plotwidget.cpp:980
void on_pushButtonPlay_toggled(bool checked)
void buildDummyData()
Definition: mainwindow.cpp:561
void on_actionLoadDummyData_triggered()
QElapsedTimer _undo_timer
Definition: mainwindow.h:110
void on_pushButtonStreaming_toggled(bool streaming)
TFSIMD_FORCE_INLINE const tfScalar & x() const
bool loadDataFromFile(const FileLoadInfo &info)
void updateCurves()
void on_actionLoadLayout_triggered()
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)
Definition: mainwindow.cpp:964
void onTrackerTimeUpdated(double absolute_time, bool do_replot)
Definition: mainwindow.cpp:346
TabbedPlotWidget * _main_tabbed_widget
Definition: mainwindow.h:88
void curvesDropped()
void on_editMathPlot(const std::string &plot_name)
virtual bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const
Definition: pj_plugin.h:21
void on_actionFullscreen_triggered()
void updateRecentLayoutMenu(QStringList new_filenames)
Definition: mainwindow.cpp:887
QShortcut _playback_shotcut
Definition: mainwindow.h:94
void onPlaybackLoop()
CustomPlotPtr getCustomPlotData() const
virtual const char * name() const =0
void loadPluginState(const QDomElement &root)
T value
bool xmlLoadState(QDomElement &tabbed_area)
unsigned colsCount() const
Definition: plotmatrix.cpp:188
std::shared_ptr< CustomFunction > CustomPlotPtr
void deleteCurves(const std::vector< std::string > &curve_names)
void setStreamingMode(bool streaming_mode)
const Point & front() const
Definition: plotdata.h:112
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void on_refreshMathPlot(const std::string &plot_name)
void on_tabbedAreaDestroyed(QObject *object)
static const QString LAYOUT_VERSION
QDateTime _prev_publish_time
Definition: mainwindow.h:129
CurveTracker::Parameter _tracker_param
Definition: mainwindow.h:120
std::map< QString, DataStreamer * > _data_streamer
Definition: mainwindow.h:105
void updateTimeSlider()
void on_pushButtonRatio_toggled(bool checked)
void on_streamingSpinBox_valueChanged(int value)
void onUndoInvoked()
Definition: mainwindow.cpp:250
void configureTracker(CurveTracker::Parameter val)
unsigned rowsCount() const
Definition: plotmatrix.cpp:183
double get() const
Definition: utils.h:23
QDomElement savePluginState(QDomDocument &doc)
QShortcut _redo_shortcut
Definition: mainwindow.h:91
bool isStreamingActive() const
void createTabbedDialog(QString suggest_win_name, PlotMatrix *first_tab)
Definition: mainwindow.cpp:366
static TabbedPlotWidget * instance(const QString &key)
bool _minimized
Definition: mainwindow.h:96
void on_actionDeleteAllData_triggered()
virtual size_t size() const
Definition: plotdata.h:308
char name[1]
CustomPlotMap _custom_plots
Definition: mainwindow.h:101
void onDeleteMultipleCurves(const std::vector< std::string > &curve_names)
Definition: mainwindow.cpp:815
void undoableChange()
void curveListChanged()
bool _autostart_publishers
Definition: mainwindow.h:115
MonitoredValue _time_offset
Definition: mainwindow.h:124
action
QStringList selected_datasources
void on_addMathPlot(const std::string &linked_name)
void changeBackgroundColor(QColor color)
static CustomPlotPtr createFromXML(QDomElement &element)
void on_actionAbout_triggered()
std::deque< QDomDocument > _undo_states
Definition: mainwindow.h:108
void set(double newValue)
Definition: utils.h:14
virtual bool readDataFromFile(FileLoadInfo *fileload_info, PlotDataMapRef &destination)=0
void setMaximumRange(double range)
virtual bool isDebugPlugin()
Definition: pj_plugin.h:17
void editExistingPlot(CustomPlotPtr data)
void updateLayout()
Definition: plotmatrix.cpp:328
void on_actionLoadData_triggered()
void plotAdded(PlotWidget *)
void onFloatingWindowDestroyed(QObject *object)
void setTrackerPosition(double abs_time)
void setZoomEnabled(bool enabled)
void on_streamingToggled()
void activateStreamingMode(bool active)
Table t1
void on_changeTimeOffset(double offset)
void * arg
void hiddenItemsChanged()
void on_pushButtonUseDateTime_toggled(bool checked)
void requestRemoveCurveByName(const std::string &name)
void updateTimeOffset()
std::unordered_map< std::string, PlotData >::iterator addNumeric(const std::string &name)
Definition: plotdata.h:147
void AddPrefixToPlotData(const std::string &prefix, std::unordered_map< std::string, Value > &data)
Definition: plotdata.h:169
void forEachWidget(std::function< void(PlotWidget *, PlotMatrix *, int, int)> op)
QShortcut _undo_shortcut
Definition: mainwindow.h:90
std::map< QString, DataLoader * > _data_loader
Definition: mainwindow.h:103
eq
int rowCount() const
QTableView * getCustomView() const
virtual bool enabled() const =0
const Point & back() const
Definition: plotdata.h:114
void on_pushButtonActivateGrid_toggled(bool checked)
void importPlotDataMap(PlotDataMapRef &new_data, bool remove_old)
void on_pushButtonTimeTracker_pressed()
bool _disable_undo_logging
Definition: mainwindow.h:111
void onUndoableChange()
Definition: mainwindow.cpp:214
virtual bool start(QStringList *)=0
QString getName() const
empty_struct data[sizeof(T)/sizeof(empty_struct)]
std::map< QString, StatePublisher * > _state_publisher
Definition: mainwindow.h:104
TabbedPlotWidget * tabbedWidget()
Definition: subwindow.h:16
int i
The DataStreamer base class to create your own plugin.
QDomDocument xmlSaveState() const
Definition: mainwindow.cpp:674
void activateGrid(bool activate)
virtual std::vector< QString > appendData(PlotDataMapRef &destination)
void removeCurve(const std::string &name)
Definition: plotwidget.cpp:426
#define C(x)
void on_splitterMoved(int, int)
Definition: mainwindow.cpp:611
void enableTracker(bool enable)
virtual bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const
void maximumZoomOut()
Definition: plotmatrix.cpp:400
void on_pushButtonRemoveTimeOffset_toggled(bool checked)
void updateRecentDataMenu(QStringList new_filenames)
Definition: mainwindow.cpp:846
void onUpdateLeftTableValues()
Definition: mainwindow.cpp:267
std::tuple< double, double, int > calculateVisibleRangeX()
virtual void setEnabled(bool enabled)
QDomDocument plugin_config
static const std::map< QString, TabbedPlotWidget * > & instances()
void onCreateFloatingWindow(PlotMatrix *first_tab=nullptr)


plotjuggler
Author(s): Davide Faconti
autogenerated on Sat Jul 6 2019 03:44:17