mainwindow.cpp
Go to the documentation of this file.
00001 #include <functional>
00002 #include <stdio.h>
00003 #include <set>
00004 #include <QMouseEvent>
00005 #include <QDebug>
00006 #include <numeric>
00007 #include <QMimeData>
00008 #include <QMenu>
00009 #include <QStringListModel>
00010 #include <qwt_plot_canvas.h>
00011 #include <QDomDocument>
00012 #include <QDesktopServices>
00013 #include <QFileDialog>
00014 #include <QMessageBox>
00015 #include <QStringRef>
00016 #include <QThread>
00017 #include <QPluginLoader>
00018 #include <QSettings>
00019 #include <QWindow>
00020 #include <QInputDialog>
00021 #include <QCommandLineParser>
00022 #include <QMovie>
00023 #include <QScrollBar>
00024 
00025 #include "mainwindow.h"
00026 #include "ui_mainwindow.h"
00027 #include "busydialog.h"
00028 #include "busytaskdialog.h"
00029 #include "filterablelistwidget.h"
00030 #include "tabbedplotwidget.h"
00031 #include "selectlistdialog.h"
00032 #include "aboutdialog.h"
00033 #include "PlotJuggler/plotdata.h"
00034 
00035 
00036 MainWindow::MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent) :
00037     QMainWindow(parent),
00038     ui(new Ui::MainWindow),
00039     _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this),
00040     _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this),
00041     _minimize_view(Qt::Key_F10, this),
00042     _minimized(false),
00043     _current_streamer(nullptr),
00044     _disable_undo_logging(false),
00045     _tracker_param( CurveTracker::VALUE )
00046 {
00047     QLocale::setDefault(QLocale::c()); // set as default
00048 
00049     _test_option = (commandline_parser.isSet("test"));
00050 
00051     _curvelist_widget = new FilterableListWidget(this);
00052     _streamer_signal_mapper = new QSignalMapper(this);
00053 
00054     ui->setupUi(this);
00055 
00056     if( commandline_parser.isSet("buffer_size"))
00057     {
00058         int buffer_size = std::max(10, commandline_parser.value("buffer_size").toInt() );
00059         ui->streamingSpinBox->setMaximum(buffer_size);
00060     }
00061 
00062     {
00063         QIcon icon(":/icons/resources/office_chart_line_stacked.png");
00064         if (!icon.isNull())
00065         {
00066             this->setWindowIcon(icon);
00067             QApplication::setWindowIcon(icon);
00068         }
00069     }
00070 
00071     connect( _curvelist_widget->getTable()->verticalScrollBar(), &QScrollBar::sliderMoved,
00072              this, &MainWindow::updateLeftTableValues );
00073 
00074     connect( _curvelist_widget, &FilterableListWidget::hiddenItemsChanged,
00075              this, &MainWindow::updateLeftTableValues );
00076 
00077     connect(_curvelist_widget, &FilterableListWidget::deleteCurve,
00078             this, &MainWindow::deleteDataOfSingleCurve );
00079 
00080     connect( ui->timeSlider, &RealSlider::realValueChanged,
00081              this, &MainWindow::onTimeSlider_valueChanged );
00082 
00083     _main_tabbed_widget = new TabbedPlotWidget("Main Window", this, NULL, _mapped_plot_data, this);
00084 
00085     ui->centralLayout->insertWidget(0, _main_tabbed_widget);
00086     ui->leftLayout->addWidget( _curvelist_widget );
00087 
00088     ui->splitter->setCollapsible(0,true);
00089     ui->splitter->setStretchFactor(0,2);
00090     ui->splitter->setStretchFactor(1,6);
00091 
00092     connect( ui->splitter, SIGNAL(splitterMoved(int,int)), SLOT(onSplitterMoved(int,int)) );
00093 
00094     createActions();
00095 
00096     loadPlugins( QCoreApplication::applicationDirPath() );
00097     loadPlugins("/usr/local/PlotJuggler/plugins");
00098 
00099     _undo_timer.start();
00100 
00101     // save initial state
00102     onUndoableChange();
00103 
00104     _replot_timer = new QTimer(this);
00105     connect(_replot_timer, &QTimer::timeout, this, &MainWindow::updateDataAndReplot);
00106 
00107     ui->menuFile->setToolTipsVisible(true);
00108     ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
00109     ui->streamingLabel->setHidden(true);
00110     ui->streamingSpinBox->setHidden(true);
00111 
00112     this->setMenuBar(ui->menuBar);
00113     ui->menuBar->setNativeMenuBar(false);
00114 
00115     connect(_streamer_signal_mapper, SIGNAL(mapped(QString)),
00116             this, SLOT(onActionLoadStreamer(QString)) );
00117 
00118     ui->actionDeleteAllData->setEnabled( _test_option );
00119 
00120     if( _test_option )
00121     {
00122         buildDummyData();
00123     }
00124 
00125     bool file_loaded = false;
00126     if( commandline_parser.isSet("datafile"))
00127     {
00128         QMetaObject::invokeMethod(this, "onActionLoadDataFileImpl",
00129                                   Q_ARG(QString, commandline_parser.value("datafile")) );
00130         file_loaded = true;
00131     }
00132     if( commandline_parser.isSet("layout"))
00133     {
00134         QMetaObject::invokeMethod(this, "onActionLoadLayoutFromFile",
00135                                   Q_ARG(QString, commandline_parser.value("layout")),
00136                                   Q_ARG(bool, file_loaded ) );
00137     }
00138 
00139     QSettings settings( "IcarusTechnology", "PlotJuggler");
00140     restoreGeometry(settings.value("MainWindow.geometry").toByteArray());
00141 
00142     bool activate_grid = settings.value("MainWindow.activateGrid", false).toBool();
00143     ui->pushButtonActivateGrid->setChecked(activate_grid);
00144 
00145     int streaming_buffer_value = settings.value("MainWindow.streamingBufferValue", 5).toInt();
00146     ui->streamingSpinBox->setValue(streaming_buffer_value);
00147 
00148     bool datetime_display  = settings.value("MainWindow.dateTimeDisplay", false).toBool();
00149     ui->pushButtonUseDateTime->setChecked( datetime_display );
00150 
00151     ui->widgetOptions->setVisible( ui->pushButtonOptions->isChecked() );
00152     ui->line->setVisible( ui->pushButtonOptions->isChecked() );
00153 
00154     //----------------------------------------------------------
00155     QIcon trackerIconA, trackerIconB, trackerIconC;
00156 
00157     trackerIconA.addFile(QStringLiteral(":/icons/resources/line_tracker.png"), QSize(36, 36));
00158     trackerIconB.addFile(QStringLiteral(":/icons/resources/line_tracker_1.png"), QSize(36, 36));
00159     trackerIconC.addFile(QStringLiteral(":/icons/resources/line_tracker_a.png"), QSize(36, 36));
00160 
00161     _tracker_button_icons[CurveTracker::LINE_ONLY]  = trackerIconA;
00162     _tracker_button_icons[CurveTracker::VALUE]      = trackerIconB;
00163     _tracker_button_icons[CurveTracker::VALUE_NAME] = trackerIconC;
00164 
00165     int tracker_setting = settings.value("MainWindow.timeTrackerSetting", (int)CurveTracker::VALUE ).toInt();
00166     _tracker_param = static_cast<CurveTracker::Parameter>(tracker_setting);
00167 
00168     ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[_tracker_param] );
00169 
00170     forEachWidget( [&](PlotWidget* plot) {
00171         plot->configureTracker(_tracker_param);
00172     });
00173 }
00174 
00175 MainWindow::~MainWindow()
00176 {
00177     delete ui;
00178 }
00179 
00180 void MainWindow::onUndoableChange()
00181 {
00182     if(_disable_undo_logging) return;
00183 
00184     int elapsed_ms = _undo_timer.restart();
00185 
00186     // overwrite the previous
00187     if( elapsed_ms < 100)
00188     {
00189         if( _undo_states.empty() == false)
00190             _undo_states.pop_back();
00191     }
00192 
00193     while( _undo_states.size() >= 100 ) _undo_states.pop_front();
00194     _undo_states.push_back( xmlSaveState() );
00195     _redo_states.clear();
00196     //  qDebug() << "undo " << _undo_states.size();
00197 }
00198 
00199 
00200 void MainWindow::onRedoInvoked()
00201 {
00202     _disable_undo_logging = true;
00203     if( _redo_states.size() > 0)
00204     {
00205         QDomDocument state_document = _redo_states.back();
00206         while( _undo_states.size() >= 100 ) _undo_states.pop_front();
00207         _undo_states.push_back( state_document );
00208         _redo_states.pop_back();
00209 
00210         xmlLoadState( state_document );
00211     }
00212     _disable_undo_logging = false;
00213 }
00214 
00215 
00216 void MainWindow::updateLeftTableValues()
00217 {
00218     const auto& table = _curvelist_widget->getTable();
00219 
00220     if( table->isColumnHidden(1) == false)
00221     {
00222         const int vertical_height = table->visibleRegion().boundingRect().height();
00223 
00224         for (int row = 0; row < _curvelist_widget->rowCount(); row++)
00225         {
00226             int vertical_pos = table->rowViewportPosition(row);
00227             if( vertical_pos < 0 || table->isRowHidden(row) ){ continue; }
00228             if( vertical_pos > vertical_height){ break; }
00229 
00230             const std::string name = table->item(row,0)->text().toStdString();
00231             auto it = _mapped_plot_data.numeric.find(name);
00232             if( it !=  _mapped_plot_data.numeric.end())
00233             {
00234                 nonstd::optional<PlotData::TimeType> value;
00235                 PlotDataPtr data = it->second;
00236 
00237                 double num = 0.0;
00238                 bool valid = false;
00239 
00240                 if( _tracker_time < std::numeric_limits<double>::max())
00241                 {
00242                     auto value = data->getYfromX( _tracker_time );
00243                     if(value){
00244                         valid = true;
00245                         num = value.value();
00246                     }
00247                 }
00248                 else{
00249                     if( data->size() > 0) {
00250                         valid = true;
00251                         num = (data->at( data->size()-1 )).y;
00252                     }
00253                 }
00254                 if( valid)
00255                 {
00256                     QString num_text = QString::number( num, 'f', 3);
00257                     if(num_text.contains('.'))
00258                     {
00259                         int idx = num_text.length() -1;
00260                         while( num_text[idx] == '0' )
00261                         {
00262                             num_text[idx] = ' ';
00263                             idx--;
00264                         }
00265                         if(  num_text[idx] == '.') num_text[idx] = ' ';
00266                     }
00267                     table->item(row,1)->setText(num_text + ' ');
00268                 }
00269             }
00270         }
00271     }
00272 }
00273 
00274 
00275 void MainWindow::onTrackerMovedFromWidget(QPointF relative_pos)
00276 {
00277     _tracker_time = relative_pos.x() + _time_offset.get();
00278 
00279     auto prev = ui->timeSlider->blockSignals(true);
00280     ui->timeSlider->setRealValue( relative_pos.x() );
00281     ui->timeSlider->blockSignals(prev);
00282 
00283     onTrackerTimeUpdated( _tracker_time );
00284 }
00285 
00286 void MainWindow::onTimeSlider_valueChanged(double relative_time)
00287 {
00288     _tracker_time = relative_time + _time_offset.get();
00289     onTrackerTimeUpdated( _tracker_time );
00290 }
00291 
00292 void MainWindow::onTrackerTimeUpdated(double absolute_time)
00293 {
00294     updatedDisplayTime();
00295     updateLeftTableValues();
00296 
00297     for ( auto it: _state_publisher)
00298     {
00299         it.second->updateState( &_mapped_plot_data, absolute_time);
00300     }
00301 
00302     forEachWidget( [&](PlotWidget* plot)
00303     {
00304         plot->setTrackerPosition( _tracker_time );
00305         plot->replot();
00306     } );
00307 }
00308 
00309 void MainWindow::createTabbedDialog(QString suggest_win_name, PlotMatrix* first_tab)
00310 {
00311     if( suggest_win_name.isEmpty())
00312     {
00313         for (int i=0; i<= TabbedPlotWidget::instances().size(); i++)
00314         {
00315             suggest_win_name = QString("Window%1").arg(i);
00316             TabbedPlotWidget* tw = TabbedPlotWidget::instance(suggest_win_name);
00317             if( tw == nullptr )
00318             {
00319                 break;
00320             }
00321         }
00322     }
00323 
00324     SubWindow* window = new SubWindow(suggest_win_name, first_tab, _mapped_plot_data, this );
00325 
00326     connect( window, SIGNAL(destroyed(QObject*)),  this,  SLOT(onFloatingWindowDestroyed(QObject*)) );
00327     connect( window, SIGNAL(destroyed(QObject*)),  this,  SLOT(onUndoableChange()) );
00328 
00329     window->tabbedWidget()->setStreamingMode( isStreamingActive() );
00330 
00331     window->setAttribute( Qt::WA_DeleteOnClose, true );
00332     window->show();
00333     window->activateWindow();
00334     window->raise();
00335 
00336     if (this->signalsBlocked() == false) onUndoableChange();
00337 }
00338 
00339 
00340 void MainWindow::createActions()
00341 {
00342     _undo_shortcut.setContext(Qt::ApplicationShortcut);
00343     _redo_shortcut.setContext(Qt::ApplicationShortcut);
00344     _minimize_view.setContext(Qt::ApplicationShortcut);
00345 
00346     connect( &_undo_shortcut, &QShortcut::activated, this, &MainWindow::onUndoInvoked );
00347     connect( &_redo_shortcut, &QShortcut::activated, this, &MainWindow::onRedoInvoked );
00348     connect( &_minimize_view, &QShortcut::activated, this, &MainWindow::on_minimizeView);
00349     //---------------------------------------------
00350 
00351     connect(ui->actionSaveLayout, &QAction::triggered,         this, &MainWindow::onActionSaveLayout );
00352     connect(ui->actionLoadLayout, &QAction::triggered,         this, &MainWindow::onActionLoadLayout );
00353     connect(ui->actionLoadData, &QAction::triggered,           this, &MainWindow::onActionLoadDataFile );
00354     connect(ui->actionLoadRecentDatafile, &QAction::triggered, this, &MainWindow::onActionReloadDataFileFromSettings );
00355     connect(ui->actionLoadRecentLayout, &QAction::triggered,   this, &MainWindow::onActionReloadRecentLayout );
00356     connect(ui->actionDeleteAllData, &QAction::triggered,      this, &MainWindow::onDeleteLoadedData );
00357 
00358     //---------------------------------------------
00359 
00360     QSettings settings( "IcarusTechnology", "PlotJuggler");
00361     if( settings.contains("MainWindow.recentlyLoadedDatafile") )
00362     {
00363         QString filename = settings.value("MainWindow.recentlyLoadedDatafile").toString();
00364         ui->actionLoadRecentDatafile->setText( "Load data from: " + filename);
00365         ui->actionLoadRecentDatafile->setEnabled( true );
00366     }
00367     else{
00368         ui->actionLoadRecentDatafile->setEnabled( false );
00369     }
00370 
00371     if( settings.contains("MainWindow.recentlyLoadedLayout") )
00372     {
00373         QString filename = settings.value("MainWindow.recentlyLoadedLayout").toString();
00374         ui->actionLoadRecentLayout->setText( "Load layout from: " + filename);
00375         ui->actionLoadRecentLayout->setEnabled( true );
00376     }
00377     else{
00378         ui->actionLoadRecentLayout->setEnabled( false );
00379     }
00380 }
00381 
00382 void MainWindow::loadPlugins(QString directory_name)
00383 {
00384     static std::set<QString> loaded_plugins;
00385 
00386     QDir pluginsDir( directory_name );
00387 
00388     for (QString filename: pluginsDir.entryList(QDir::Files))
00389     {
00390         QFileInfo fileinfo(filename);
00391         if( fileinfo.suffix() != "so" && fileinfo.suffix() != "dll"){
00392             continue;
00393         }
00394 
00395         if( loaded_plugins.find( filename ) != loaded_plugins.end())
00396         {
00397             continue;
00398         }
00399 
00400         QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename), this);
00401 
00402         QObject *plugin = pluginLoader.instance();
00403         if (plugin)
00404         {
00405             DataLoader *loader        = qobject_cast<DataLoader *>(plugin);
00406             StatePublisher *publisher = qobject_cast<StatePublisher *>(plugin);
00407             DataStreamer *streamer    =  qobject_cast<DataStreamer *>(plugin);
00408 
00409             if (loader)
00410             {
00411                 qDebug() << filename << ": is a DataLoader plugin";
00412                 if( !_test_option && loader->isDebugPlugin())
00413                 {
00414                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00415                 }
00416                 else{
00417                     loaded_plugins.insert( loader->name() );
00418                     _data_loader.insert( std::make_pair( loader->name(), loader) );
00419                 }
00420             }
00421             else if (publisher)
00422             {
00423                 qDebug() << filename << ": is a StatePublisher plugin";
00424                 if( !_test_option && publisher->isDebugPlugin())
00425                 {
00426                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00427                 }
00428                 else
00429                 {
00430                     loaded_plugins.insert( publisher->name() );
00431 
00432                     _state_publisher.insert( std::make_pair(publisher->name(), publisher) );
00433 
00434                     QAction* activatePublisher = new QAction(tr("Start: ") + publisher->name() , this);
00435                     activatePublisher->setCheckable(true);
00436                     activatePublisher->setChecked(false);
00437                     ui->menuPublishers->setEnabled(true);
00438                     ui->menuPublishers->addAction(activatePublisher);
00439 
00440                     publisher->setParentMenu( ui->menuPublishers );
00441 
00442                     ui->menuPublishers->addSeparator();
00443 
00444                     connect(activatePublisher, &QAction::toggled,
00445                            [=](bool enable) { publisher->setEnabled( enable ); } );
00446                 }
00447             }
00448             else if (streamer)
00449             {
00450                 qDebug() << filename << ": is a DataStreamer plugin";
00451                 if( !_test_option && streamer->isDebugPlugin())
00452                 {
00453                     qDebug() << filename << "...but will be ignored unless the argument -t is used.";
00454                 }
00455                 else{
00456                     QString name(streamer->name());
00457                     loaded_plugins.insert( name );
00458                     _data_streamer.insert( std::make_pair(name , streamer ) );
00459 
00460                     QAction* startStreamer = new QAction(QString("Start: ") + name, this);
00461                     ui->menuStreaming->setEnabled(true);
00462                     ui->menuStreaming->addAction(startStreamer);
00463 
00464                     streamer->setParentMenu( ui->menuStreaming );
00465                     ui->menuStreaming->addSeparator();
00466 
00467                     connect(startStreamer, SIGNAL(triggered()),
00468                             _streamer_signal_mapper, SLOT(map()) );
00469                     _streamer_signal_mapper->setMapping(startStreamer, name );
00470                 }
00471             }
00472         }
00473         else{
00474             if( pluginLoader.errorString().contains("is not an ELF object") == false)
00475             {
00476                 qDebug() << filename << ": " << pluginLoader.errorString();
00477             }
00478         }
00479     }
00480 }
00481 
00482 void MainWindow::buildDummyData()
00483 {
00484     size_t SIZE = 100*1000;
00485 
00486     QStringList  words_list;
00487     words_list << "world/siam" << "world/tre" << "walk/piccoli" << "walk/porcellin"
00488                << "fly/high/mai" << "fly/high/nessun" << "fly/low/ci" << "fly/low/dividera"
00489                << "data_1" << "data_2" << "data_3" << "data_10";
00490 
00491     for(auto& word: words_list){
00492         _curvelist_widget->addItem( word, true );
00493     }
00494 
00495 
00496     for( const QString& name: words_list)
00497     {
00498         double A =  6* ((double)qrand()/(double)RAND_MAX)  - 3;
00499         double B =  3* ((double)qrand()/(double)RAND_MAX)  ;
00500         double C =  3* ((double)qrand()/(double)RAND_MAX)  ;
00501         double D =  20* ((double)qrand()/(double)RAND_MAX)  ;
00502 
00503         PlotDataPtr plot ( new PlotData( name.toStdString().c_str() ) );
00504 
00505         double t = 0;
00506         for (unsigned indx=0; indx<SIZE; indx++)
00507         {
00508             t += 0.001;
00509             plot->pushBack( PlotData::Point( t,  A*sin(B*t + C) + D*t*0.02 ) ) ;
00510         }
00511         _mapped_plot_data.numeric.insert( std::make_pair( name.toStdString(), plot) );
00512     }
00513 
00514     //---------------------------------------
00515     PlotDataPtr sin_plot ( new PlotData( "_sin" ) );
00516     PlotDataPtr cos_plot ( new PlotData( "_cos" ) );
00517 
00518     double t = 0;
00519     for (unsigned indx=0; indx<SIZE; indx++)
00520     {
00521         t += 0.001;
00522         sin_plot->pushBack( PlotData::Point( t,  1.0*sin(t*0.4) ) ) ;
00523         cos_plot->pushBack( PlotData::Point( t,  2.0*cos(t*0.4) ) ) ;
00524     }
00525 
00526     _mapped_plot_data.numeric.insert( std::make_pair( sin_plot->name(), sin_plot) );
00527     _mapped_plot_data.numeric.insert( std::make_pair( cos_plot->name(), cos_plot) );
00528 
00529     _curvelist_widget->addItem( QString::fromStdString(sin_plot->name()), true );
00530     _curvelist_widget->addItem( QString::fromStdString(cos_plot->name()), true );
00531     //--------------------------------------
00532 
00533     updateTimeSlider();
00534 
00535     _curvelist_widget->updateFilter();
00536 
00537     forEachWidget( [](PlotWidget* plot) {
00538         plot->reloadPlotData();
00539     } );
00540 }
00541 
00542 void MainWindow::onSplitterMoved(int , int )
00543 {
00544     QList<int> sizes = ui->splitter->sizes();
00545     int maxLeftWidth = _curvelist_widget->maximumWidth();
00546     int totalWidth = sizes[0] + sizes[1];
00547 
00548     if( sizes[0] > maxLeftWidth)
00549     {
00550         sizes[0] = maxLeftWidth;
00551         sizes[1] = totalWidth - maxLeftWidth;
00552         ui->splitter->setSizes(sizes);
00553     }
00554 }
00555 
00556 void MainWindow::resizeEvent(QResizeEvent *)
00557 {
00558     onSplitterMoved( 0, 0 );
00559 }
00560 
00561 
00562 void MainWindow::onPlotAdded(PlotWidget* plot)
00563 {
00564     connect( plot, &PlotWidget::undoableChange,
00565              this, &MainWindow::onUndoableChange );
00566 
00567     connect( plot, &PlotWidget::trackerMoved,
00568              this, &MainWindow::onTrackerMovedFromWidget);
00569 
00570     connect( plot, &PlotWidget::swapWidgetsRequested,
00571              this, &MainWindow::onSwapPlots);
00572 
00573     connect( this, &MainWindow::requestRemoveCurveByName,
00574              plot, &PlotWidget::removeCurve) ;
00575 
00576     connect( &_time_offset, SIGNAL( valueChanged(double)),
00577              plot, SLOT(on_changeTimeOffset(double)) );
00578 
00579     plot->on_changeTimeOffset( _time_offset.get() );
00580     plot->activateGrid( ui->pushButtonActivateGrid->isChecked() );
00581     plot->enableTracker( !isStreamingActive() );
00582     plot->configureTracker( _tracker_param );
00583 }
00584 
00585 void MainWindow::onPlotMatrixAdded(PlotMatrix* matrix)
00586 {
00587     connect( matrix, &PlotMatrix::plotAdded,      this, &MainWindow:: onPlotAdded);
00588     connect( matrix, &PlotMatrix::undoableChange, this, &MainWindow:: onUndoableChange );
00589 }
00590 
00591 QDomDocument MainWindow::xmlSaveState() const
00592 {
00593     QDomDocument doc;
00594     QDomProcessingInstruction instr =
00595             doc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
00596 
00597     doc.appendChild(instr);
00598 
00599     QDomElement root = doc.createElement( "root" );
00600 
00601     QDomElement main_area =_main_tabbed_widget->xmlSaveState(doc);
00602     root.appendChild( main_area );
00603 
00604     for (auto& it: TabbedPlotWidget::instances() )
00605     {
00606         QDomElement tabbed_area = it.second->xmlSaveState(doc);
00607         root.appendChild( tabbed_area );
00608     }
00609 
00610     doc.appendChild(root);
00611 
00612     QDomElement relative_time = doc.createElement( "use_relative_time_offset" );
00613     relative_time.setAttribute("enabled", ui->pushButtonRemoveTimeOffset->isChecked() );
00614     root.appendChild( relative_time );
00615 
00616     return doc;
00617 }
00618 
00619 bool MainWindow::xmlLoadState(QDomDocument state_document)
00620 {
00621     QDomElement root = state_document.namedItem("root").toElement();
00622     if ( root.isNull() ) {
00623         qWarning() << "No <root> element found at the top-level of the XML file!";
00624         return false;
00625     }
00626 
00627     size_t num_floating = 0;
00628     std::map<QString,QDomElement> tabbed_widgets_with_name;
00629 
00630     for (QDomElement tw = root.firstChildElement(  "tabbed_widget" )  ;
00631          tw.isNull() == false;
00632          tw = tw.nextSiblingElement( "tabbed_widget" ) )
00633     {
00634         if( ! tw.hasAttribute("name") ||  ! tw.hasAttribute("parent"))
00635         {
00636             QMessageBox::warning(0, tr("Warning"),
00637                                  tr("This Layout format can not be parsed anymore\n") );
00638             return false;
00639         }
00640 
00641         if( tw.attribute("parent") != ("main_window") )
00642         {
00643             num_floating++;
00644         }
00645         tabbed_widgets_with_name[ tw.attribute("name") ] = tw;
00646     }
00647 
00648     // add if missing
00649     for(const auto& it: tabbed_widgets_with_name)
00650     {
00651         if( TabbedPlotWidget::instance( it.first ) == nullptr)
00652         {
00653             createTabbedDialog( it.first, NULL );
00654         }
00655     }
00656 
00657     // remove those which don't share list of names
00658     for(const auto& it: TabbedPlotWidget::instances())
00659     {
00660         if( tabbed_widgets_with_name.count( it.first ) == 0)
00661         {
00662             it.second->deleteLater();
00663         }
00664     }
00665 
00666     //-----------------------------------------------------
00667 
00668     for ( QDomElement tw = root.firstChildElement(  "tabbed_widget" )  ;
00669            tw.isNull() == false;
00670            tw = tw.nextSiblingElement( "tabbed_widget" ) )
00671     {
00672         if( tw.attribute("parent") == ("main_window") )
00673         {
00674             _main_tabbed_widget->xmlLoadState( tw );
00675         }
00676         else{
00677             TabbedPlotWidget* tabwidget = TabbedPlotWidget::instance( tw.attribute("name"));
00678             tabwidget->xmlLoadState( tw );
00679          }
00680     }
00681 
00682     QDomElement relative_time = root.firstChildElement( "use_relative_time_offset" );
00683     if( !relative_time.isNull())
00684     {
00685         bool remove_offset = (relative_time.attribute("enabled") == QString("1"));
00686         ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
00687     }
00688 
00689     return true;
00690 }
00691 
00692 void MainWindow::onActionSaveLayout()
00693 {
00694     QDomDocument doc = xmlSaveState();
00695 
00696     if( _loaded_datafile.isEmpty() == false)
00697     {
00698         QDomElement root = doc.namedItem("root").toElement();
00699         QDomElement previously_loaded_datafile =  doc.createElement( "previouslyLoadedDatafile" );
00700         previously_loaded_datafile.setAttribute("filename", _loaded_datafile );
00701         previously_loaded_datafile.setAttribute("configuration", _last_load_configuration );
00702         root.appendChild( previously_loaded_datafile );
00703     }
00704     if( _current_streamer )
00705     {
00706         QDomElement root = doc.namedItem("root").toElement();
00707         QDomElement loaded_streamer =  doc.createElement( "previouslyLoadedStreamer" );
00708         loaded_streamer.setAttribute("name", _current_streamer->name() );
00709         loaded_streamer.setAttribute("configuration", _last_stream_configuration );
00710         root.appendChild( loaded_streamer );
00711     }
00712 
00713     QSettings settings( "IcarusTechnology", "PlotJuggler");
00714 
00715     QString directory_path  = settings.value("MainWindow.lastLayoutDirectory",
00716                                              QDir::currentPath() ). toString();
00717 
00718     QFileDialog saveDialog;
00719     saveDialog.setAcceptMode(QFileDialog::AcceptSave);
00720     saveDialog.setDefaultSuffix("xml");
00721     saveDialog.setNameFilter("XML (*.xml)");
00722     saveDialog.setDirectory(directory_path);
00723     saveDialog.exec();
00724 
00725     if(saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
00726     {
00727         return;
00728     }
00729 
00730     QString fileName = saveDialog.selectedFiles().first();
00731 
00732     if (fileName.isEmpty())
00733         return;
00734 
00735     QFile file(fileName);
00736     if (file.open(QIODevice::WriteOnly)) {
00737         QTextStream stream(&file);
00738         stream << doc.toString() << endl;
00739     }
00740 }
00741 
00742 void MainWindow::deleteDataOfSingleCurve(const QString& curve_name)
00743 {
00744     auto plot_curve = _mapped_plot_data.numeric.find( curve_name.toStdString() );
00745     if( plot_curve == _mapped_plot_data.numeric.end())
00746     {
00747         return;
00748     }
00749 
00750     _mapped_plot_data.numeric.erase( plot_curve );
00751 
00752     auto rows_to_remove = _curvelist_widget->findRowsByName( curve_name );
00753     for(int row : rows_to_remove)
00754     {
00755         _curvelist_widget->removeRow(row);
00756     }
00757 
00758     emit requestRemoveCurveByName( curve_name );
00759 
00760 
00761     if( _curvelist_widget->rowCount() == 0)
00762     {
00763         ui->actionDeleteAllData->setEnabled( false );
00764     }
00765 }
00766 
00767 
00768 void MainWindow::onDeleteLoadedData()
00769 {
00770     QMessageBox::StandardButton reply;
00771     reply = QMessageBox::question(0, tr("Warning"),
00772                                   tr("Do you really want to remove the loaded data?\n"),
00773                                   QMessageBox::Yes | QMessageBox::No,
00774                                   QMessageBox::No );
00775     if( reply == QMessageBox::No ) {
00776         return;
00777     }
00778 
00779     _mapped_plot_data.numeric.clear();
00780     _mapped_plot_data.user_defined.clear();
00781 
00782     _curvelist_widget->clear();
00783 
00784     forEachWidget( [](PlotWidget* plot) {
00785         plot->detachAllCurves();
00786     } );
00787 
00788     ui->actionDeleteAllData->setEnabled( false );
00789 
00790 }
00791 
00792 void MainWindow::onActionLoadDataFile(bool reload_from_settings)
00793 {
00794     if( _data_loader.empty())
00795     {
00796         QMessageBox::warning(0, tr("Warning"),
00797                              tr("No plugin was loaded to process a data file\n") );
00798         return;
00799     }
00800 
00801     QSettings settings( "IcarusTechnology", "PlotJuggler");
00802 
00803     QString file_extension_filter;
00804 
00805     std::set<QString> extensions;
00806 
00807     for (auto& it: _data_loader)
00808     {
00809         DataLoader* loader = it.second;
00810         for (QString extension: loader->compatibleFileExtensions() )
00811         {
00812             extensions.insert( extension.toLower() );
00813         }
00814     }
00815 
00816     for (auto it = extensions.begin(); it != extensions.end(); it++)
00817     {
00818         file_extension_filter.append( QString(" *.") + *it );
00819     }
00820 
00821     QString directory_path = settings.value("MainWindow.lastDatafileDirectory", QDir::currentPath() ).toString();
00822 
00823     QString filename;
00824     if( reload_from_settings && settings.contains("MainWindow.recentlyLoadedDatafile") )
00825     {
00826         filename = settings.value("MainWindow.recentlyLoadedDatafile").toString();
00827     }
00828     else{
00829         filename = QFileDialog::getOpenFileName(this, "Open Datafile",
00830                                                 directory_path,
00831                                                 file_extension_filter);
00832     }
00833 
00834     if (filename.isEmpty()) {
00835         return;
00836     }
00837 
00838     directory_path = QFileInfo(filename).absolutePath();
00839 
00840     settings.setValue("MainWindow.lastDatafileDirectory", directory_path);
00841     settings.setValue("MainWindow.recentlyLoadedDatafile", filename);
00842 
00843     ui->actionLoadRecentDatafile->setText("Load data from: " + filename);
00844 
00845     onActionLoadDataFileImpl(filename, false );
00846 }
00847 
00848 void MainWindow::importPlotDataMap(const PlotDataMap& new_data, bool delete_older)
00849 {
00850     // overwrite the old user_defined map
00851     _mapped_plot_data.user_defined = new_data.user_defined;
00852 
00853     for (auto& it: new_data.numeric)
00854     {
00855         const std::string& name  = it.first;
00856         PlotDataPtr plot  = it.second;
00857         auto plot_with_same_name = _mapped_plot_data.numeric.find(name);
00858 
00859         // this is a new plot
00860         if( plot_with_same_name == _mapped_plot_data.numeric.end() )
00861         {
00862             _curvelist_widget->addItem( QString::fromStdString( name ), false );
00863             _mapped_plot_data.numeric.insert( std::make_pair(name, plot) );
00864         }
00865         else{ // a plot with the same name existed already, overwrite it
00866             plot_with_same_name->second = plot;
00867         }
00868     }
00869     _curvelist_widget->sortColumns();
00870 
00871     if( delete_older && _mapped_plot_data.numeric.size() > new_data.numeric.size() )
00872     {
00873         QMessageBox::StandardButton reply;
00874         reply = QMessageBox::question(0, tr("Warning"),
00875                                       tr("Do you want to remove the previously loaded data?\n"),
00876                                       QMessageBox::Yes | QMessageBox::No,
00877                                       QMessageBox::Yes );
00878         if( reply == QMessageBox::Yes )
00879         {
00880             std::vector<std::string> data_to_remove;
00881 
00882             for (auto& it: _mapped_plot_data.numeric )
00883             {
00884                 auto& name = it.first;
00885                 if( new_data.numeric.find( name ) == new_data.numeric.end() ){
00886                     data_to_remove.push_back(name);
00887                 }
00888             }
00889             for (auto& to_remove: data_to_remove )
00890             {
00891                 this->deleteDataOfSingleCurve( QString( to_remove.c_str() ) );
00892             }
00893         }
00894     }
00895 
00896     forEachWidget( [](PlotWidget* plot) {
00897         plot->reloadPlotData();
00898     } );
00899 
00900     updateTimeSlider();
00901 }
00902 
00903 bool MainWindow::isStreamingActive() const
00904 {
00905     return ui->pushButtonStreaming->isChecked();
00906 }
00907 
00908 void MainWindow::onActionLoadDataFileImpl(QString filename, bool reuse_last_configuration )
00909 {
00910     const QString extension = QFileInfo(filename).suffix().toLower();
00911 
00912     DataLoader* loader = nullptr;
00913 
00914     typedef std::map<QString,DataLoader*>::iterator MapIterator;
00915 
00916     std::vector<MapIterator> compatible_loaders;
00917 
00918     for (MapIterator it = _data_loader.begin(); it != _data_loader.end(); it++)
00919     {
00920         DataLoader* data_loader = it->second;
00921         std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
00922 
00923         for(auto& ext: extensions){
00924 
00925             if( extension == QString(ext).toLower()){
00926                 compatible_loaders.push_back( it );
00927                 break;
00928             }
00929         }
00930     }
00931 
00932     if( compatible_loaders.size() == 1)
00933     {
00934         loader = compatible_loaders.front()->second;
00935     }
00936     else{
00937         static QString last_plugin_name_used;
00938 
00939         QStringList names;
00940         for (auto cl: compatible_loaders)
00941         {
00942             const auto& name = cl->first;
00943 
00944             if( name == last_plugin_name_used ){
00945                 names.push_front( name );
00946             }
00947             else{
00948                 names.push_back( name );
00949             }
00950         }
00951 
00952         bool ok;
00953         QString plugin_name = QInputDialog::getItem(this, tr("QInputDialog::getItem()"), tr("Select the loader to use:"), names, 0, false, &ok);
00954         if (ok && !plugin_name.isEmpty())
00955         {
00956             loader = _data_loader[ plugin_name ];
00957             last_plugin_name_used = plugin_name;
00958         }
00959     }
00960 
00961     if( loader )
00962     {
00963         QFile file(filename);
00964 
00965         if (!file.open(QFile::ReadOnly | QFile::Text)) {
00966             QMessageBox::warning(this, tr("Datafile"),
00967                                  tr("Cannot read file %1:\n%2.")
00968                                  .arg(filename)
00969                                  .arg(file.errorString()));
00970             return;
00971         }
00972         file.close();
00973 
00974         _loaded_datafile = filename;
00975         ui->actionDeleteAllData->setEnabled( true );
00976 
00977         QString load_configuration; // must be empty by default
00978         if( reuse_last_configuration )
00979         {
00980             load_configuration = _last_load_configuration;
00981         }
00982 
00983 
00984         PlotDataMap mapped_data;
00985         try{
00986             mapped_data= loader->readDataFromFile(
00987                         filename,
00988                         load_configuration   );
00989         }
00990         catch(std::exception &ex)
00991         {
00992             QMessageBox::warning(this, tr("Exception from the plugin"),
00993                                  tr("The plugin [%1] thrown the following exception: \n\n %3\n")
00994                                  .arg(loader->name()).arg(ex.what()) );
00995             return;
00996         }
00997 
00998         _last_load_configuration = load_configuration;
00999 
01000         // remap to different type
01001         importPlotDataMap(mapped_data, true);
01002     }
01003     else{
01004         QMessageBox::warning(this, tr("Error"),
01005                              tr("Cannot read files with extension %1.\n No plugin can handle that!\n")
01006                              .arg(filename) );
01007     }
01008     _curvelist_widget->updateFilter();
01009 }
01010 
01011 
01012 void MainWindow::onActionReloadDataFileFromSettings()
01013 {
01014     onActionLoadDataFile( true );
01015 }
01016 
01017 void MainWindow::onActionReloadRecentLayout()
01018 {
01019     onActionLoadLayout( true );
01020 }
01021 
01022 void MainWindow::onActionLoadStreamer(QString streamer_name)
01023 {
01024     if( _current_streamer )
01025     {
01026         _current_streamer->shutdown();
01027         _current_streamer = nullptr;
01028     }
01029 
01030     if( _data_streamer.empty())
01031     {
01032         qDebug() << "Error, no streamer loaded";
01033         return;
01034     }
01035 
01036     if( _data_streamer.size() == 1)
01037     {
01038         _current_streamer = _data_streamer.begin()->second;
01039     }
01040     else if( _data_streamer.size() > 1)
01041     {
01042         auto it = _data_streamer.find(streamer_name);
01043         if( it != _data_streamer.end())
01044         {
01045             _current_streamer = it->second;
01046         }
01047         else{
01048             qDebug() << "Error. The streamer " << streamer_name <<
01049                         " can't be loaded";
01050             return;
01051         }
01052     }
01053 
01054 
01055     if( _current_streamer && _current_streamer->start( _last_stream_configuration) )
01056     {
01057         _current_streamer->enableStreaming( false );
01058         importPlotDataMap( _current_streamer->getDataMap(), true );
01059         _loaded_datafile = QString();
01060 
01061         for(auto& action: ui->menuStreaming->actions()) {
01062             action->setEnabled(false);
01063         }
01064         ui->actionStopStreaming->setEnabled(true);
01065         ui->actionDeleteAllData->setEnabled( false );
01066         ui->actionDeleteAllData->setToolTip("Stop streaming to be able to delete the data");
01067 
01068         ui->pushButtonStreaming->setEnabled(true);
01069         ui->pushButtonStreaming->setChecked(true);
01070 
01071         on_streamingSpinBox_valueChanged( ui->streamingSpinBox->value() );
01072     }
01073     else{
01074         qDebug() << "Failed to launch the streamer";
01075     }
01076 }
01077 
01078 void MainWindow::onActionLoadLayout(bool reload_previous)
01079 {
01080     QSettings settings( "IcarusTechnology", "PlotJuggler");
01081 
01082     QString directory_path = QDir::currentPath();
01083 
01084     if( settings.contains("MainWindow.lastLayoutDirectory") )
01085     {
01086         directory_path = settings.value("MainWindow.lastLayoutDirectory").toString();
01087     }
01088 
01089     QString filename;
01090     if( reload_previous && settings.contains("MainWindow.recentlyLoadedLayout") )
01091     {
01092         filename = settings.value("MainWindow.recentlyLoadedLayout").toString();
01093     }
01094     else{
01095         filename = QFileDialog::getOpenFileName(this,
01096                                                 "Open Layout",
01097                                                 directory_path,
01098                                                 "*.xml");
01099     }
01100     if (filename.isEmpty())
01101         return;
01102     else
01103         onActionLoadLayoutFromFile(filename, true);
01104 }
01105 
01106 void MainWindow::onActionLoadLayoutFromFile(QString filename, bool load_data)
01107 {
01108     QSettings settings( "IcarusTechnology", "PlotJuggler");
01109 
01110     QString directory_path = QFileInfo(filename).absolutePath();
01111     settings.setValue("MainWindow.lastLayoutDirectory",  directory_path);
01112     settings.setValue("MainWindow.recentlyLoadedLayout", filename);
01113 
01114     ui->actionLoadRecentLayout->setText("Load layout from: " + filename);
01115 
01116     QFile file(filename);
01117     if (!file.open(QFile::ReadOnly | QFile::Text)) {
01118         QMessageBox::warning(this, tr("Layout"),
01119                              tr("Cannot read file %1:\n%2.")
01120                              .arg(filename)
01121                              .arg(file.errorString()));
01122         return;
01123     }
01124 
01125     QString errorStr;
01126     int errorLine, errorColumn;
01127 
01128     QDomDocument domDocument;
01129 
01130     if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
01131         QMessageBox::information(window(), tr("XML Layout"),
01132                                  tr("Parse error at line %1:\n%2")
01133                                  .arg(errorLine)
01134                                  .arg(errorStr));
01135         return;
01136     }
01137 
01138     QDomElement root = domDocument.namedItem("root").toElement();
01139 
01140     if(load_data)
01141     {
01142         QDomElement previously_loaded_datafile =  root.firstChildElement( "previouslyLoadedDatafile" );
01143         if( previously_loaded_datafile.isNull() == false)
01144         {
01145             QString filename;
01146 
01147             //new format
01148             if( previously_loaded_datafile.hasAttribute("filename"))
01149             {
01150                 filename =  previously_loaded_datafile.attribute("filename");
01151             }
01152             else{  // old format
01153                 filename = previously_loaded_datafile.text();
01154             }
01155 
01156             QMessageBox::StandardButton reload_previous;
01157             reload_previous = QMessageBox::question(0, tr("Wait!"),
01158                                                     tr("Do you want to reload the previous datafile and its configuration?\n\n[%1]\n").arg(filename),
01159                                                     QMessageBox::Yes | QMessageBox::No,
01160                                                     QMessageBox::Yes );
01161 
01162             if( reload_previous == QMessageBox::Yes )
01163             {
01164                 _last_load_configuration = (previously_loaded_datafile.attribute("configuration", QString() ));
01165                 onActionLoadDataFileImpl( filename, true );
01166             }
01167         }
01168 
01169         QDomElement previously_loaded_streamer =  root.firstChildElement( "previouslyLoadedStreamer" );
01170         if( previously_loaded_streamer.isNull() == false)
01171         {
01172             QString streamer_name;
01173             if( previously_loaded_streamer.hasAttribute("name"))
01174             {
01175                 //new format
01176                 streamer_name = previously_loaded_streamer.attribute("name");
01177             }
01178             else{ //old format
01179                 streamer_name = previously_loaded_streamer.text();
01180             }
01181             _last_stream_configuration = (previously_loaded_streamer.attribute("configuration", QString() ));
01182 
01183             bool streamer_loaded = false;
01184             for(auto& it: _data_streamer) {
01185                 if( it.first == streamer_name) streamer_loaded = true;
01186             }
01187             if( streamer_loaded ){
01188                 onActionLoadStreamer( streamer_name );
01189             }
01190             else{
01191                 QMessageBox::warning(this, tr("Error Loading Streamer"),
01192                                      tr("The stramer named %1 can not be loaded.").arg(streamer_name));
01193             }
01194         }
01195     }
01196 
01198 
01199     xmlLoadState( domDocument );
01200 
01201     _undo_states.clear();
01202     _undo_states.push_back( domDocument );
01203 
01204 }
01205 
01206 
01207 void MainWindow::onUndoInvoked( )
01208 {
01209     // qDebug() << "on_UndoInvoked "<<_undo_states.size() << " -> " <<_undo_states.size()-1;
01210 
01211     _disable_undo_logging = true;
01212     if( _undo_states.size() > 1)
01213     {
01214         QDomDocument state_document = _undo_states.back();
01215         while( _redo_states.size() >= 100 ) _redo_states.pop_front();
01216         _redo_states.push_back( state_document );
01217         _undo_states.pop_back();
01218         state_document = _undo_states.back();
01219 
01220         xmlLoadState( state_document );
01221     }
01222     _disable_undo_logging = false;
01223 }
01224 
01225 
01226 void MainWindow::on_tabbedAreaDestroyed(QObject *object)
01227 {
01228     this->setFocus();
01229 }
01230 
01231 void MainWindow::onFloatingWindowDestroyed(QObject *object)
01232 {
01233 //    for (size_t i=0; i< SubWindow::instances().size(); i++)
01234 //    {
01235 //        if( SubWindow::instances()[i] == object)
01236 //        {
01237 //            SubWindow::instances().erase( SubWindow::instances().begin() + i);
01238 //            break;
01239 //        }
01240 //    }
01241 }
01242 
01243 void MainWindow::onCreateFloatingWindow(PlotMatrix* first_tab)
01244 {
01245     createTabbedDialog( QString(), first_tab );
01246 }
01247 
01248 void MainWindow::forEachWidget(std::function<void (PlotWidget*, PlotMatrix*, int,int )> operation)
01249 {
01250     auto func = [&](QTabWidget * tabs)
01251     {
01252         for (int t=0; t < tabs->count(); t++)
01253         {
01254             PlotMatrix* matrix =  static_cast<PlotMatrix*>(tabs->widget(t));
01255 
01256             for(unsigned row=0; row< matrix->rowsCount(); row++)
01257             {
01258                 for(unsigned col=0; col< matrix->colsCount(); col++)
01259                 {
01260                     PlotWidget* plot = matrix->plotAt(row, col);
01261                     operation(plot, matrix, row, col);
01262                 }
01263             }
01264         }
01265     };
01266 
01267     func( _main_tabbed_widget->tabWidget() );
01268     for(const auto& it: TabbedPlotWidget::instances())
01269     {
01270         func( it.second->tabWidget() );
01271     }
01272 }
01273 
01274 void MainWindow::forEachWidget(std::function<void (PlotWidget *)> op)
01275 {
01276     forEachWidget( [&](PlotWidget*plot, PlotMatrix*, int,int) { op(plot); } );
01277 }
01278 
01279 void MainWindow::updateTimeSlider()
01280 {
01281     //----------------------------------
01282     // find min max time
01283 
01284     double min_time =  std::numeric_limits<double>::max();
01285     double max_time = -std::numeric_limits<double>::max();
01286     size_t max_steps = 0;
01287 
01288     forEachWidget([&](PlotWidget* widget)
01289     {
01290       for (auto it: widget->curveList())
01291       {
01292         const auto& curve_name = it.first.toStdString();
01293 
01294         const PlotDataPtr data = _mapped_plot_data.numeric[curve_name];
01295         if(data->size() >=1)
01296         {
01297           const double t0 = data->at(0).x;
01298           const double t1 = data->at( data->size() -1).x;
01299           min_time  = std::min( min_time, t0);
01300           max_time  = std::max( max_time, t1);
01301           max_steps = std::max( max_steps, data->size());
01302         }
01303       }
01304     });
01305 
01306     // needed if all the plots are empty
01307     if( max_steps == 0 || max_time < min_time)
01308     {
01309       for (auto it: _mapped_plot_data.numeric)
01310       {
01311         const PlotDataPtr data = it.second;
01312         if(data->size() >=1)
01313         {
01314           const double t0 = data->at(0).x;
01315           const double t1 = data->at( data->size() -1).x;
01316           min_time  = std::min( min_time, t0);
01317           max_time  = std::max( max_time, t1);
01318           max_steps = std::max( max_steps, data->size());
01319         }
01320       }
01321     }
01322 
01323     // last opportunity. Everuthing else failed
01324     if( max_steps == 0 || max_time < min_time)
01325     {
01326       min_time = 0.0;
01327       max_time = 1.0;
01328       max_steps = 1;
01329     }
01330     //----------------------------------
01331     // Update Time offset
01332     //if( update_timeoffset)
01333     {
01334         bool remove_offset = ui->pushButtonRemoveTimeOffset->isChecked();
01335 
01336         if( remove_offset )
01337         {
01338             if( isStreamingActive() == false){
01339                 _time_offset.set( min_time );
01340             }
01341         }
01342         else{
01343             _time_offset.set( 0.0 );
01344         }
01345     }
01346 
01347     //----------------------------------
01348     ui->timeSlider->setLimits(min_time - _time_offset.get(),
01349                               max_time - _time_offset.get(),
01350                               max_steps);
01351 }
01352 
01353 void MainWindow::onSwapPlots(PlotWidget *source, PlotWidget *destination)
01354 {
01355     if( !source || !destination ) return;
01356 
01357     PlotMatrix* src_matrix = NULL;
01358     PlotMatrix* dst_matrix = NULL;
01359     QPoint src_pos;
01360     QPoint dst_pos;
01361 
01362     forEachWidget( [&](PlotWidget* plot, PlotMatrix* matrix, int row,int col)
01363     {
01364         if( plot == source ) {
01365             src_matrix = matrix;
01366             src_pos.setX( row );
01367             src_pos.setY( col );
01368         }
01369         else if( plot == destination )
01370         {
01371             dst_matrix = matrix;
01372             dst_pos.setX( row );
01373             dst_pos.setY( col );
01374         }
01375     });
01376 
01377     if(src_matrix && dst_matrix)
01378     {
01379         src_matrix->gridLayout()->removeWidget( source );
01380         dst_matrix->gridLayout()->removeWidget( destination );
01381 
01382         src_matrix->gridLayout()->addWidget( destination, src_pos.x(), src_pos.y() );
01383         dst_matrix->gridLayout()->addWidget( source,      dst_pos.x(), dst_pos.y() );
01384 
01385         src_matrix->updateLayout();
01386         if( src_matrix != dst_matrix){
01387             dst_matrix->updateLayout();
01388         }
01389     }
01390     onUndoableChange();
01391 }
01392 
01393 void MainWindow::on_pushButtonStreaming_toggled(bool streaming)
01394 {
01395     if( !_current_streamer )
01396     {
01397         streaming = false;
01398     }
01399     else{
01400         _current_streamer->enableStreaming( streaming ) ;
01401     }
01402 
01403     if( streaming )
01404     {
01405         ui->horizontalSpacer->changeSize(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed);
01406         ui->pushButtonStreaming->setText("Streaming ON");
01407     }
01408     else{
01409         _replot_timer->stop( );
01410         ui->horizontalSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
01411         ui->pushButtonStreaming->setText("Streaming OFF");
01412     }
01413     ui->streamingLabel->setHidden( !streaming );
01414     ui->streamingSpinBox->setHidden( !streaming );
01415     ui->timeSlider->setHidden( streaming );
01416 
01417     forEachWidget( [&](PlotWidget* plot)
01418     {
01419         plot->enableTracker( !streaming );
01420     } );
01421 
01422     emit activateStreamingMode( streaming );
01423 
01424     this->repaint();
01425 
01426     if( _current_streamer && streaming)
01427     {
01428         _replot_timer->setSingleShot(true);
01429         _replot_timer->start( 5 );
01430 
01431         double min_time = std::numeric_limits<double>::max();
01432         for (auto it: _mapped_plot_data.numeric )
01433         {
01434             PlotDataPtr data = it.second;
01435             data->flushAsyncBuffer();
01436             if(data->size() > 0)
01437             {
01438                 min_time  = std::min( min_time,  data->at(0).x);
01439             }
01440         }
01441 
01442         if( min_time == std::numeric_limits<double>::max())
01443         {
01444             using namespace std::chrono;
01445             auto epoch = high_resolution_clock::now().time_since_epoch();
01446             min_time = duration<double>(epoch).count();
01447         }
01448         _time_offset.set(min_time);
01449     }
01450     else{
01451         onUndoableChange();
01452     }
01453 }
01454 
01455 void MainWindow::updateDataAndReplot()
01456 {
01457 
01458     // STEP 1: sync the data (usefull for streaming
01459     bool data_updated = false;
01460     {
01461         PlotData::asyncPushMutex().lock();
01462         for(auto it : _mapped_plot_data.numeric)
01463         {
01464             PlotDataPtr data = ( it.second );
01465             data_updated |=  data->flushAsyncBuffer();
01466         }
01467         PlotData::asyncPushMutex().unlock();
01468     }
01469 
01470     if( data_updated )
01471     {
01472         forEachWidget( [](PlotWidget* plot)
01473         {
01474             plot->updateCurves(true);
01475         } );
01476         updateTimeSlider();
01477     }
01478     //--------------------------------
01479     // trigger again the execution of this callback if steaming == true
01480     if( isStreamingActive())
01481     {
01482         static auto prev_time = std::chrono::steady_clock::now();
01483         auto time_now =  std::chrono::steady_clock::now();
01484         if( (time_now - prev_time) > std::chrono::seconds(2) )
01485         {
01486             prev_time = time_now;
01487             importPlotDataMap( _current_streamer->getDataMap(), false );
01488         }
01489 
01490         _replot_timer->setSingleShot(true);
01491         _replot_timer->stop( );
01492         _replot_timer->start( 40 ); // 25 Hz at most
01493 
01494         _tracker_time = ui->timeSlider->getMaximum() + _time_offset.get();
01495         forEachWidget( [&](PlotWidget* plot)
01496         {
01497             plot->setTrackerPosition( _tracker_time );
01498         } );
01499 
01500         onTrackerTimeUpdated(_tracker_time);
01501     }
01502     //--------------------------------
01503     // zoom out and replot
01504     _main_tabbed_widget->currentTab()->maximumZoomOut() ;
01505 
01506     for(const auto& it: TabbedPlotWidget::instances())
01507     {
01508         PlotMatrix* matrix =  it.second->currentTab() ;
01509         matrix->maximumZoomOut(); // includes replot
01510     }
01511 }
01512 
01513 void MainWindow::on_streamingSpinBox_valueChanged(int value)
01514 {
01515     for (auto it : _mapped_plot_data.numeric )
01516     {
01517         PlotDataPtr plot = it.second;
01518         plot->setMaximumRangeX( value );
01519     }
01520 
01521     for (auto it: _mapped_plot_data.user_defined)
01522     {
01523         PlotDataAnyPtr plot = it.second;
01524         plot->setMaximumRangeX( value );
01525     }
01526 }
01527 
01528 void MainWindow::on_actionAbout_triggered()
01529 {
01530     AboutDialog* aboutdialog = new AboutDialog(this);
01531     aboutdialog->show();
01532 }
01533 
01534 void MainWindow::on_actionStopStreaming_triggered()
01535 {
01536     ui->pushButtonStreaming->setChecked(false);
01537     ui->pushButtonStreaming->setEnabled(false);
01538     _current_streamer->shutdown();
01539     _current_streamer = nullptr;
01540 
01541     for(auto& action: ui->menuStreaming->actions()) {
01542         action->setEnabled(true);
01543     }
01544     ui->actionStopStreaming->setEnabled(false);
01545 
01546     if( !_mapped_plot_data.numeric.empty()){
01547         ui->actionDeleteAllData->setEnabled( true );
01548         ui->actionDeleteAllData->setToolTip("");
01549     }
01550 }
01551 
01552 
01553 void MainWindow::on_actionExit_triggered()
01554 {
01555     QMessageBox::StandardButton reply;
01556     reply = QMessageBox::question(0, tr("Warning"),
01557                                   tr("Do you really want quit?\n"),
01558                                   QMessageBox::Yes | QMessageBox::No,
01559                                   QMessageBox::Yes );
01560     if( reply == QMessageBox::Yes ) {
01561         this->close();
01562     }
01563 }
01564 
01565 void MainWindow::on_actionQuick_Help_triggered()
01566 {
01567     const QString path =  tr(PJ_DOCUMENTATION_DIR) + "/index.html";
01568     QFileInfo check_file(path);
01569 
01570     QUrl url_SYS( tr("file:///")  + path, QUrl::TolerantMode);
01571 
01572     if( check_file.exists() && url_SYS.isValid() )
01573     {
01574         if(QDesktopServices::openUrl(url_SYS))
01575         {
01576             return;
01577         }
01578     }
01579     QMessageBox::warning(this, "Can't find Documentation",
01580                          QString("Can't open the file:\n\n%1\n\n Is it correctly installed in your system?").arg( url_SYS.path()) );
01581 }
01582 
01583 
01584 void MainWindow::on_pushButtonRemoveTimeOffset_toggled(bool )
01585 {
01586     updateTimeSlider();
01587     updatedDisplayTime();
01588     if (this->signalsBlocked() == false)  onUndoableChange();
01589 }
01590 
01591 
01592 void MainWindow::on_pushButtonOptions_toggled(bool checked)
01593 {
01594     ui->widgetOptions->setVisible( checked );
01595     ui->line->setVisible( checked );
01596 }
01597 
01598 void MainWindow::updatedDisplayTime()
01599 {
01600     const double relative_time = _tracker_time - _time_offset.get();
01601     if( ui->pushButtonUseDateTime->isChecked() )
01602     {
01603         if( _time_offset.get() > 0 )
01604         {
01605             QTime time = QTime::fromMSecsSinceStartOfDay( std::round(relative_time*1000.0));
01606             ui->displayTime->setText( time.toString("HH:mm::ss.zzz") );
01607         }
01608         else{
01609             QDateTime datetime = QDateTime::fromMSecsSinceEpoch( std::round(relative_time*1000.0) );
01610             ui->displayTime->setText( datetime.toString("d/M/yy HH:mm::ss.zzz") );
01611         }
01612     }
01613     else{
01614         ui->displayTime->setText( QString::number(relative_time, 'f', 3));
01615     }
01616 }
01617 
01618 void MainWindow::on_pushButtonActivateGrid_toggled(bool checked)
01619 {
01620     forEachWidget( [checked](PlotWidget* plot) {
01621         plot->activateGrid( checked );
01622         plot->replot();
01623     });
01624 }
01625 
01626 void MainWindow::on_actionClearBuffer_triggered()
01627 {
01628     {
01629         std::unique_lock<std::mutex> locker( PlotData::asyncPushMutex() );
01630         for (auto it: _mapped_plot_data.numeric )
01631         {
01632             it.second->clear();
01633         }
01634     }
01635 
01636     {
01637         std::unique_lock<std::mutex> locker( PlotDataAny::asyncPushMutex() );
01638         for (auto it: _mapped_plot_data.user_defined )
01639         {
01640             it.second->clear();
01641         }
01642     }
01643     forEachWidget( [](PlotWidget* plot) {
01644         plot->reloadPlotData();
01645         plot->replot();
01646     });
01647 }
01648 
01649 void MainWindow::on_pushButtonUseDateTime_toggled(bool checked)
01650 {
01651     updatedDisplayTime();
01652 }
01653 
01654 void MainWindow::on_pushButtonTimeTracker_pressed()
01655 {
01656     if( _tracker_param == CurveTracker::LINE_ONLY)
01657     {
01658         _tracker_param = CurveTracker::VALUE;
01659     }
01660     else if( _tracker_param == CurveTracker::VALUE)
01661     {
01662         _tracker_param = CurveTracker::VALUE_NAME;
01663     }
01664     else if( _tracker_param == CurveTracker::VALUE_NAME)
01665     {
01666         _tracker_param = CurveTracker::LINE_ONLY;
01667     }
01668     ui->pushButtonTimeTracker->setIcon( _tracker_button_icons[ _tracker_param ] );
01669 
01670     forEachWidget( [&](PlotWidget* plot) {
01671         plot->configureTracker(_tracker_param);
01672         plot->replot();
01673     });
01674 }
01675 
01676 void MainWindow::on_minimizeView()
01677 {
01678     _minimized = !_minimized;
01679 
01680     ui->leftFrame->setVisible(!_minimized);
01681     ui->widgetOptions->setVisible( !_minimized && ui->pushButtonOptions->isChecked() );
01682     ui->widgetTimescale->setVisible(!_minimized);
01683     ui->menuBar->setVisible(!_minimized);
01684 
01685     for (auto it: TabbedPlotWidget::instances() )
01686     {
01687        it.second->setControlsVisible( !_minimized );
01688     }
01689 }
01690 
01691 void MainWindow::closeEvent(QCloseEvent *event)
01692 {
01693     _replot_timer->stop();
01694     if( _current_streamer )
01695     {
01696         _current_streamer->shutdown();
01697         _current_streamer = nullptr;
01698     }
01699     QSettings settings( "IcarusTechnology", "PlotJuggler");
01700     settings.setValue("MainWindow.geometry", saveGeometry());
01701     settings.setValue("MainWindow.activateGrid", ui->pushButtonActivateGrid->isChecked() );
01702     settings.setValue("MainWindow.streamingBufferValue", ui->streamingSpinBox->value() );
01703     settings.setValue("MainWindow.dateTimeDisplay",ui->pushButtonUseDateTime->isChecked() );
01704     settings.setValue("MainWindow.timeTrackerSetting", (int)_tracker_param );
01705 
01706     // clean up all the plugins
01707     for(auto& it : _data_loader ) { delete it.second; }
01708     for(auto& it : _state_publisher ) { delete it.second; }
01709     for(auto& it : _data_streamer ) { delete it.second; }
01710 }
01711 


plotjuggler
Author(s): Davide Faconti
autogenerated on Fri Sep 1 2017 02:41:56