mainwindow.cpp
Go to the documentation of this file.
00001 #include <functional>
00002 #include <stdio.h>
00003 #include <numeric>
00004 
00005 #include <QActionGroup>
00006 #include <QCheckBox>
00007 #include <QCommandLineParser>
00008 #include <QDebug>
00009 #include <QDesktopServices>
00010 #include <QDomDocument>
00011 #include <QDoubleSpinBox>
00012 #include <QElapsedTimer>
00013 #include <QFileDialog>
00014 #include <QInputDialog>
00015 #include <QMenu>
00016 #include <QGroupBox>
00017 #include <QMessageBox>
00018 #include <QMimeData>
00019 #include <QMouseEvent>
00020 #include <QPluginLoader>
00021 #include <QPushButton>
00022 #include <QKeySequence>
00023 #include <QScrollBar>
00024 #include <QSettings>
00025 #include <QStringListModel>
00026 #include <QStringRef>
00027 #include <QThread>
00028 #include <QTextStream>
00029 #include <QWindow>
00030 #include <QHeaderView>
00031 
00032 #include "mainwindow.h"
00033 #include "curvelist_panel.h"
00034 #include "tabbedplotwidget.h"
00035 #include "selectlistdialog.h"
00036 #include "PlotJuggler/plotdata.h"
00037 #include "qwt_plot_canvas.h"
00038 #include "transforms/function_editor.h"
00039 #include "utils.h"
00040 
00041 #include "ui_aboutdialog.h"
00042 #include "ui_support_dialog.h"
00043 #include "cheatsheet/video_cheatsheet.h"
00044 
00045 MainWindow::MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent) :
00046     QMainWindow(parent),
00047     ui(new Ui::MainWindow),
00048     _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this),
00049     _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this),
00050     _fullscreen_shortcut(Qt::Key_F10, this),
00051     _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this),
00052     _playback_shotcut(Qt::Key_Space, this),
00053     _minimized(false),
00054     _current_streamer(nullptr),
00055     _disable_undo_logging(false),
00056     _tracker_time(0),
00057     _tracker_param( CurveTracker::VALUE )
00058 {
00059     QLocale::setDefault(QLocale::c()); // set as default
00060 
00061     _test_option = commandline_parser.isSet("test");
00062     _autostart_publishers = commandline_parser.isSet("publish");
00063 
00064     _curvelist_widget = new CurveListPanel(_custom_plots, this);
00065 
00066     ui->setupUi(this);
00067 
00068     if( commandline_parser.isSet("buffer_size"))
00069     {
00070         int buffer_size = std::max(10, commandline_parser.value("buffer_size").toInt() );
00071         ui->streamingSpinBox->setMaximum(buffer_size);
00072     }
00073 
00074     {
00075         QIcon icon(":/icons/resources/light/office_chart_line_stacked.png");
00076         if (!icon.isNull())
00077         {
00078             this->setWindowIcon(icon);
00079             QApplication::setWindowIcon(icon);
00080         }
00081     }
00082 
00083     connect( _curvelist_widget->getTableView()->verticalScrollBar(), &QScrollBar::sliderMoved,
00084              this, &MainWindow::onUpdateLeftTableValues );
00085 
00086     connect( _curvelist_widget, &CurveListPanel::hiddenItemsChanged,
00087              this, &MainWindow::onUpdateLeftTableValues );
00088 
00089     connect(_curvelist_widget, &CurveListPanel::deleteCurves,
00090             this, &MainWindow::onDeleteMultipleCurves );
00091 
00092     connect(_curvelist_widget, &CurveListPanel::createMathPlot,
00093             this, &MainWindow::on_addMathPlot);
00094 
00095     connect(_curvelist_widget, &CurveListPanel::editMathPlot,
00096             this, &MainWindow::on_editMathPlot);
00097 
00098     connect(_curvelist_widget, &CurveListPanel::refreshMathPlot,
00099             this, &MainWindow::on_refreshMathPlot);
00100 
00101     connect(_curvelist_widget->getTableView()->verticalScrollBar(), &QScrollBar::valueChanged,
00102             this, &MainWindow::onUpdateLeftTableValues );
00103 
00104     connect( ui->timeSlider, &RealSlider::realValueChanged,
00105              this, &MainWindow::onTimeSlider_valueChanged );
00106 
00107     connect( ui->playbackRate, &QDoubleSpinBox::editingFinished, this, [this]()
00108     {
00109         ui->playbackRate->clearFocus();
00110     });
00111 
00112 
00113     _main_tabbed_widget = new TabbedPlotWidget("Main Window", this, nullptr, _mapped_plot_data, this);
00114 
00115     ui->plottingLayout->insertWidget(0, _main_tabbed_widget, 1);
00116     ui->leftLayout->addWidget( _curvelist_widget );
00117 
00118     ui->splitter->setCollapsible(0,true);
00119     ui->splitter->setStretchFactor(0,2);
00120     ui->splitter->setStretchFactor(1,6);
00121 
00122     connect( ui->splitter, SIGNAL(splitterMoved(int,int)), SLOT(on_splitterMoved(int,int)) );
00123 
00124     initializeActions();
00125     initializePlugins( QCoreApplication::applicationDirPath() );
00126     initializePlugins("/usr/local/PlotJuggler/plugins");
00127 
00128     _undo_timer.start();
00129 
00130     // save initial state
00131     onUndoableChange();
00132 
00133     _replot_timer = new QTimer(this);
00134     _replot_timer->setInterval(40);
00135     connect(_replot_timer, &QTimer::timeout, this, [this](){ updateDataAndReplot(false); } );
00136 
00137     _publish_timer = new QTimer(this);
00138     _publish_timer->setInterval(20);
00139     connect(_publish_timer, &QTimer::timeout, this, &MainWindow::onPlaybackLoop );
00140 
00141     ui->menuFile->setToolTipsVisible(true);
00142     ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
00143     ui->streamingLabel->setHidden(true);
00144     ui->streamingSpinBox->setHidden(true);
00145 
00146     this->setMenuBar(ui->menuBar);
00147     ui->menuBar->setNativeMenuBar(false);
00148 
00149     if( _test_option )
00150     {
00151         connect( ui->actionLoadDummyData, &QAction::triggered,
00152                  this, &MainWindow::buildDummyData );
00153         buildDummyData();
00154     }
00155     else{
00156         ui->actionLoadDummyData->setVisible(false);
00157     }
00158 
00159     bool file_loaded = false;
00160     if( commandline_parser.isSet("datafile") )
00161     {
00162         QStringList datafiles =  commandline_parser.values("datafile");
00163         file_loaded = loadDataFromFiles( datafiles );
00164     }
00165     if( commandline_parser.isSet("layout"))
00166     {
00167         loadLayoutFromFile( commandline_parser.value("layout"));
00168     }
00169 
00170     QSettings settings;
00171     restoreGeometry(settings.value("MainWindow.geometry").toByteArray());
00172 
00173     bool activate_grid = settings.value("MainWindow.activateGrid", false).toBool();
00174     ui->pushButtonActivateGrid->setChecked(activate_grid);
00175 
00176     int streaming_buffer_value = settings.value("MainWindow.streamingBufferValue", 5).toInt();
00177     ui->streamingSpinBox->setValue(streaming_buffer_value);
00178 
00179     bool datetime_display  = settings.value("MainWindow.dateTimeDisplay", false).toBool();
00180     ui->pushButtonUseDateTime->setChecked( datetime_display );
00181 
00182     bool remove_time_offset = settings.value("MainWindow.removeTimeOffset", true).toBool();
00183     ui->pushButtonRemoveTimeOffset->setChecked(remove_time_offset);
00184 
00185     ui->widgetOptions->setVisible( ui->pushButtonOptions->isChecked() );
00186     ui->line->setVisible( ui->pushButtonOptions->isChecked() );
00187 
00188     //----------------------------------------------------------
00189     QIcon trackerIconA, trackerIconB, trackerIconC;
00190 
00191     trackerIconA.addFile(QStringLiteral(":/icons/resources/light/line_tracker.png"), QSize(36, 36));
00192     trackerIconB.addFile(QStringLiteral(":/icons/resources/light/line_tracker_1.png"), QSize(36, 36));
00193     trackerIconC.addFile(QStringLiteral(":/icons/resources/light/line_tracker_a.png"), QSize(36, 36));
00194 
00195     _tracker_button_icons[CurveTracker::LINE_ONLY]  = trackerIconA;
00196     _tracker_button_icons[CurveTracker::VALUE]      = trackerIconB;
00197     _tracker_button_icons[CurveTracker::VALUE_NAME] = trackerIconC;
00198 
00199     int tracker_setting = settings.value("MainWindow.timeTrackerSetting", (int)CurveTracker::VALUE ).toInt();
00200     _tracker_param = static_cast<CurveTracker::Parameter>(tracker_setting);
00201 
00202     ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[_tracker_param] );
00203 
00204     forEachWidget( [&](PlotWidget* plot) {
00205         plot->configureTracker(_tracker_param);
00206     });
00207 }
00208 
00209 MainWindow::~MainWindow()
00210 {
00211     delete ui;
00212 }
00213 
00214 void MainWindow::onUndoableChange()
00215 {
00216     if(_disable_undo_logging) return;
00217 
00218     int elapsed_ms = _undo_timer.restart();
00219 
00220     // overwrite the previous
00221     if( elapsed_ms < 100)
00222     {
00223         if( _undo_states.empty() == false)
00224             _undo_states.pop_back();
00225     }
00226 
00227     while( _undo_states.size() >= 100 ) _undo_states.pop_front();
00228     _undo_states.push_back( xmlSaveState() );
00229     _redo_states.clear();
00230     //    qDebug() << "undo " << _undo_states.size();
00231 }
00232 
00233 
00234 void MainWindow::onRedoInvoked()
00235 {
00236     _disable_undo_logging = true;
00237     if( _redo_states.size() > 0)
00238     {
00239         QDomDocument state_document = _redo_states.back();
00240         while( _undo_states.size() >= 100 ) _undo_states.pop_front();
00241         _undo_states.push_back( state_document );
00242         _redo_states.pop_back();
00243 
00244         xmlLoadState( state_document );
00245     }
00246     //    qDebug() << "undo " << _undo_states.size();
00247     _disable_undo_logging = false;
00248 }
00249 
00250 void MainWindow::onUndoInvoked( )
00251 {
00252     _disable_undo_logging = true;
00253     if( _undo_states.size() > 1)
00254     {
00255         QDomDocument state_document = _undo_states.back();
00256         while( _redo_states.size() >= 100 ) _redo_states.pop_front();
00257         _redo_states.push_back( state_document );
00258         _undo_states.pop_back();
00259         state_document = _undo_states.back();
00260 
00261         xmlLoadState( state_document );
00262     }
00263     //    qDebug() << "undo " << _undo_states.size();
00264     _disable_undo_logging = false;
00265 }
00266 
00267 void MainWindow::onUpdateLeftTableValues()
00268 {
00269     auto table_model = _curvelist_widget->getTableModel();
00270 
00271     for(auto table_view: { _curvelist_widget->getTableView(), _curvelist_widget->getCustomView() } )
00272     {
00273         if( _curvelist_widget->is2ndColumnHidden() )
00274         {
00275             continue;
00276         }
00277 
00278         const int vertical_height = table_view->visibleRegion().boundingRect().height();
00279 
00280         for (int row = 0; row < _curvelist_widget->rowCount(); row++)
00281         {
00282             int vertical_pos = table_view->rowViewportPosition(row);
00283             if( vertical_pos < 0 || table_view->isRowHidden(row) ){ continue; }
00284             if( vertical_pos > vertical_height){ break; }
00285 
00286             const std::string& name = table_model->item(row,0)->text().toStdString();
00287             auto it = _mapped_plot_data.numeric.find(name);
00288             if( it !=  _mapped_plot_data.numeric.end())
00289             {
00290                 auto& data = it->second;
00291 
00292                 double num = 0.0;
00293                 bool valid = false;
00294 
00295                 if( _tracker_time < std::numeric_limits<double>::max())
00296                 {
00297                     auto value = data.getYfromX( _tracker_time );
00298                     if(value){
00299                         valid = true;
00300                         num = value.value();
00301                     }
00302                 }
00303                 else if( data.size() > 0)
00304                 {
00305                     valid = true;
00306                     num = data.back().y;
00307                 }
00308                 if( valid )
00309                 {
00310                     QString num_text = QString::number( num, 'f', 3);
00311                     if(num_text.contains('.'))
00312                     {
00313                         int idx = num_text.length() -1;
00314                         while( num_text[idx] == '0' )
00315                         {
00316                             num_text[idx] = ' ';
00317                             idx--;
00318                         }
00319                         if(  num_text[idx] == '.') num_text[idx] = ' ';
00320                     }
00321                     table_model->item(row,1)->setText(num_text + ' ');
00322                 }
00323             }
00324         }
00325     }
00326 }
00327 
00328 
00329 void MainWindow::onTrackerMovedFromWidget(QPointF relative_pos)
00330 {
00331     _tracker_time = relative_pos.x() + _time_offset.get();
00332 
00333     auto prev = ui->timeSlider->blockSignals(true);
00334     ui->timeSlider->setRealValue( _tracker_time );
00335     ui->timeSlider->blockSignals(prev);
00336 
00337     onTrackerTimeUpdated( _tracker_time, true );
00338 }
00339 
00340 void MainWindow::onTimeSlider_valueChanged(double abs_time)
00341 {
00342     _tracker_time = abs_time;
00343     onTrackerTimeUpdated( _tracker_time, true );
00344 }
00345 
00346 void MainWindow::onTrackerTimeUpdated(double absolute_time, bool do_replot)
00347 {
00348     updatedDisplayTime();
00349     onUpdateLeftTableValues();
00350 
00351     for ( auto& it: _state_publisher)
00352     {
00353         it.second->updateState( absolute_time);
00354     }
00355 
00356     forEachWidget( [&](PlotWidget* plot)
00357     {
00358         plot->setTrackerPosition( _tracker_time );
00359         if(do_replot)
00360         {
00361             plot->replot();
00362         }
00363     } );
00364 }
00365 
00366 void MainWindow::createTabbedDialog(QString suggest_win_name, PlotMatrix* first_tab)
00367 {
00368     if( suggest_win_name.isEmpty())
00369     {
00370         for (size_t i=0; i<= TabbedPlotWidget::instances().size(); i++)
00371         {
00372             suggest_win_name = QString("Window%1").arg(i);
00373             TabbedPlotWidget* tw = TabbedPlotWidget::instance(suggest_win_name);
00374             if( tw == nullptr )
00375             {
00376                 break;
00377             }
00378         }
00379     }
00380 
00381     SubWindow* window = new SubWindow(suggest_win_name, first_tab, _mapped_plot_data, this );
00382 
00383     connect( window, SIGNAL(destroyed(QObject*)),  this,  SLOT(onFloatingWindowDestroyed(QObject*)) );
00384     connect( window, SIGNAL(destroyed(QObject*)),  this,  SLOT(onUndoableChange()) );
00385 
00386     window->tabbedWidget()->setStreamingMode( isStreamingActive() );
00387 
00388     window->setAttribute( Qt::WA_DeleteOnClose, true );
00389     window->show();
00390     window->activateWindow();
00391     window->raise();
00392 
00393     if (this->signalsBlocked() == false) onUndoableChange();
00394 }
00395 
00396 
00397 void MainWindow::initializeActions()
00398 {
00399     _undo_shortcut.setContext(Qt::ApplicationShortcut);
00400     _redo_shortcut.setContext(Qt::ApplicationShortcut);
00401     _fullscreen_shortcut.setContext(Qt::ApplicationShortcut);
00402 
00403     connect( &_undo_shortcut, &QShortcut::activated, this, &MainWindow::onUndoInvoked );
00404     connect( &_redo_shortcut, &QShortcut::activated, this, &MainWindow::onRedoInvoked );
00405     connect( &_streaming_shortcut, &QShortcut::activated, this, &MainWindow::on_streamingToggled );
00406     connect( &_playback_shotcut, &QShortcut::activated, ui->pushButtonPlay, &QPushButton::toggle );
00407     connect( &_fullscreen_shortcut, &QShortcut::activated, this, &MainWindow::on_actionFullscreen_triggered);
00408 
00409     QShortcut* open_menu_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F), this);
00410     connect( open_menu_shortcut, &QShortcut::activated, [this](){
00411         ui->menuFile->exec( ui->menuBar->mapToGlobal(QPoint(0,25)));
00412     } );
00413 
00414     QShortcut* open_streaming_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_S), this);
00415     connect( open_streaming_shortcut, &QShortcut::activated, [this](){
00416         ui->menuStreaming->exec( ui->menuBar->mapToGlobal(QPoint(50,25)));
00417     } );
00418 
00419     QShortcut* open_publish_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_P), this);
00420     connect( open_publish_shortcut, &QShortcut::activated, [this](){
00421         ui->menuPublishers->exec( ui->menuBar->mapToGlobal(QPoint(140,25)));
00422     } );
00423 
00424     QShortcut* open_help_shortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H), this);
00425     connect( open_help_shortcut, &QShortcut::activated, [this](){
00426         ui->menuHelp->exec( ui->menuBar->mapToGlobal(QPoint(230,25)));
00427     } );
00428 
00429     //---------------------------------------------
00430 
00431     QSettings settings;
00432     updateRecentDataMenu( settings.value("MainWindow.recentlyLoadedDatafile").toStringList() );
00433     updateRecentLayoutMenu( settings.value("MainWindow.recentlyLoadedLayout").toStringList() );
00434 }
00435 
00436 void MainWindow::initializePlugins(QString directory_name)
00437 {
00438     static std::set<QString> loaded_plugins;
00439 
00440     QDir pluginsDir( directory_name );
00441 
00442     for (const QString& filename: pluginsDir.entryList(QDir::Files))
00443     {
00444         QFileInfo fileinfo(filename);
00445         if( fileinfo.suffix() != "so" && fileinfo.suffix() != "dll" && fileinfo.suffix() != "dylib"){
00446             continue;
00447         }
00448 
00449         if( loaded_plugins.find( filename ) != loaded_plugins.end())
00450         {
00451             continue;
00452         }
00453 
00454         QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename), this);
00455 
00456         QObject *plugin = pluginLoader.instance();
00457         if (plugin)
00458         {
00459             DataLoader *loader        = qobject_cast<DataLoader *>(plugin);
00460             StatePublisher *publisher = qobject_cast<StatePublisher *>(plugin);
00461             DataStreamer *streamer    =  qobject_cast<DataStreamer *>(plugin);
00462 
00463             QString plugin_name;
00464             if( loader )    plugin_name = loader->name();
00465             if( publisher ) plugin_name = publisher->name();
00466             if( streamer )  plugin_name = streamer->name();
00467             plugin_name.replace(" ", "_");
00468 
00469             if( loaded_plugins.find(plugin_name) == loaded_plugins.end())
00470             {
00471                 loaded_plugins.insert( plugin_name );
00472             }
00473             else{
00474                 QMessageBox::warning(this, tr("Warning"),
00475                                      tr("Trying to load twice a plugin with name [%1].\n"
00476                                         "Only the first will be loaded.").arg(plugin_name) );
00477                 continue;
00478             }
00479 
00480             if (loader)
00481             {
00482                 qDebug() << filename << ": is a DataLoader plugin";
00483                 if( !_test_option && loader->isDebugPlugin())
00484                 {
00485                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00486                 }
00487                 else{
00488                     _data_loader.insert( std::make_pair( plugin_name, loader) );
00489                 }
00490             }
00491             else if (publisher)
00492             {
00493                 publisher->setDataMap( &_mapped_plot_data );
00494                 qDebug() << filename << ": is a StatePublisher plugin";
00495                 if( !_test_option && publisher->isDebugPlugin())
00496                 {
00497                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00498                 }
00499                 else
00500                 {
00501                     ui->menuPublishers->setEnabled(true);
00502 
00503                     _state_publisher.insert( std::make_pair(plugin_name, publisher) );
00504                     QAction* activatePublisher = new QAction(tr("Start: ") + plugin_name , this);
00505                     activatePublisher->setProperty("starter_button", true);
00506                     activatePublisher->setCheckable(true);
00507                     activatePublisher->setChecked(false);
00508 
00509                     ui->menuPublishers->addSeparator();
00510                     ui->menuPublishers->addSection(plugin_name);
00511                     ui->menuPublishers->addAction(activatePublisher);
00512                     publisher->setParentMenu( ui->menuPublishers, activatePublisher );
00513 
00514                     connect(activatePublisher, &QAction::toggled,
00515                             [=](bool enable)
00516                     {
00517                         publisher->setEnabled( enable );
00518                     } );
00519                 }
00520             }
00521             else if (streamer)
00522             {
00523                 qDebug() << filename << ": is a DataStreamer plugin";
00524                 if( !_test_option && streamer->isDebugPlugin())
00525                 {
00526                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00527                 }
00528                 else{
00529                     _data_streamer.insert( std::make_pair(plugin_name , streamer ) );
00530 
00531                     QAction* startStreamer = new QAction(QString("Start: ") + plugin_name, this);
00532                     ui->menuStreaming->setEnabled(true);
00533                     ui->menuStreaming->addAction(startStreamer);
00534 
00535                     streamer->addActionsToParentMenu( ui->menuStreaming );
00536                     ui->menuStreaming->addSeparator();
00537 
00538                     connect(startStreamer, &QAction::triggered, this, [this, plugin_name]()
00539                     {
00540                         on_actionStartStreaming(plugin_name);
00541                     });
00542 
00543 
00544                     connect(streamer, &DataStreamer::connectionClosed,
00545                             ui->actionStopStreaming, &QAction::trigger );
00546 
00547                     connect(streamer, &DataStreamer::clearBuffers,
00548                             this, &MainWindow::on_actionClearBuffer_triggered );
00549                 }
00550             }
00551         }
00552         else{
00553             if( pluginLoader.errorString().contains("is not an ELF object") == false)
00554             {
00555                 qDebug() << filename << ": " << pluginLoader.errorString();
00556             }
00557         }
00558     }
00559 }
00560 
00561 void MainWindow::buildDummyData()
00562 {
00563     PlotDataMapRef datamap;
00564 
00565     static int count = 0;
00566     size_t SIZE = 10000;
00567     QElapsedTimer timer;
00568     timer.start();
00569     QStringList  words_list;
00570     words_list << "world/siam" << "world/tre" << "walk/piccoli" << "walk/porcellin"
00571                << "fly/high/mai" << "fly/high/nessun" << "fly/low/ci" << "fly/low/dividera"
00572                << "data_1" << "data_2" << "data_3" << "data_10";
00573 
00574     for( int i=0; i<10; i++)
00575     {
00576         words_list.append(QString("data_vect/%1").arg(count++));
00577     }
00578 
00579     for( const QString& name: words_list)
00580     {
00581         double A =  6* ((double)qrand()/(double)RAND_MAX) - 3;
00582         double B =  3* ((double)qrand()/(double)RAND_MAX)  ;
00583         double C =  3* ((double)qrand()/(double)RAND_MAX)  ;
00584         double D =  20* ((double)qrand()/(double)RAND_MAX)  ;
00585 
00586         auto it = datamap.addNumeric( name.toStdString() );
00587         PlotData& plot = it->second;
00588 
00589         double t = 0;
00590         for (unsigned indx=0; indx<SIZE; indx++)
00591         {
00592             t += 0.01;
00593             plot.pushBack( PlotData::Point( t+35,  A*sin(B*t + C) + D*t*0.02 ) ) ;
00594         }
00595     }
00596 
00597     PlotData& sin_plot =  datamap.addNumeric( "_sin" )->second;
00598     PlotData& cos_plot =  datamap.addNumeric( "_cos" )->second;
00599 
00600     double t = 0;
00601     for (unsigned indx=0; indx<SIZE; indx++)
00602     {
00603         t += 0.01;
00604         sin_plot.pushBack( PlotData::Point( t+20,  sin(t*0.4) ) ) ;
00605         cos_plot.pushBack( PlotData::Point( t+20,  cos(t*0.4) ) ) ;
00606     }
00607 
00608     importPlotDataMap(datamap, true);
00609 }
00610 
00611 void MainWindow::on_splitterMoved(int , int )
00612 {
00613     QList<int> sizes = ui->splitter->sizes();
00614     int maxLeftWidth = _curvelist_widget->maximumWidth();
00615     int totalWidth = sizes[0] + sizes[1];
00616 
00617     if( sizes[0] > maxLeftWidth)
00618     {
00619         sizes[0] = maxLeftWidth;
00620         sizes[1] = totalWidth - maxLeftWidth;
00621         ui->splitter->setSizes(sizes);
00622     }
00623 }
00624 
00625 void MainWindow::resizeEvent(QResizeEvent *)
00626 {
00627     on_splitterMoved( 0, 0 );
00628 }
00629 
00630 
00631 void MainWindow::onPlotAdded(PlotWidget* plot)
00632 {
00633     connect( plot, &PlotWidget::undoableChange,
00634              this, &MainWindow::onUndoableChange );
00635 
00636     connect( plot, &PlotWidget::trackerMoved,
00637              this, &MainWindow::onTrackerMovedFromWidget);
00638 
00639     connect( plot, &PlotWidget::swapWidgetsRequested,
00640              this, &MainWindow::onSwapPlots);
00641 
00642     connect( this, &MainWindow::requestRemoveCurveByName,
00643              plot, &PlotWidget::removeCurve) ;
00644 
00645     connect( plot, &PlotWidget::curveListChanged,
00646              this, [this]()
00647     {
00648         updateTimeOffset();
00649         updateTimeSlider();
00650     });
00651 
00652     connect( &_time_offset, SIGNAL( valueChanged(double)),
00653              plot, SLOT(on_changeTimeOffset(double)) );
00654 
00655     connect( ui->pushButtonUseDateTime, &QPushButton::toggled,
00656              plot, &PlotWidget::on_changeDateTimeScale);
00657 
00658     connect( plot, &PlotWidget::curvesDropped,
00659              _curvelist_widget, &CurveListPanel::clearSelections);
00660 
00661     plot->on_changeTimeOffset( _time_offset.get() );
00662     plot->on_changeDateTimeScale( ui->pushButtonUseDateTime->isChecked() );
00663     plot->activateGrid( ui->pushButtonActivateGrid->isChecked() );
00664     plot->enableTracker( !isStreamingActive() );
00665     plot->configureTracker( _tracker_param );
00666 }
00667 
00668 void MainWindow::onPlotMatrixAdded(PlotMatrix* matrix)
00669 {
00670     connect( matrix, &PlotMatrix::plotAdded,      this, &MainWindow:: onPlotAdded);
00671     connect( matrix, &PlotMatrix::undoableChange, this, &MainWindow:: onUndoableChange );
00672 }
00673 
00674 QDomDocument MainWindow::xmlSaveState() const
00675 {
00676     QDomDocument doc;
00677     QDomProcessingInstruction instr =
00678             doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
00679 
00680     doc.appendChild(instr);
00681 
00682     QDomElement root = doc.createElement( "root" );
00683 
00684     for (auto& it: TabbedPlotWidget::instances() )
00685     {
00686         QDomElement tabbed_area = it.second->xmlSaveState(doc);
00687         root.appendChild( tabbed_area );
00688     }
00689 
00690     doc.appendChild(root);
00691 
00692     QDomElement relative_time = doc.createElement( "use_relative_time_offset" );
00693     relative_time.setAttribute("enabled", ui->pushButtonRemoveTimeOffset->isChecked() );
00694     root.appendChild( relative_time );
00695 
00696     return doc;
00697 }
00698 
00699 void MainWindow::checkAllCurvesFromLayout(const QDomElement& root)
00700 {
00701     std::set<std::string> curves;
00702     // ugly code, I am sorry
00703     for ( QDomElement   tw = root.firstChildElement(  "tabbed_widget" )  ;
00704           !tw.isNull(); tw = tw.nextSiblingElement( "tabbed_widget" ) )
00705     {
00706         for ( QDomElement   pm = tw.firstChildElement(  "plotmatrix" )  ;
00707               !pm.isNull(); pm = pm.nextSiblingElement( "plotmatrix" ) )
00708         {
00709             for ( QDomElement   pl = pm.firstChildElement(  "plot" )  ;
00710                   !pl.isNull(); pl = pl.nextSiblingElement( "plot" ) )
00711             {
00712                 for ( QDomElement   cv = pl.firstChildElement(  "curve" )  ;
00713                       !cv.isNull(); cv = cv.nextSiblingElement( "curve" ) )
00714                 {
00715                     curves.insert( cv.attribute("name").toStdString() );
00716                 }
00717             }
00718         }
00719     }
00720 
00721     std::vector<std::string> missing_curves;
00722 
00723     for (auto& curve_name: curves)
00724     {
00725         if( _mapped_plot_data.numeric.count( curve_name ) == 0)
00726         {
00727             missing_curves.push_back(curve_name);
00728         }
00729     }
00730     if( missing_curves.size() > 0 )
00731     {
00732         QMessageBox msgBox(this);
00733         msgBox.setWindowTitle("Warning");
00734         msgBox.setText(tr("One or more timeseries in the layout haven't been loaded yet\n"
00735                           "What do you want to do?"));
00736 
00737         QPushButton* buttonRemove = msgBox.addButton(tr("Remove curves from plots"), QMessageBox::RejectRole);
00738         QPushButton* buttonPlaceholder = msgBox.addButton(tr("Create empty placeholders"), QMessageBox::YesRole);
00739         msgBox.setDefaultButton(buttonPlaceholder);
00740         msgBox.exec();
00741         if( msgBox.clickedButton() == buttonPlaceholder )
00742         {
00743             for(auto& name: missing_curves )
00744             {
00745                 _curvelist_widget->addItem( QString::fromStdString( name ) );
00746                 _mapped_plot_data.addNumeric(name);
00747             }
00748             _curvelist_widget->refreshColumns();
00749         }
00750     }
00751 }
00752 
00753 bool MainWindow::xmlLoadState(QDomDocument state_document)
00754 {
00755     QDomElement root = state_document.namedItem("root").toElement();
00756     if ( root.isNull() ) {
00757         qWarning() << "No <root> element found at the top-level of the XML file!";
00758         return false;
00759     }
00760 
00761     size_t num_floating = 0;
00762     std::map<QString,QDomElement> tabbed_widgets_with_name;
00763 
00764     for (QDomElement tw = root.firstChildElement(  "tabbed_widget" )  ;
00765          tw.isNull() == false;
00766          tw = tw.nextSiblingElement( "tabbed_widget" ) )
00767     {
00768         if( tw.attribute("parent") != ("main_window") )
00769         {
00770             num_floating++;
00771         }
00772         tabbed_widgets_with_name[ tw.attribute("name") ] = tw;
00773     }
00774 
00775     // add if missing
00776     for(const auto& it: tabbed_widgets_with_name)
00777     {
00778         if( TabbedPlotWidget::instance( it.first ) == nullptr)
00779         {
00780             createTabbedDialog( it.first, nullptr );
00781         }
00782     }
00783 
00784     // remove those which don't share list of names
00785     for(const auto& it: TabbedPlotWidget::instances())
00786     {
00787         if( tabbed_widgets_with_name.count( it.first ) == 0)
00788         {
00789             it.second->deleteLater();
00790         }
00791     }
00792 
00793     //-----------------------------------------------------
00794     checkAllCurvesFromLayout(root);
00795     //-----------------------------------------------------
00796 
00797     for ( QDomElement tw = root.firstChildElement(  "tabbed_widget" )  ;
00798           tw.isNull() == false;
00799           tw = tw.nextSiblingElement( "tabbed_widget" ) )
00800     {
00801         TabbedPlotWidget* tabwidget = TabbedPlotWidget::instance( tw.attribute("name"));
00802         tabwidget->xmlLoadState( tw );
00803     }
00804 
00805     QDomElement relative_time = root.firstChildElement( "use_relative_time_offset" );
00806     if( !relative_time.isNull())
00807     {
00808         bool remove_offset = (relative_time.attribute("enabled") == QString("1"));
00809         ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
00810     }
00811     return true;
00812 }
00813 
00814 
00815 void MainWindow::onDeleteMultipleCurves(const std::vector<std::string> &curve_names)
00816 {
00817     for( const auto& curve_name: curve_names )
00818     {
00819         auto plot_curve = _mapped_plot_data.numeric.find( curve_name );
00820         if( plot_curve == _mapped_plot_data.numeric.end())
00821         {
00822           continue;
00823         }
00824 
00825         emit requestRemoveCurveByName( curve_name );
00826         _mapped_plot_data.numeric.erase( plot_curve );
00827 
00828         auto custom_it = _custom_plots.find( curve_name );
00829         if( custom_it != _custom_plots.end())
00830         {
00831             _custom_plots.erase( custom_it );
00832         }
00833 
00834         int row = _curvelist_widget->findRowByName( curve_name );
00835         if( row != -1 )
00836         {
00837             _curvelist_widget->removeRow(row);
00838         }
00839     }
00840 
00841     forEachWidget( [](PlotWidget* plot) {
00842         plot->replot();
00843     } );
00844 }
00845 
00846 void MainWindow::updateRecentDataMenu(QStringList new_filenames)
00847 {
00848     QMenu* menu =  ui->menuRecentData;
00849 
00850     QAction* separator = nullptr;
00851     QStringList prev_filenames;
00852     for (QAction *action: menu->actions())
00853     {
00854         if ( action->isSeparator() )
00855         {
00856             separator = action;
00857             break;
00858         }
00859         if(new_filenames.contains( action->text() ) == false)
00860         {
00861             prev_filenames.push_back( action->text() );
00862         }
00863         menu->removeAction( action );
00864     }
00865 
00866     new_filenames.append( prev_filenames );
00867     while( new_filenames.size() > 10 )
00868     {
00869         new_filenames.removeLast();
00870     }
00871 
00872     for (const auto& filename: new_filenames)
00873     {
00874         QAction* action = new QAction(filename, nullptr);
00875         connect( action, &QAction::triggered, this, [this, filename]
00876         {
00877             loadDataFromFiles( {filename} );
00878         } );
00879         menu->insertAction(separator, action );
00880     }
00881 
00882     QSettings settings;
00883     settings.setValue("MainWindow.recentlyLoadedDatafile", new_filenames );
00884     menu->setEnabled( new_filenames.size() > 0 );
00885 }
00886 
00887 void MainWindow::updateRecentLayoutMenu(QStringList new_filenames)
00888 {
00889     QMenu* menu =  ui->menuRecentLayout;
00890 
00891     QAction* separator = nullptr;
00892     QStringList prev_filenames;
00893     for (QAction *action: menu->actions())
00894     {
00895         if ( action->isSeparator() )
00896         {
00897             separator = action;
00898             break;
00899         }
00900         if(new_filenames.contains( action->text() ) == false)
00901         {
00902             prev_filenames.push_back( action->text() );
00903         }
00904         menu->removeAction( action );
00905     }
00906 
00907     new_filenames.append( prev_filenames );
00908     while( new_filenames.size() > 10 )
00909     {
00910         new_filenames.removeLast();
00911     }
00912 
00913     for (const auto& filename: new_filenames)
00914     {
00915         QAction* action = new QAction(filename, nullptr);
00916         connect( action, &QAction::triggered, this, [this, filename]
00917         {
00918             if ( this->loadLayoutFromFile(filename) )
00919             {
00920                 updateRecentLayoutMenu( {filename} );
00921             }
00922         } );
00923         menu->insertAction(separator, action );
00924     }
00925 
00926     QSettings settings;
00927     settings.setValue("MainWindow.recentlyLoadedLayout", new_filenames );
00928     menu->setEnabled( new_filenames.size() > 0 );
00929 }
00930 
00931 
00932 void MainWindow::deleteAllData()
00933 {
00934     forEachWidget( [](PlotWidget* plot) {
00935         plot->detachAllCurves();
00936     } );
00937 
00938     _mapped_plot_data.numeric.clear();
00939     _mapped_plot_data.user_defined.clear();
00940     _custom_plots.clear();
00941     _curvelist_widget->clear();
00942     _loaded_datafiles.clear();
00943 
00944     bool stopped = false;
00945     for (QAction* action: ui->menuPublishers->actions())
00946     {
00947         auto is_start_button = action->property("starter_button");
00948         if( is_start_button.isValid() && is_start_button.toBool() && action->isChecked() )
00949         {
00950             action->setChecked( false );
00951             stopped = true;
00952         }
00953     }
00954 
00955     if( stopped )
00956     {
00957         QMessageBox::warning(this, "State publishers stopped",
00958                              "All the state publishers have been stopped because old data has been deleted.");
00959     }
00960 }
00961 
00962 
00963 template <typename T>
00964 void importPlotDataMapHelper(std::unordered_map<std::string,T>& source,
00965                              std::unordered_map<std::string,T>& destination,
00966                              bool delete_older)
00967 {
00968     for (auto& it: source)
00969     {
00970         const std::string& name  = it.first;
00971         T& source_plot  = it.second;
00972         auto plot_with_same_name = destination.find(name);
00973 
00974         // this is a new plot
00975         if( plot_with_same_name == destination.end() )
00976         {
00977             plot_with_same_name = destination.emplace( std::piecewise_construct,
00978                                                        std::forward_as_tuple(name),
00979                                                        std::forward_as_tuple(name)
00980                                                        ).first;
00981         }
00982         T& destination_plot = plot_with_same_name->second;
00983 
00984         if( delete_older )
00985         {
00986             double max_range_x = destination_plot.maximumRangeX();
00987             destination_plot.swapData(source_plot);
00988             destination_plot.setMaximumRangeX(max_range_x); // just in case
00989         }
00990         else
00991         {
00992             for (size_t i=0; i< source_plot.size(); i++)
00993             {
00994                 destination_plot.pushBack( source_plot.at(i) );
00995             }
00996         }
00997         source_plot.clear();
00998     }
00999 }
01000 
01001 void MainWindow::importPlotDataMap(PlotDataMapRef& new_data, bool remove_old)
01002 {
01003     if( new_data.user_defined.empty() && new_data.numeric.empty() )
01004     {
01005         return;
01006     }
01007 
01008     if( remove_old )
01009     {
01010         std::vector<std::string> old_plots_to_delete;
01011 
01012         for (auto& it: _mapped_plot_data.numeric)
01013         {
01014             // timeseries in old but not in new
01015             if( new_data.numeric.count( it.first ) == 0 )
01016             {
01017                 old_plots_to_delete.push_back( it.first );
01018             }
01019         }
01020 
01021         if( !old_plots_to_delete.empty() )
01022         {
01023             QMessageBox::StandardButton reply;
01024             reply = QMessageBox::question(this, tr("Warning"),
01025                                           tr("Do you want to remove the previously loaded data?\n"),
01026                                           QMessageBox::Yes | QMessageBox::No,
01027                                           QMessageBox::Yes );
01028             if( reply == QMessageBox::Yes )
01029             {
01030                 onDeleteMultipleCurves(old_plots_to_delete);
01031             }
01032         }
01033     }
01034 
01035     bool curvelist_modified = false;
01036     for (auto& it: new_data.numeric)
01037     {
01038         const std::string& name  = it.first;
01039         if( it.second.size()>0 && _mapped_plot_data.numeric.count(name) == 0)
01040         {
01041             _curvelist_widget->addItem( QString::fromStdString( name ) );
01042             curvelist_modified = true;
01043         }
01044     }
01045 
01046     importPlotDataMapHelper( new_data.numeric, _mapped_plot_data.numeric, remove_old );
01047     importPlotDataMapHelper( new_data.user_defined, _mapped_plot_data.user_defined, remove_old );
01048 
01049     if( curvelist_modified )
01050     {
01051         _curvelist_widget->refreshColumns();
01052     }
01053 }
01054 
01055 bool MainWindow::isStreamingActive() const
01056 {
01057     return ui->pushButtonStreaming->isChecked() && _current_streamer;
01058 }
01059 
01060 bool MainWindow::loadDataFromFiles( QStringList filenames )
01061 {
01062     if( filenames.size() > 1 )
01063     {
01064         static bool show_me = true;
01065 
01066         QMessageBox msgbox;
01067         msgbox.setWindowTitle("Loading multiple files");
01068         msgbox.setText("You are loading multiple files at once. A prefix will be automatically added to the name of the timeseries.\n\n"
01069                        "This is an experimental feature. Publishers will not work as you may expect.");
01070         msgbox.addButton(QMessageBox::Ok);
01071         //    QCheckBox *cb = new QCheckBox("Don't show this again");
01072         //    msgbox.setCheckBox(cb);
01073         //    connect(cb, &QCheckBox::stateChanged, this, [this, cb , &show_me]() {  show_me = !cb->isChecked(); } );
01074         msgbox.exec();
01075     }
01076 
01077     char prefix_ch = 'A';
01078     QStringList loaded_filenames;
01079 
01080     for( const auto& filename: filenames)
01081     {
01082         FileLoadInfo info;
01083         info.filename = filename;
01084         if( filenames.size() > 1 )
01085         {
01086             info.prefix = prefix_ch;
01087         }
01088 
01089         if( loadDataFromFile(info) )
01090         {
01091             loaded_filenames.push_back(filename);
01092             prefix_ch++;
01093         }
01094     }
01095     if( loaded_filenames.size() > 0 )
01096     {
01097         updateRecentDataMenu(loaded_filenames);
01098         return true;
01099     }
01100     return false;
01101 }
01102 
01103 bool MainWindow::loadDataFromFile(const FileLoadInfo& info)
01104 {
01105     const QString extension = QFileInfo(info.filename).suffix().toLower();
01106 
01107     typedef std::map<QString,DataLoader*>::iterator MapIterator;
01108 
01109     std::vector<MapIterator> compatible_loaders;
01110 
01111     for (MapIterator it = _data_loader.begin(); it != _data_loader.end(); ++it)
01112     {
01113         DataLoader* data_loader = it->second;
01114         std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
01115 
01116         for(auto& ext: extensions){
01117 
01118             if( extension == QString(ext).toLower()){
01119                 compatible_loaders.push_back( it );
01120                 break;
01121             }
01122         }
01123     }
01124 
01125     DataLoader* dataloader = nullptr;
01126 
01127     if( compatible_loaders.size() == 1)
01128     {
01129         dataloader = compatible_loaders.front()->second;
01130     }
01131     else{
01132         static QString last_plugin_name_used;
01133 
01134         QStringList names;
01135         for (auto& cl: compatible_loaders)
01136         {
01137             const auto& name = cl->first;
01138 
01139             if( name == last_plugin_name_used ){
01140                 names.push_front( name );
01141             }
01142             else{
01143                 names.push_back( name );
01144             }
01145         }
01146 
01147         bool ok;
01148         QString plugin_name = QInputDialog::getItem(this, tr("QInputDialog::getItem()"),
01149                                                     tr("Select the loader to use:"),
01150                                                     names, 0, false, &ok);
01151         if (ok && !plugin_name.isEmpty())
01152         {
01153             dataloader = _data_loader[ plugin_name ];
01154             last_plugin_name_used = plugin_name;
01155         }
01156     }
01157 
01158     if( dataloader )
01159     {
01160         QFile file(info.filename);
01161 
01162         if (!file.open(QFile::ReadOnly | QFile::Text)) {
01163             QMessageBox::warning(this, tr("Datafile"),
01164                                  tr("Cannot read file %1:\n%2.")
01165                                  .arg(info.filename)
01166                                  .arg(file.errorString()));
01167             return false;
01168         }
01169         file.close();
01170 
01171         try{
01172             PlotDataMapRef mapped_data;
01173             FileLoadInfo new_info = info;
01174 
01175             if( dataloader->readDataFromFile( &new_info, mapped_data ) )
01176             {
01177                 AddPrefixToPlotData( info.prefix.toStdString(), mapped_data.numeric );
01178 
01179                 importPlotDataMap(mapped_data, true);     
01180 
01181                 QDomElement plugin_elem = dataloader->xmlSaveState(new_info.plugin_config);
01182                 new_info.plugin_config.appendChild( plugin_elem );
01183 
01184                 _loaded_datafiles.push_back(new_info);
01185             }
01186         }
01187         catch(std::exception &ex)
01188         {
01189             QMessageBox::warning(this, tr("Exception from the plugin"),
01190                                  tr("The plugin [%1] thrown the following exception: \n\n %3\n")
01191                                  .arg(dataloader->name()).arg(ex.what()) );
01192             return false;
01193         }
01194     }
01195     else{
01196         QMessageBox::warning(this, tr("Error"),
01197                              tr("Cannot read files with extension %1.\n No plugin can handle that!\n")
01198                              .arg(info.filename) );
01199     }
01200     _curvelist_widget->updateFilter();
01201     updateDataAndReplot( true );
01202     ui->timeSlider->setRealValue( ui->timeSlider->getMinimum() );
01203 
01204     return true;
01205 }
01206 
01207 
01208 void MainWindow::on_actionStartStreaming(QString streamer_name)
01209 {
01210     if( _current_streamer )
01211     {
01212         _current_streamer->shutdown();
01213         _current_streamer = nullptr;
01214     }
01215 
01216     if( _data_streamer.empty())
01217     {
01218         qDebug() << "Error, no streamer loaded";
01219         return;
01220     }
01221 
01222     if( _data_streamer.size() == 1)
01223     {
01224         _current_streamer = _data_streamer.begin()->second;
01225     }
01226     else if( _data_streamer.size() > 1)
01227     {
01228         auto it = _data_streamer.find(streamer_name);
01229         if( it != _data_streamer.end())
01230         {
01231             _current_streamer = it->second;
01232         }
01233         else{
01234             qDebug() << "Error. The streamer " << streamer_name <<
01235                         " can't be loaded";
01236             return;
01237         }
01238     }
01239 
01240     bool started = false;
01241     try{
01242         // TODO data sources
01243         started = _current_streamer && _current_streamer->start( nullptr );
01244     }
01245     catch(std::runtime_error& err)
01246     {
01247         QMessageBox::warning(this, tr("Exception from the plugin"),
01248                              tr("The plugin thrown the following exception: \n\n %1\n")
01249                              .arg(err.what()) );
01250         return;
01251     }
01252     if( started )
01253     {
01254         {
01255             std::lock_guard<std::mutex> lock( _current_streamer->mutex() );
01256             importPlotDataMap( _current_streamer->dataMap(), true );
01257         }
01258 
01259         for(auto& action: ui->menuStreaming->actions()) {
01260             action->setEnabled(false);
01261         }
01262         ui->actionClearBuffer->setEnabled(true);
01263 
01264         ui->actionStopStreaming->setEnabled(true);
01265         ui->actionDeleteAllData->setToolTip("Stop streaming to be able to delete the data");
01266 
01267         ui->pushButtonStreaming->setEnabled(true);
01268         ui->pushButtonStreaming->setChecked(true);
01269         ui->pushButtonRemoveTimeOffset->setEnabled( false );
01270 
01271         on_streamingSpinBox_valueChanged( ui->streamingSpinBox->value() );
01272     }
01273     else{
01274         qDebug() << "Failed to launch the streamer";
01275     }
01276 }
01277 
01278 void MainWindow::loadPluginState(const QDomElement& root)
01279 {
01280     QDomElement plugins = root.firstChildElement("Plugins");
01281 
01282     for ( QDomElement plugin_elem = plugins.firstChildElement()  ;
01283           plugin_elem.isNull() == false;
01284           plugin_elem = plugin_elem.nextSiblingElement() )
01285     {
01286         const QString plugin_name = plugin_elem.attribute("ID");
01287 
01288         if( plugin_elem.nodeName() != "plugin" || plugin_name.isEmpty() )
01289         {
01290             QMessageBox::warning(this, tr("Error loading Plugin State from Layout"),
01291                                  tr("The method xmlSaveState() must return a node line this <plugin ID=\"PluginName\" ") );
01292         }
01293 
01294         if( _data_loader.find(plugin_name) != _data_loader.end() )
01295         {
01296             _data_loader[plugin_name]->xmlLoadState( plugin_elem );
01297         }
01298         if( _data_streamer.find(plugin_name) != _data_streamer.end() )
01299         {
01300             _data_streamer[plugin_name]->xmlLoadState( plugin_elem );
01301         }
01302         if( _state_publisher.find(plugin_name) != _state_publisher.end() )
01303         {
01304             StatePublisher* publisher = _state_publisher[plugin_name];
01305             publisher->xmlLoadState( plugin_elem );
01306 
01307             if( _autostart_publishers && plugin_elem.attribute("status") == "active" )
01308             {
01309                 publisher->setEnabled(true);
01310             }
01311         }
01312     }
01313 }
01314 
01315 QDomElement MainWindow::savePluginState(QDomDocument& doc)
01316 {
01317     QDomElement list_plugins = doc.createElement( "Plugins" );
01318 
01319     auto CheckValidFormat = [this](const QString& expected_name, const QDomElement& elem)
01320     {
01321         if( elem.nodeName() != "plugin" || elem.attribute("ID") !=  expected_name )
01322         {
01323             QMessageBox::warning(this, tr("Error saving Plugin State to Layout"),
01324                                  tr("[%1] The method xmlSaveState() must return a node line this <plugin ID=\"PluginName\">")
01325                                  .arg(expected_name) );
01326         }
01327     };
01328 
01329     for (auto& it: _data_loader)
01330     {
01331         const DataLoader* dataloader = it.second;
01332         QDomElement plugin_elem =  dataloader->xmlSaveState(doc);
01333         if( !plugin_elem.isNull() )
01334         {
01335             list_plugins.appendChild( plugin_elem );
01336             CheckValidFormat( it.first, plugin_elem );
01337         }
01338     }
01339 
01340     for (auto& it: _data_streamer)
01341     {
01342         const DataStreamer* datastreamer = it.second;
01343         QDomElement plugin_elem =  datastreamer->xmlSaveState(doc);
01344         if( !plugin_elem.isNull() )
01345         {
01346             list_plugins.appendChild( plugin_elem );
01347             CheckValidFormat( it.first, plugin_elem );
01348         }
01349     }
01350 
01351     for (auto& it: _state_publisher)
01352     {
01353         const StatePublisher* state_publisher = it.second;
01354         QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
01355         if( !plugin_elem.isNull() )
01356         {
01357             list_plugins.appendChild( plugin_elem );
01358             CheckValidFormat( it.first, plugin_elem );
01359         }
01360 
01361         plugin_elem.setAttribute("status", state_publisher->enabled() ? "active" : "idle");
01362 
01363     }
01364     return list_plugins;
01365 }
01366 
01367 std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
01368 {
01369     // find min max time
01370     double min_time =  std::numeric_limits<double>::max();
01371     double max_time = -std::numeric_limits<double>::max();
01372     int max_steps = 0;
01373 
01374     forEachWidget([&](PlotWidget* widget)
01375     {
01376         for (auto& it: widget->curveList())
01377         {
01378             const auto& curve_name = it.first;
01379 
01380             const auto& data = _mapped_plot_data.numeric.find(curve_name)->second;
01381             if(data.size() >=1)
01382             {
01383                 const double t0 = data.front().x;
01384                 const double t1 = data.back().x;
01385                 min_time  = std::min( min_time, t0);
01386                 max_time  = std::max( max_time, t1);
01387                 max_steps = std::max( max_steps, (int)data.size());
01388             }
01389         }
01390     });
01391 
01392     // needed if all the plots are empty
01393     if( max_steps == 0 || max_time < min_time)
01394     {
01395         for (const auto& it: _mapped_plot_data.numeric)
01396         {
01397             const PlotData& data = it.second;
01398             if(data.size() >=1)
01399             {
01400                 const double t0 = data.front().x;
01401                 const double t1 = data.back().x;
01402                 min_time  = std::min( min_time, t0);
01403                 max_time  = std::max( max_time, t1);
01404                 max_steps = std::max( max_steps, (int)data.size());
01405             }
01406         }
01407     }
01408 
01409     // last opportunity. Everything else failed
01410     if( max_steps == 0 || max_time < min_time)
01411     {
01412         min_time = 0.0;
01413         max_time = 1.0;
01414         max_steps = 1;
01415     }
01416     return std::tuple<double,double,int>( min_time, max_time, max_steps );
01417 }
01418 
01419 static const QString LAYOUT_VERSION = "2.2.1";
01420 
01421 bool MainWindow::loadLayoutFromFile(QString filename)
01422 {
01423     QSettings settings;
01424 
01425     QFile file(filename);
01426     if (!file.open(QFile::ReadOnly | QFile::Text)) {
01427         QMessageBox::warning(this, tr("Layout"),
01428                              tr("Cannot read file %1:\n%2.")
01429                              .arg(filename)
01430                              .arg(file.errorString()));
01431         return false;
01432     }
01433 
01434     QString errorStr;
01435     int errorLine, errorColumn;
01436 
01437     QDomDocument domDocument;
01438 
01439     if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
01440         QMessageBox::information(window(), tr("XML Layout"),
01441                                  tr("Parse error at line %1:\n%2")
01442                                  .arg(errorLine)
01443                                  .arg(errorStr));
01444         return false;
01445     }
01446 
01447     //-------------------------------------------------
01448     // refresh plugins
01449     QDomElement root = domDocument.namedItem("root").toElement();
01450 
01451     if( !root.hasAttribute("version") && root.attribute("version") != LAYOUT_VERSION )
01452     {
01453         QMessageBox::warning(this, tr("Wrong Layout version"),
01454                              tr("This Layout ID is not supported [%1].\nThis version of PlotJuggler use Layout ID [%2]")
01455                              .arg(root.attribute("version"))
01456                              .arg(LAYOUT_VERSION) );
01457         return false;
01458     }
01459 
01460     loadPluginState(root);
01461     //-------------------------------------------------
01462     QDomElement previously_loaded_datafile =  root.firstChildElement( "previouslyLoaded_Datafiles" );
01463 
01464     QDomElement datafile_elem = previously_loaded_datafile.firstChildElement( "fileInfo" );
01465     while( !datafile_elem.isNull() )
01466     {
01467         FileLoadInfo info;
01468         info.filename = datafile_elem.attribute("filename");
01469         info.prefix   = datafile_elem.attribute("prefix");
01470 
01471         QDomElement datasources_elem = datafile_elem.firstChildElement( "selected_datasources" );
01472         QString topics_list = datasources_elem.attribute("value");
01473         info.selected_datasources = topics_list.split(";", QString::SkipEmptyParts);
01474 
01475         auto plugin_elem = datafile_elem.firstChildElement( "plugin" );
01476         info.plugin_config.appendChild( info.plugin_config.importNode( plugin_elem, true ) );
01477 
01478         loadDataFromFile( info );
01479         datafile_elem = datafile_elem.nextSiblingElement( "fileInfo" );
01480     }
01481 
01482     QDomElement previousl_streamer =  root.firstChildElement( "previouslyLoaded_Streamer" );
01483     if( !previousl_streamer.isNull() )
01484     {
01485         QString streamer_name = previousl_streamer.attribute("name");
01486 
01487         QMessageBox msgBox(this);
01488         msgBox.setWindowTitle("Start Streaming?");
01489         msgBox.setText(tr("Start the previously used streaming plugin?\n\n %1 \n\n").arg(streamer_name));
01490         QPushButton* yes = msgBox.addButton(tr("Yes"), QMessageBox::YesRole);
01491         QPushButton* no  = msgBox.addButton(tr("No"), QMessageBox::RejectRole);
01492         msgBox.setDefaultButton(yes);
01493         msgBox.exec();
01494 
01495         if( msgBox.clickedButton() == yes )
01496         {
01497             if( _data_streamer.count(streamer_name) != 0 )
01498             {
01499                 on_actionStartStreaming( streamer_name );
01500             }
01501             else{
01502                 QMessageBox::warning(this, tr("Error Loading Streamer"),
01503                                      tr("The streamer named %1 can not be loaded.").arg(streamer_name));
01504             }
01505         }
01506     }
01507     //-------------------------------------------------
01508     // autostart_publishers
01509     QDomElement plugins = root.firstChildElement("Plugins");
01510 
01511     if( ! plugins.isNull() && _autostart_publishers )
01512     {
01513         for ( QDomElement plugin_elem = plugins.firstChildElement()  ;
01514               plugin_elem.isNull() == false;
01515               plugin_elem = plugin_elem.nextSiblingElement() )
01516         {
01517             const QString plugin_name = plugin_elem.nodeName();
01518             if( _state_publisher.find(plugin_name) != _state_publisher.end() )
01519             {
01520                 StatePublisher* publisher = _state_publisher[plugin_name];
01521 
01522                 if( plugin_elem.attribute("status") == "active" )
01523                 {
01524                     publisher->setEnabled(true);
01525                 }
01526             }
01527         }
01528     }
01529     //-------------------------------------------------
01530     auto custom_equations = root.firstChildElement( "customMathEquations" );
01531 
01532     try{
01533         if( !custom_equations.isNull() )
01534         {
01535             for (QDomElement custom_eq = custom_equations.firstChildElement( "snippet" )  ;
01536                  custom_eq.isNull() == false;
01537                  custom_eq = custom_eq.nextSiblingElement( "snippet" ) )
01538             {
01539                 CustomPlotPtr new_custom_plot = CustomFunction::createFromXML(custom_eq);
01540                 const auto& name = new_custom_plot->name();
01541                 _custom_plots[name] = new_custom_plot;
01542                 new_custom_plot->calculateAndAdd( _mapped_plot_data );
01543                 _curvelist_widget->addItem( QString::fromStdString( name ) );
01544             }
01545             _curvelist_widget->refreshColumns();
01546         }
01547     }
01548     catch( std::runtime_error& err)
01549     {
01550         QMessageBox::warning(this, tr("Exception"),
01551                              tr("Failed to refresh a customMathEquation \n\n %1\n")
01552                              .arg(err.what()) );
01553     }
01554 
01555     QByteArray snippets_saved_xml = settings.value("AddCustomPlotDialog.savedXML",
01556                                                    QByteArray() ).toByteArray();
01557 
01558     auto snippets_element = root.firstChildElement("snippets");
01559     if( !snippets_element.isNull() )
01560     {
01561         auto snippets_previous = GetSnippetsFromXML(snippets_saved_xml);
01562         auto snippets_layout   = GetSnippetsFromXML(snippets_element);
01563 
01564         bool snippets_are_different = false;
01565         for(const auto& snippet_it :  snippets_layout)
01566         {
01567             auto prev_it = snippets_previous.find( snippet_it.first );
01568 
01569             if( prev_it == snippets_previous.end() ||
01570                     prev_it->second.equation   != snippet_it.second.equation ||
01571                     prev_it->second.globalVars != snippet_it.second.globalVars)
01572             {
01573                 snippets_are_different = true;
01574                 break;
01575             }
01576         }
01577 
01578         if( snippets_are_different )
01579         {
01580             QMessageBox msgBox(this);
01581             msgBox.setWindowTitle("Overwrite custom transforms?");
01582             msgBox.setText("Your layour file contains a set of custom transforms different from "
01583                            "the last one you used.\nant to load these transformations?");
01584             msgBox.addButton(QMessageBox::No);
01585             msgBox.addButton(QMessageBox::Yes);
01586             msgBox.setDefaultButton(QMessageBox::Yes);
01587 
01588             if( msgBox.exec() == QMessageBox::Yes )
01589             {
01590                 for(const auto& snippet_it :  snippets_layout)
01591                 {
01592                     snippets_previous[snippet_it.first] = snippet_it.second;
01593                 }
01594                 QDomDocument doc;
01595                 auto snippets_root_element = ExportSnippets( snippets_previous, doc);
01596                 doc.appendChild( snippets_root_element );
01597                 settings.setValue("AddCustomPlotDialog.savedXML", doc.toByteArray(2));
01598             }
01599         }
01600     }
01601 
01603 
01604     xmlLoadState( domDocument );
01605 
01606     forEachWidget([&](PlotWidget* plot)
01607     {
01608         plot->zoomOut(false);
01609     } );
01610 
01611     _undo_states.clear();
01612     _undo_states.push_back( domDocument );
01613     return true;
01614 }
01615 
01616 
01617 void MainWindow::on_tabbedAreaDestroyed(QObject *object)
01618 {
01619     this->setFocus();
01620 }
01621 
01622 void MainWindow::onFloatingWindowDestroyed(QObject *object)
01623 {
01624     //    for (size_t i=0; i< SubWindow::instances().size(); i++)
01625     //    {
01626     //        if( SubWindow::instances()[i] == object)
01627     //        {
01628     //            SubWindow::instances().erase( SubWindow::instances().begin() + i);
01629     //            break;
01630     //        }
01631     //    }
01632 }
01633 
01634 void MainWindow::onCreateFloatingWindow(PlotMatrix* first_tab)
01635 {
01636     createTabbedDialog( QString(), first_tab );
01637 }
01638 
01639 void MainWindow::forEachWidget(std::function<void (PlotWidget*, PlotMatrix*, int,int )> operation)
01640 {
01641     auto func = [&](QTabWidget * tabs)
01642     {
01643         for (int t=0; t < tabs->count(); t++)
01644         {
01645             PlotMatrix* matrix =  static_cast<PlotMatrix*>(tabs->widget(t));
01646 
01647             for(unsigned row=0; row< matrix->rowsCount(); row++)
01648             {
01649                 for(unsigned col=0; col< matrix->colsCount(); col++)
01650                 {
01651                     PlotWidget* plot = matrix->plotAt(row, col);
01652                     operation(plot, matrix, row, col);
01653                 }
01654             }
01655         }
01656     };
01657 
01658     for(const auto& it: TabbedPlotWidget::instances())
01659     {
01660         func( it.second->tabWidget() );
01661     }
01662 }
01663 
01664 void MainWindow::forEachWidget(std::function<void (PlotWidget *)> op)
01665 {
01666     forEachWidget( [&](PlotWidget*plot, PlotMatrix*, int,int) { op(plot); } );
01667 }
01668 
01669 void MainWindow::updateTimeSlider()
01670 {
01671     auto range = calculateVisibleRangeX();
01672 
01673     ui->timeSlider->setLimits(std::get<0>(range),
01674                               std::get<1>(range),
01675                               std::get<2>(range));
01676 
01677     _tracker_time = std::max( _tracker_time, ui->timeSlider->getMinimum() );
01678     _tracker_time = std::min( _tracker_time, ui->timeSlider->getMaximum() );
01679 }
01680 
01681 void MainWindow::updateTimeOffset()
01682 {
01683     auto range = calculateVisibleRangeX();
01684     double min_time = std::get<0>(range);
01685 
01686     const bool remove_offset = ui->pushButtonRemoveTimeOffset->isChecked();
01687     if( remove_offset && min_time != std::numeric_limits<double>::max())
01688     {
01689         _time_offset.set( min_time );
01690     }
01691     else{
01692         _time_offset.set( 0.0 );
01693     }
01694 }
01695 
01696 void MainWindow::onSwapPlots(PlotWidget *source, PlotWidget *destination)
01697 {
01698     if( !source || !destination ) return;
01699 
01700     PlotMatrix* src_matrix = nullptr;
01701     PlotMatrix* dst_matrix = nullptr;
01702     QPoint src_pos;
01703     QPoint dst_pos;
01704 
01705     forEachWidget( [&](PlotWidget* plot, PlotMatrix* matrix, int row,int col)
01706     {
01707         if( plot == source ) {
01708             src_matrix = matrix;
01709             src_pos.setX( row );
01710             src_pos.setY( col );
01711         }
01712         else if( plot == destination )
01713         {
01714             dst_matrix = matrix;
01715             dst_pos.setX( row );
01716             dst_pos.setY( col );
01717         }
01718     });
01719 
01720     if(src_matrix && dst_matrix)
01721     {
01722         src_matrix->gridLayout()->removeWidget( source );
01723         dst_matrix->gridLayout()->removeWidget( destination );
01724 
01725         src_matrix->gridLayout()->addWidget( destination, src_pos.x(), src_pos.y() );
01726         dst_matrix->gridLayout()->addWidget( source,      dst_pos.x(), dst_pos.y() );
01727 
01728         src_matrix->updateLayout();
01729         if( src_matrix != dst_matrix){
01730             dst_matrix->updateLayout();
01731         }
01732         source->changeBackgroundColor( Qt::white );
01733         destination->changeBackgroundColor( Qt::white );
01734     }
01735     onUndoableChange();
01736 }
01737 
01738 void MainWindow::on_pushButtonStreaming_toggled(bool streaming)
01739 {
01740     if( !_current_streamer )
01741     {
01742         streaming = false;
01743     }
01744 
01745     ui->pushButtonRemoveTimeOffset->setEnabled( !streaming );
01746 
01747     if( streaming )
01748     {
01749         ui->horizontalSpacer->changeSize(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed);
01750         ui->pushButtonStreaming->setText("Streaming ON");
01751     }
01752     else{
01753         _replot_timer->stop( );
01754         ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
01755         ui->pushButtonStreaming->setText("Streaming OFF");
01756     }
01757     ui->streamingLabel->setHidden( !streaming );
01758     ui->streamingSpinBox->setHidden( !streaming );
01759     ui->timeSlider->setHidden( streaming );
01760     ui->pushButtonPlay->setHidden( streaming );
01761 
01762     if( streaming && ui->pushButtonPlay->isChecked() )
01763     {
01764         ui->pushButtonPlay->setChecked(false);
01765     }
01766 
01767     forEachWidget([&](PlotWidget* plot)
01768     {
01769         plot->enableTracker( !streaming );
01770     } );
01771 
01772     emit activateStreamingMode( streaming );
01773 
01774     if( _current_streamer && streaming)
01775     {
01776         _replot_timer->start();
01777         updateTimeOffset();
01778     }
01779     else{
01780         updateDataAndReplot( true );
01781         onUndoableChange();
01782     }
01783 }
01784 
01785 void MainWindow::on_streamingToggled()
01786 {
01787     if( ui->pushButtonStreaming->isEnabled() )
01788     {
01789         bool streaming = ui->pushButtonStreaming->isChecked();
01790         ui->pushButtonStreaming->setChecked( !streaming );
01791     }
01792     else {
01793         if( ui->pushButtonPlay->isEnabled() )
01794         {
01795             bool playing = ui->pushButtonPlay->isChecked();
01796             ui->pushButtonPlay->setChecked( !playing );
01797         }
01798     }
01799 }
01800 
01801 void MainWindow::updateDataAndReplot(bool replot_hidden_tabs)
01802 {
01803     if( _current_streamer )
01804     {
01805         std::lock_guard<std::mutex> lock( _current_streamer->mutex() );
01806 
01807         auto curvelist_added = _current_streamer->appendData( _mapped_plot_data );
01808 
01809         for(const auto& str: curvelist_added)
01810         {
01811             _curvelist_widget->addItem(str);
01812         }
01813 
01814         if( curvelist_added.size() > 0  )
01815         {
01816             _curvelist_widget->refreshColumns();
01817         }
01818 
01819         for( auto& custom_it: _custom_plots)
01820         {
01821             auto* dst_plot = &_mapped_plot_data.numeric.at(custom_it.first);
01822             custom_it.second->calculate(_mapped_plot_data, dst_plot);
01823         }
01824     }
01825 
01826     const bool is_streaming_active = isStreamingActive();
01827 
01828     forEachWidget( [is_streaming_active](PlotWidget* plot)
01829     {
01830         plot->updateCurves();
01831         plot->setZoomEnabled( !is_streaming_active );
01832     } );
01833 
01834     //--------------------------------
01835     // trigger again the execution of this callback if steaming == true
01836     if( is_streaming_active )
01837     {
01838         auto range = calculateVisibleRangeX();
01839         double max_time = std::get<1>(range);
01840         _tracker_time = max_time;
01841 
01842         onTrackerTimeUpdated(_tracker_time, false);
01843     }
01844     else{
01845         updateTimeOffset();
01846         updateTimeSlider();
01847     }
01848     //--------------------------------
01849     for(const auto& it: TabbedPlotWidget::instances())
01850     {
01851         if( replot_hidden_tabs )
01852         {
01853             QTabWidget* tabs = it.second->tabWidget();
01854             for (int index=0; index < tabs->count(); index++)
01855             {
01856                 PlotMatrix* matrix =  static_cast<PlotMatrix*>( tabs->widget(index) );
01857                 matrix->maximumZoomOut();
01858             }
01859         }
01860         else{
01861             PlotMatrix* matrix =  it.second->currentTab() ;
01862             matrix->maximumZoomOut(); // includes replot
01863         }
01864     }
01865 }
01866 
01867 void MainWindow::on_streamingSpinBox_valueChanged(int value)
01868 {
01869     double real_value = value;
01870     if ( value == ui->streamingSpinBox->maximum())
01871     {
01872         real_value = std::numeric_limits<double>::max();
01873         ui->streamingSpinBox->setStyleSheet("QSpinBox { color: red; }");
01874         ui->streamingSpinBox->setSuffix("=inf");
01875     }
01876     else{
01877         ui->streamingSpinBox->setStyleSheet("QSpinBox { color: black; }");
01878         ui->streamingSpinBox->setSuffix(" sec");
01879     }
01880 
01881     if( isStreamingActive() == false)
01882     {
01883         return;
01884     }
01885 
01886     for (auto& it : _mapped_plot_data.numeric )
01887     {
01888         it.second.setMaximumRangeX( real_value );
01889     }
01890 
01891     for (auto& it: _mapped_plot_data.user_defined)
01892     {
01893         it.second.setMaximumRangeX( real_value );
01894     }
01895 
01896     if( _current_streamer )
01897     {
01898         _current_streamer->setMaximumRange( real_value );
01899     }
01900 }
01901 
01902 void MainWindow::on_actionStopStreaming_triggered()
01903 {
01904     ui->pushButtonStreaming->setChecked(false);
01905     ui->pushButtonStreaming->setEnabled(false);
01906     _replot_timer->stop();
01907     _current_streamer->shutdown();
01908     _current_streamer = nullptr;
01909 
01910     for(auto& action: ui->menuStreaming->actions()) {
01911         action->setEnabled(true);
01912     }
01913     ui->actionStopStreaming->setEnabled(false);
01914 
01915     if( !_mapped_plot_data.numeric.empty()){
01916         ui->actionDeleteAllData->setToolTip("");
01917     }
01918 
01919     // reset this.
01920     for(auto& it: _mapped_plot_data.numeric)
01921     {
01922         it.second.setMaximumRangeX( std::numeric_limits<double>::max() );
01923     }
01924     for(auto& it: _mapped_plot_data.user_defined)
01925     {
01926         it.second.setMaximumRangeX( std::numeric_limits<double>::max() );
01927     }
01928 }
01929 
01930 
01931 void MainWindow::on_actionExit_triggered()
01932 {
01933     this->close();
01934 }
01935 
01936 void MainWindow::on_pushButtonRemoveTimeOffset_toggled(bool )
01937 {
01938     updateTimeOffset();
01939     updatedDisplayTime();
01940 
01941     forEachWidget( [](PlotWidget* plot)
01942     {
01943         plot->replot();
01944     } );
01945 
01946     if (this->signalsBlocked() == false)  onUndoableChange();
01947 }
01948 
01949 
01950 void MainWindow::on_pushButtonOptions_toggled(bool checked)
01951 {
01952     ui->widgetOptions->setVisible( checked );
01953     ui->line->setVisible( checked );
01954 }
01955 
01956 void MainWindow::updatedDisplayTime()
01957 {
01958     QLineEdit* timeLine = ui->displayTime;
01959     const double relative_time = _tracker_time - _time_offset.get();
01960     if( ui->pushButtonUseDateTime->isChecked() )
01961     {
01962         if( ui->pushButtonRemoveTimeOffset->isChecked() )
01963         {
01964             QTime time = QTime::fromMSecsSinceStartOfDay( std::round(relative_time*1000.0));
01965             timeLine->setText( time.toString("HH:mm::ss.zzz") );
01966         }
01967         else{
01968             QDateTime datetime = QDateTime::fromMSecsSinceEpoch( std::round(_tracker_time*1000.0) );
01969             timeLine->setText( datetime.toString("[yyyy MMM dd] HH:mm::ss.zzz") );
01970         }
01971     }
01972     else{
01973         timeLine->setText( QString::number(relative_time, 'f', 3));
01974     }
01975 
01976     QFontMetrics fm( timeLine->font() );
01977     int width = fm.width( timeLine->text()) + 10;
01978     timeLine->setFixedWidth( std::max( 100, width ) );
01979 }
01980 
01981 void MainWindow::on_pushButtonActivateGrid_toggled(bool checked)
01982 {
01983     forEachWidget( [checked](PlotWidget* plot) {
01984         plot->activateGrid( checked );
01985         plot->replot();
01986     });
01987 }
01988 
01989 void MainWindow::on_pushButtonRatio_toggled(bool checked)
01990 {
01991     forEachWidget( [checked](PlotWidget* plot) {
01992         plot->setConstantRatioXY( checked );
01993         plot->replot();
01994     });
01995 }
01996 
01997 void MainWindow::on_pushButtonPlay_toggled(bool checked)
01998 {
01999     if( checked )
02000     {
02001         _publish_timer->start();
02002         _prev_publish_time = QDateTime::currentDateTime();
02003     }
02004     else{
02005         _publish_timer->stop();
02006     }
02007 }
02008 
02009 void MainWindow::on_actionClearBuffer_triggered()
02010 {
02011     for (auto& it: _mapped_plot_data.numeric )
02012     {
02013         it.second.clear();
02014     }
02015 
02016     for (auto& it: _mapped_plot_data.user_defined )
02017     {
02018         it.second.clear();
02019     }
02020 
02021     forEachWidget( [](PlotWidget* plot) {
02022         plot->reloadPlotData();
02023         plot->replot();
02024     });
02025 }
02026 
02027 void MainWindow::on_pushButtonUseDateTime_toggled(bool checked)
02028 {
02029     updatedDisplayTime();
02030 }
02031 
02032 void MainWindow::on_pushButtonTimeTracker_pressed()
02033 {
02034     if( _tracker_param == CurveTracker::LINE_ONLY)
02035     {
02036         _tracker_param = CurveTracker::VALUE;
02037     }
02038     else if( _tracker_param == CurveTracker::VALUE)
02039     {
02040         _tracker_param = CurveTracker::VALUE_NAME;
02041     }
02042     else if( _tracker_param == CurveTracker::VALUE_NAME)
02043     {
02044         _tracker_param = CurveTracker::LINE_ONLY;
02045     }
02046     ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[ _tracker_param ] );
02047 
02048     forEachWidget( [&](PlotWidget* plot) {
02049         plot->configureTracker(_tracker_param);
02050         plot->replot();
02051     });
02052 }
02053 
02054 void MainWindow::closeEvent(QCloseEvent *event)
02055 {
02056     _replot_timer->stop();
02057     _publish_timer->stop();
02058 
02059     if( _current_streamer )
02060     {
02061         _current_streamer->shutdown();
02062         _current_streamer = nullptr;
02063     }
02064     QSettings settings;
02065     settings.setValue("MainWindow.geometry", saveGeometry());
02066     settings.setValue("MainWindow.activateGrid", ui->pushButtonActivateGrid->isChecked() );
02067     settings.setValue("MainWindow.streamingBufferValue", ui->streamingSpinBox->value() );
02068     settings.setValue("MainWindow.removeTimeOffset",ui->pushButtonRemoveTimeOffset->isChecked() );
02069     settings.setValue("MainWindow.dateTimeDisplay", ui->pushButtonUseDateTime->isChecked() );
02070     settings.setValue("MainWindow.timeTrackerSetting", (int)_tracker_param );
02071 
02072     // clean up all the plugins
02073     for(auto& it : _data_loader ) { delete it.second; }
02074     for(auto& it : _state_publisher ) { delete it.second; }
02075     for(auto& it : _data_streamer ) { delete it.second; }
02076 }
02077 
02078 void MainWindow::on_addMathPlot(const std::string& linked_name)
02079 {
02080     addOrEditMathPlot(linked_name, false);
02081 }
02082 
02083 void MainWindow::on_editMathPlot(const std::string &plot_name)
02084 {
02085     addOrEditMathPlot(plot_name, true);
02086 }
02087 
02088 void MainWindow::on_refreshMathPlot(const std::string &plot_name)
02089 {
02090     try{
02091         auto custom_it = _custom_plots.find(plot_name);
02092         if(custom_it == _custom_plots.end())
02093         {
02094             qWarning("failed to find custom equation");
02095             return;
02096         }
02097         CustomPlotPtr ce = custom_it->second;
02098 
02099         ce->calculateAndAdd(_mapped_plot_data);
02100 
02101         onUpdateLeftTableValues();
02102         updateDataAndReplot( true );
02103     }
02104     catch(const std::runtime_error &e)
02105     {
02106         QMessageBox::critical(this, "error", "Failed to refresh data : " + QString::fromStdString(e.what()));
02107     }
02108 }
02109 
02110 void MainWindow::addOrEditMathPlot(const std::string &name, bool modifying)
02111 {
02112     AddCustomPlotDialog dialog(_mapped_plot_data, _custom_plots, this);
02113 
02114     if(!modifying)
02115     {
02116         dialog.setLinkedPlotName(QString::fromStdString(name));
02117         dialog.setEditorMode( AddCustomPlotDialog::FUNCTION_OR_TIMESERIES );
02118     }
02119     else
02120     {
02121         dialog.setEditorMode( AddCustomPlotDialog::TIMESERIES_ONLY );
02122 
02123         auto custom_it = _custom_plots.find(name);
02124         if(custom_it == _custom_plots.end())
02125         {
02126             qWarning("failed to find custom equation");
02127             return;
02128         }
02129 
02130         // clear already existing data first
02131         auto data_it = _mapped_plot_data.numeric.find( name );
02132         if( data_it != _mapped_plot_data.numeric.end())
02133         {
02134             data_it->second.clear();
02135         }
02136         dialog.editExistingPlot(custom_it->second);
02137     }
02138 
02139     if(dialog.exec() == QDialog::Accepted)
02140     {
02141         const QString& qplot_name = dialog.getName();
02142         std::string plot_name = qplot_name.toStdString();
02143         CustomPlotPtr eq = dialog.getCustomPlotData();
02144 
02145         try {
02146             eq->calculateAndAdd(_mapped_plot_data);
02147         }
02148         catch(std::exception& ex)
02149         {
02150             QMessageBox::warning(this, tr("Warning"),
02151                                  tr("Failed to create the custom timeseries. Error:\n\n%1")
02152                                  .arg( ex.what() ) );
02153 
02154             return;
02155         }
02156 
02157         // keep data for reference
02158         auto custom_it = _custom_plots.find(plot_name);
02159         if( custom_it == _custom_plots.end() )
02160         {
02161             _custom_plots.insert( {plot_name, eq} );
02162         }
02163         else{
02164             custom_it->second = eq;
02165             modifying = true;
02166         }
02167 
02168         if(!modifying)
02169         {
02170             _curvelist_widget->addItem(qplot_name);
02171         }
02172         onUpdateLeftTableValues();
02173 
02174         if(modifying)
02175         {
02176             updateDataAndReplot( true );
02177         }
02178     }
02179 }
02180 
02181 void MainWindow::onPlaybackLoop()
02182 {
02183     qint64 delta_ms = (QDateTime::currentMSecsSinceEpoch() - _prev_publish_time.toMSecsSinceEpoch());
02184     _prev_publish_time = QDateTime::currentDateTime();
02185     delta_ms = std::max( (qint64)_publish_timer->interval(), delta_ms);
02186 
02187     _tracker_time +=  delta_ms*0.001*ui->playbackRate->value();
02188     if( _tracker_time >= ui->timeSlider->getMaximum())
02189     {
02190         if( !ui->playbackLoop->isChecked() )
02191         {
02192             ui->pushButtonPlay->setChecked(false);
02193         }
02194         _tracker_time = ui->timeSlider->getMinimum();
02195     }
02197     auto prev = ui->timeSlider->blockSignals(true);
02198     ui->timeSlider->setRealValue( _tracker_time );
02199     ui->timeSlider->blockSignals(prev);
02200 
02202     updatedDisplayTime();
02203     onUpdateLeftTableValues();
02204 
02205     for ( auto& it: _state_publisher)
02206     {
02207         it.second->play( _tracker_time );
02208     }
02209 
02210     forEachWidget( [&](PlotWidget* plot)
02211     {
02212         plot->setTrackerPosition( _tracker_time );
02213         plot->replot();
02214     } );
02215 }
02216 
02217 void MainWindow::on_actionReportBug_triggered()
02218 {
02219     QDesktopServices::openUrl( QUrl( "https://github.com/facontidavide/PlotJuggler/issues" ));
02220 }
02221 
02222 void MainWindow::on_actionAbout_triggered()
02223 {
02224     QDialog* dialog = new QDialog(this);
02225     auto ui = new Ui::AboutDialog();
02226     ui->setupUi(dialog);
02227 
02228     ui->label_version->setText( QApplication::applicationVersion() );
02229     dialog->setAttribute(Qt::WA_DeleteOnClose);
02230 
02231     dialog->exec();
02232 }
02233 
02234 void MainWindow::on_actionCheatsheet_triggered()
02235 {
02236     QSettings settings;
02237 
02238     HelpVideo* dialog = new HelpVideo(this);
02239     dialog->restoreGeometry(settings.value("Cheatsheet.geometry").toByteArray());
02240     dialog->setAttribute(Qt::WA_DeleteOnClose);
02241     dialog->show();
02242 
02243     connect(dialog, &QDialog::finished, this, [this, dialog]()
02244     {
02245         QSettings settings;
02246         settings.setValue("Cheatsheet.geometry", dialog->saveGeometry());
02247     } );
02248 }
02249 
02250 void MainWindow::on_actionSupportPlotJuggler_triggered()
02251 {
02252     QDialog* dialog = new QDialog(this);
02253     auto ui = new Ui::SupportDialog();
02254     ui->setupUi(dialog);
02255 
02256     dialog->setAttribute(Qt::WA_DeleteOnClose);
02257 
02258     dialog->exec();
02259 }
02260 
02261 void MainWindow::on_actionSaveAllPlotTabs_triggered()
02262 {
02263     QSettings settings;
02264     QString directory_path  = settings.value("MainWindow.saveAllPlotTabs",
02265                                              QDir::currentPath() ).toString();
02266     // Get destination folder
02267     QFileDialog saveDialog;
02268     saveDialog.setDirectory(directory_path);
02269     saveDialog.setFileMode(QFileDialog::FileMode::Directory);
02270     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
02271     saveDialog.exec();
02272 
02273     uint image_number = 1;
02274     if(saveDialog.result() == QDialog::Accepted && !saveDialog.selectedFiles().empty())
02275     {
02276         // Save Plots
02277         QString directory = saveDialog.selectedFiles().first();
02278         settings.setValue("MainWindow.saveAllPlotTabs", directory);
02279 
02280         QStringList file_names;
02281         QStringList existing_files;
02282         QDateTime current_date_time(QDateTime::currentDateTime());
02283         QString current_date_time_name(current_date_time.toString("yyyy-MM-dd_HH-mm-ss"));
02284         for(const auto& it: TabbedPlotWidget::instances())
02285         {
02286             auto tab_widget = it.second->tabWidget();
02287             for(int i=0; i< tab_widget->count(); i++)
02288             {
02289                 PlotMatrix* matrix = static_cast<PlotMatrix*>( tab_widget->widget(i) );
02290                 QString name = QString("%1/%2_%3_%4.png")
02291                         .arg(directory)
02292                         .arg(current_date_time_name)
02293                         .arg(image_number, 2, 10, QLatin1Char('0')).arg(matrix->name());
02294                 file_names.push_back( name );
02295                 image_number++;
02296 
02297                 QFileInfo check_file( file_names.back() );
02298                 if( check_file.exists() && check_file.isFile() )
02299                 {
02300                     existing_files.push_back( name );
02301                 }
02302             }
02303         }
02304         if( existing_files.isEmpty() == false)
02305         {
02306             QMessageBox msgBox;
02307             msgBox.setText("One or more files will be overwritten. ant to continue?");
02308             QString all_files;
02309             for(const auto& str: existing_files)
02310             {
02311                 all_files.push_back("\n");
02312                 all_files.append(str);
02313             }
02314             msgBox.setInformativeText(all_files);
02315             msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Ok );
02316             msgBox.setDefaultButton(QMessageBox::Ok);
02317 
02318             if( msgBox.exec() != QMessageBox::Ok)
02319             {
02320                 return;
02321             }
02322         }
02323 
02324         image_number = 0;
02325         for(const auto& it: TabbedPlotWidget::instances())
02326         {
02327             auto tab_widget = it.second->tabWidget();
02328             for(int i=0; i< tab_widget->count(); i++)
02329             {
02330                 PlotMatrix* matrix = static_cast<PlotMatrix*>( tab_widget->widget(i) );
02331                 TabbedPlotWidget::saveTabImage( file_names[image_number], matrix);
02332                 image_number++;
02333             }
02334         }
02335     }
02336 }
02337 
02338 void MainWindow::on_actionLoadData_triggered()
02339 {
02340     if( _data_loader.empty())
02341     {
02342         QMessageBox::warning(this, tr("Warning"),
02343                              tr("No plugin was loaded to process a data file\n") );
02344         return;
02345     }
02346 
02347     QSettings settings;
02348 
02349     QString file_extension_filter;
02350 
02351     std::set<QString> extensions;
02352 
02353     for (auto& it: _data_loader)
02354     {
02355         DataLoader* loader = it.second;
02356         for (QString extension: loader->compatibleFileExtensions() )
02357         {
02358             extensions.insert( extension.toLower() );
02359         }
02360     }
02361 
02362     for (const auto& it: extensions)
02363     {
02364         file_extension_filter.append( QString(" *.") + it );
02365     }
02366 
02367     QString directory_path = settings.value("MainWindow.lastDatafileDirectory", QDir::currentPath() ).toString();
02368 
02369     QFileDialog loadDialog( this );
02370     loadDialog.setFileMode(QFileDialog::ExistingFiles);
02371     loadDialog.setViewMode(QFileDialog::Detail);
02372     loadDialog.setNameFilter(file_extension_filter);
02373     loadDialog.setDirectory(directory_path);
02374 
02375     QStringList fileNames;
02376     if (loadDialog.exec())
02377     {
02378         fileNames = loadDialog.selectedFiles();
02379     }
02380 
02381     if (fileNames.isEmpty()) {
02382         return;
02383     }
02384 
02385     directory_path = QFileInfo(fileNames[0]).absolutePath();
02386     settings.setValue("MainWindow.lastDatafileDirectory", directory_path);
02387 
02388     if( loadDataFromFiles(fileNames) )
02389     {
02390         updateRecentDataMenu(fileNames);
02391     }
02392 }
02393 
02394 void MainWindow::on_actionLoadLayout_triggered()
02395 {
02396     QSettings settings;
02397 
02398     QString directory_path = settings.value("MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
02399     QString filename = QFileDialog::getOpenFileName(this, "Open Layout",
02400                                                     directory_path, "*.xml");
02401     if (filename.isEmpty()){
02402         return;
02403     }
02404 
02405     if( loadLayoutFromFile(filename) )
02406     {
02407         updateRecentLayoutMenu( {filename} );
02408     }
02409 
02410     directory_path = QFileInfo(filename).absolutePath();
02411     settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
02412 }
02413 
02414 void MainWindow::on_actionSaveLayout_triggered()
02415 
02416 {
02417     QDomDocument doc = xmlSaveState();
02418 
02419     QSettings settings;
02420 
02421     QString directory_path  = settings.value("MainWindow.lastLayoutDirectory",
02422                                              QDir::currentPath() ).toString();
02423 
02424     QFileDialog saveDialog;
02425     saveDialog.setOption(QFileDialog::DontUseNativeDialog, true);
02426 
02427     QGridLayout *save_layout = static_cast<QGridLayout*>(saveDialog.layout());
02428 
02429     QFrame* frame = new QFrame;
02430     frame->setFrameStyle(QFrame::Box | QFrame::Plain);
02431     frame->setLineWidth(1);
02432 
02433     QVBoxLayout *vbox = new QVBoxLayout;
02434     QLabel* title = new QLabel("Save Layout options");
02435     QFrame* separator = new QFrame;
02436     separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
02437 
02438     auto checkbox_datasource = new QCheckBox("Save data source");
02439     checkbox_datasource->setToolTip("ant the layout to remember the source of your data,\n"
02440                                     "i.e. the Datafile used or the Streaming Plugin loaded ?");
02441     checkbox_datasource->setFocusPolicy( Qt::NoFocus );
02442     checkbox_datasource->setChecked( settings.value("MainWindow.saveLayoutDataSource", true).toBool() );
02443 
02444     auto checkbox_snippets = new QCheckBox("Save custom transformations");
02445     checkbox_snippets->setToolTip("Do you want the layout to save the custom transformations?");
02446     checkbox_snippets->setFocusPolicy( Qt::NoFocus );
02447     checkbox_snippets->setChecked( settings.value("MainWindow.saveLayoutSnippets", true).toBool() );
02448 
02449     vbox->addWidget(title);
02450     vbox->addWidget(separator);
02451     vbox->addWidget(checkbox_datasource);
02452     vbox->addWidget(checkbox_snippets);
02453     frame->setLayout(vbox);
02454 
02455     int rows = save_layout->rowCount();
02456     int col = save_layout->columnCount();
02457     save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
02458 
02459     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
02460     saveDialog.setDefaultSuffix("xml");
02461     saveDialog.setNameFilter("XML (*.xml)");
02462     saveDialog.setDirectory(directory_path);
02463     saveDialog.exec();
02464 
02465     if(saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
02466     {
02467         return;
02468     }
02469 
02470     QString fileName = saveDialog.selectedFiles().first();
02471 
02472     if (fileName.isEmpty()){
02473         return;
02474     }
02475 
02476     directory_path = QFileInfo(fileName).absolutePath();
02477     settings.setValue("MainWindow.lastLayoutDirectory", directory_path);
02478     settings.setValue("MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked() );
02479     settings.setValue("MainWindow.saveLayoutSnippets",   checkbox_snippets->isChecked() );
02480 
02481     QDomElement root = doc.namedItem("root").toElement();
02482     root.setAttribute("version", LAYOUT_VERSION);
02483 
02484     root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
02485 
02486     root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
02487 
02488     root.appendChild( savePluginState(doc) );
02489 
02490     root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
02491 
02492     if( checkbox_datasource->isChecked() )
02493     {
02494         QDomElement loaded_list = doc.createElement( "previouslyLoaded_Datafiles" );
02495 
02496         for(const auto& loaded: _loaded_datafiles)
02497         {
02498             QDomElement file_elem =  doc.createElement( "fileInfo" );
02499             file_elem.setAttribute("filename", loaded.filename );
02500             file_elem.setAttribute("prefix", loaded.prefix );
02501 
02502             QDomElement datasources_elem = doc.createElement( "selected_datasources" );
02503             QString topics_list = loaded.selected_datasources.join(";");
02504             datasources_elem.setAttribute("value", topics_list);
02505             file_elem.appendChild( datasources_elem );
02506 
02507             file_elem.appendChild( loaded.plugin_config.firstChild() );
02508             loaded_list.appendChild( file_elem );
02509         }
02510         root.appendChild( loaded_list );
02511 
02512         if( _current_streamer )
02513         {
02514             QDomElement loaded_streamer =  doc.createElement( "previouslyLoaded_Streamer" );
02515             QString streamer_name = _current_streamer->name();
02516             streamer_name.replace(" ", "_");
02517             loaded_streamer.setAttribute("name", streamer_name );
02518             root.appendChild( loaded_streamer );
02519         }
02520     }
02521     //-----------------------------------
02522     root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
02523     if( checkbox_snippets->isChecked() )
02524     {
02525         QDomElement custom_equations =  doc.createElement("customMathEquations");
02526         for (const auto& custom_it: _custom_plots)
02527         {
02528             const auto& custom_plot = custom_it.second;
02529             custom_equations.appendChild( custom_plot->xmlSaveState(doc) );
02530         }
02531         root.appendChild(custom_equations);
02532 
02533         QByteArray snippets_xml_text = settings.value("AddCustomPlotDialog.savedXML",
02534                                                       QByteArray() ).toByteArray();
02535         auto snipped_saved = GetSnippetsFromXML(snippets_xml_text);
02536         auto snippets_root = ExportSnippets( snipped_saved, doc);
02537         root.appendChild(snippets_root);
02538     }
02539     root.appendChild( doc.createComment(" - - - - - - - - - - - - - - ") );
02540     //------------------------------------
02541     QFile file(fileName);
02542     if (file.open(QIODevice::WriteOnly)) {
02543         QTextStream stream(&file);
02544         stream << doc.toString() << endl;
02545     }
02546 }
02547 
02548 void MainWindow::on_actionFullscreen_triggered()
02549 {
02550     static bool first_call = true;
02551     if( first_call && !_minimized )
02552     {
02553         first_call = false;
02554         QMessageBox::information(this, "Remember!", "Press F10 to switch back to the normal view");
02555     }
02556 
02557     _minimized = !_minimized;
02558 
02559     ui->leftFrame->setVisible(!_minimized);
02560     ui->widgetOptions->setVisible( !_minimized && ui->pushButtonOptions->isChecked() );
02561     ui->widgetTimescale->setVisible(!_minimized);
02562     ui->menuBar->setVisible(!_minimized);
02563 
02564     for (auto& it: TabbedPlotWidget::instances() )
02565     {
02566         it.second->setControlsVisible( !_minimized );
02567     }
02568 }
02569 
02570 void MainWindow::on_actionLoadDummyData_triggered()
02571 {
02572     buildDummyData();
02573 }
02574 
02575 void MainWindow::on_actionFunctionEditor_triggered()
02576 {
02577     AddCustomPlotDialog dialog(_mapped_plot_data, _custom_plots, this);
02578     dialog.setEditorMode( AddCustomPlotDialog::FUNCTION_ONLY );
02579     dialog.exec();
02580 }
02581 
02582 void MainWindow::on_actionClearRecentData_triggered()
02583 {
02584     QMenu* menu = ui->menuRecentData;
02585     for (QAction *action: menu->actions())
02586     {
02587         if ( action->isSeparator() ){
02588             break;
02589         }
02590         menu->removeAction( action );
02591     }
02592     menu->setEnabled(false);
02593     QSettings settings;
02594     settings.setValue("MainWindow.recentlyLoadedDatafile", {} );
02595 }
02596 
02597 void MainWindow::on_actionClearRecentLayout_triggered()
02598 {
02599     QMenu* menu = ui->menuRecentLayout;
02600     for (QAction *action: menu->actions())
02601     {
02602         if ( action->isSeparator() ){
02603             break;
02604         }
02605         menu->removeAction( action );
02606     }
02607     menu->setEnabled(false);
02608     QSettings settings;
02609     settings.setValue("MainWindow.recentlyLoadedLayout", {} );
02610 }
02611 
02612 void MainWindow::on_actionDeleteAllData_triggered()
02613 {
02614     QMessageBox msgBox(this);
02615     msgBox.setWindowTitle("Warning. Can't be undone.");
02616     msgBox.setText(tr("Do you want to remove the previously loaded data?\n"));
02617     msgBox.addButton(QMessageBox::No);
02618     msgBox.addButton(QMessageBox::Yes);
02619     msgBox.setDefaultButton(QMessageBox::Yes);
02620   //  QPushButton* buttonPlaceholder = msgBox.addButton(tr("Keep empty placeholders"), QMessageBox::NoRole);
02621     auto reply = msgBox.exec();
02622 
02623     if( reply == QMessageBox::No ) {
02624         return;
02625     }
02626 
02627 //    if( msgBox.clickedButton() == buttonPlaceholder )
02628 //    {
02629 //        for( auto& it: _mapped_plot_data.numeric )
02630 //        {
02631 //            it.second.clear();
02632 //        }
02633 //        for( auto& it: _mapped_plot_data.user_defined )
02634 //        {
02635 //            it.second.clear();
02636 //        }
02637 
02638 //        for(const auto& it: TabbedPlotWidget::instances())
02639 //        {
02640 //            PlotMatrix* matrix =  it.second->currentTab() ;
02641 //            matrix->maximumZoomOut(); // includes replot
02642 //        }
02643 //    }
02644 //    else
02645     {
02646         deleteAllData();
02647     }
02648 }
02649 
02650 
02651 
02652 


plotjuggler
Author(s): Davide Faconti
autogenerated on Wed Jul 3 2019 19:28:04