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());
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
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
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
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
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
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
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
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{
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;
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
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
01148 if( previously_loaded_datafile.hasAttribute("filename"))
01149 {
01150 filename = previously_loaded_datafile.attribute("filename");
01151 }
01152 else{
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
01176 streamer_name = previously_loaded_streamer.attribute("name");
01177 }
01178 else{
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
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
01234
01235
01236
01237
01238
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
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
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
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
01332
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
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
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 );
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
01504 _main_tabbed_widget->currentTab()->maximumZoomOut() ;
01505
01506 for(const auto& it: TabbedPlotWidget::instances())
01507 {
01508 PlotMatrix* matrix = it.second->currentTab() ;
01509 matrix->maximumZoomOut();
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
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