11 #include <QApplication> 12 #include <QActionGroup> 14 #include <QCommandLineParser> 16 #include <QDesktopServices> 17 #include <QDomDocument> 18 #include <QDoubleSpinBox> 19 #include <QElapsedTimer> 20 #include <QFileDialog> 21 #include <QInputDialog> 24 #include <QMessageBox> 26 #include <QMouseEvent> 27 #include <QPluginLoader> 28 #include <QPushButton> 29 #include <QKeySequence> 32 #include <QStringListModel> 35 #include <QTextStream> 37 #include <QHeaderView> 38 #include <QStandardPaths> 39 #include <QXmlStreamReader> 55 #include "ui_aboutdialog.h" 56 #include "ui_support_dialog.h" 62 #ifdef COMPILED_WITH_CATKIN 65 #ifdef COMPILED_WITH_AMENT 66 #include <ament_index_cpp/get_package_prefix.hpp> 67 #include <ament_index_cpp/get_package_share_directory.hpp> 73 , _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this)
74 , _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this)
75 , _fullscreen_shortcut(Qt::Key_F10, this)
76 , _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this)
77 , _playback_shotcut(Qt::Key_Space, this)
79 , _active_streamer_plugin(
nullptr)
80 , _disable_undo_logging(false)
84 , _recent_data_files(new QMenu())
85 , _recent_layout_files(new QMenu())
87 QLocale::setDefault(QLocale::c());
93 if (commandline_parser.isSet(
"enabled_plugins"))
96 commandline_parser.value(
"enabled_plugins").split(
";", QString::SkipEmptyParts);
104 if (commandline_parser.isSet(
"disabled_plugins"))
107 commandline_parser.value(
"disabled_plugins").split(
";", QString::SkipEmptyParts);
116 if (commandline_parser.isSet(
"skin_path"))
118 QDir path(commandline_parser.value(
"skin_path"));
124 if (commandline_parser.isSet(
"window_title"))
126 setWindowTitle(commandline_parser.value(
"window_title"));
130 QFile fileTitle(
_skin_path +
"/mainwindow_title.txt");
131 if (fileTitle.open(QIODevice::ReadOnly))
133 QString title = fileTitle.readAll().trimmed();
134 setWindowTitle(title);
140 ui->playbackLoop->setText(
"");
141 ui->pushButtonZoomOut->setText(
"");
142 ui->pushButtonPlay->setText(
"");
143 ui->pushButtonUseDateTime->setText(
"");
144 ui->pushButtonActivateGrid->setText(
"");
145 ui->pushButtonRatio->setText(
"");
146 ui->pushButtonLink->setText(
"");
147 ui->pushButtonTimeTracker->setText(
"");
148 ui->pushButtonLoadDatafile->setText(
"");
149 ui->pushButtonRemoveTimeOffset->setText(
"");
150 ui->pushButtonLegend->setText(
"");
152 if (commandline_parser.isSet(
"buffer_size"))
154 int buffer_size = std::max(10, commandline_parser.value(
"buffer_size").toInt());
155 ui->streamingSpinBox->setMaximum(buffer_size);
176 ui->labelStreamingAnimation->setHidden(
true);
201 connect(
ui->playbackRate, &QDoubleSpinBox::editingFinished,
this,
202 [
this]() { ui->playbackRate->clearFocus(); });
204 connect(
ui->playbackStep, &QDoubleSpinBox::editingFinished,
this,
205 [
this]() { ui->playbackStep->clearFocus(); });
212 else if (option == 2)
224 ui->plottingLayout->insertWidget(0, _main_tabbed_widget, 1);
227 ui->mainSplitter->setCollapsible(0,
true);
228 ui->mainSplitter->setStretchFactor(0, 2);
229 ui->mainSplitter->setStretchFactor(1, 6);
231 ui->layoutTimescale->removeWidget(
ui->widgetButtons);
232 _main_tabbed_widget->tabWidget()->setCornerWidget(
ui->widgetButtons);
234 connect(
ui->mainSplitter, SIGNAL(splitterMoved(
int,
int)),
242 auto plugin_extra_folders =
243 commandline_parser.value(
"plugin_folders").split(
";", QString::SkipEmptyParts);
261 _publish_timer->setInterval(20);
264 ui->menuFile->setToolTipsVisible(
true);
266 this->setMenuBar(
ui->menuBar);
267 ui->menuBar->setNativeMenuBar(
false);
274 bool file_loaded =
false;
275 if (commandline_parser.isSet(
"datafile"))
277 QStringList datafiles = commandline_parser.values(
"datafile");
280 if (commandline_parser.isSet(
"layout"))
285 restoreGeometry(settings.value(
"MainWindow.geometry").toByteArray());
286 restoreState(settings.value(
"MainWindow.state").toByteArray());
290 bool activate_grid = settings.value(
"MainWindow.activateGrid",
false).toBool();
291 ui->pushButtonActivateGrid->setChecked(activate_grid);
293 bool zoom_link_active = settings.value(
"MainWindow.buttonLink",
true).toBool();
294 ui->pushButtonLink->setChecked(zoom_link_active);
296 bool ration_active = settings.value(
"MainWindow.buttonRatio",
true).toBool();
297 ui->pushButtonRatio->setChecked(ration_active);
299 int streaming_buffer_value =
300 settings.value(
"MainWindow.streamingBufferValue", 5).toInt();
301 ui->streamingSpinBox->setValue(streaming_buffer_value);
303 bool datetime_display = settings.value(
"MainWindow.dateTimeDisplay",
false).toBool();
304 ui->pushButtonUseDateTime->setChecked(datetime_display);
306 bool remove_time_offset = settings.value(
"MainWindow.removeTimeOffset",
true).toBool();
307 ui->pushButtonRemoveTimeOffset->setChecked(remove_time_offset);
311 if (settings.value(
"MainWindow.hiddenFileFrame",
false).toBool())
313 ui->buttonHideFileFrame->setText(
"+");
314 ui->frameFile->setHidden(
true);
316 if (settings.value(
"MainWindow.hiddenStreamingFrame",
false).toBool())
318 ui->buttonHideStreamingFrame->setText(
"+");
319 ui->frameStreaming->setHidden(
true);
321 if (settings.value(
"MainWindow.hiddenPublishersFrame",
false).toBool())
323 ui->buttonHidePublishersFrame->setText(
"+");
324 ui->framePublishers->setHidden(
true);
328 QIcon trackerIconA, trackerIconB, trackerIconC;
330 trackerIconA.addFile(QStringLiteral(
":/style_light/line_tracker.png"), QSize(36, 36));
331 trackerIconB.addFile(QStringLiteral(
":/style_light/line_tracker_1.png"), QSize(36, 36));
332 trackerIconC.addFile(QStringLiteral(
":/style_light/line_tracker_a.png"), QSize(36, 36));
338 int tracker_setting =
346 auto editor_layout =
new QVBoxLayout();
347 editor_layout->setMargin(0);
348 ui->formulaPage->setLayout(editor_layout);
354 [
this]() {
ui->widgetStack->setCurrentIndex(0); });
362 QString theme = settings.value(
"Preferences::theme",
"light").toString();
370 auto json_parser = std::make_shared<JSON_ParserFactory>();
373 auto cbor_parser = std::make_shared<CBOR_ParserFactory>();
376 auto bson_parser = std::make_shared<BSON_ParserFactory>();
379 auto msgpack = std::make_shared<MessagePack_ParserFactory>();
387 ui->comboStreaming->setCurrentIndex(
index);
409 if (elapsed_ms < 100)
466 auto prev =
ui->timeSlider->blockSignals(
true);
468 ui->timeSlider->blockSignals(prev);
485 it.second->updateState(absolute_time);
510 &QPushButton::toggle);
514 QShortcut* open_menu_shortcut =
new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F),
this);
515 connect(open_menu_shortcut, &QShortcut::activated,
516 [
this]() {
ui->menuFile->exec(
ui->menuBar->mapToGlobal(QPoint(0, 25))); });
518 QShortcut* open_help_shortcut =
new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H),
this);
519 connect(open_help_shortcut, &QShortcut::activated,
520 [
this]() {
ui->menuHelp->exec(
ui->menuBar->mapToGlobal(QPoint(230, 25))); });
526 settings.value(
"MainWindow.recentlyLoadedDatafile").toStringList());
528 settings.value(
"MainWindow.recentlyLoadedLayout").toStringList());
535 QStringList plugin_folders;
536 QStringList builtin_folders;
538 plugin_folders += command_line_plugin_folders;
540 settings.value(
"Preferences::plugin_folders", QStringList()).toStringList();
542 builtin_folders += QCoreApplication::applicationDirPath();
546 #ifdef COMPILED_WITH_CATKIN 547 builtin_folders += QCoreApplication::applicationDirPath() +
"_ros";
549 const char* env = std::getenv(
"CMAKE_PREFIX_PATH");
552 QString env_catkin_paths = QString::fromStdString(env);
553 env_catkin_paths.replace(
";",
":");
554 auto catkin_paths = env_catkin_paths.split(
":");
556 for (
const auto& path : catkin_paths)
558 builtin_folders += path +
"/lib/plotjuggler_ros";
562 #ifdef COMPILED_WITH_AMENT 563 auto ros2_path = QString::fromStdString(ament_index_cpp::get_package_prefix(
"plotjugg" 566 ros2_path +=
"/lib/plotjuggler_ros";
572 QMessageBox::warning(
nullptr,
"Missing package [plotjuggler-ros]",
573 "If you just upgraded from PlotJuggler 2.x to 3.x , try " 574 "installing this package:\n\n" 575 "sudo apt install ros-${ROS_DISTRO}-plotjuggler-ros",
576 QMessageBox::Cancel, QMessageBox::Cancel);
580 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
"/PlotJuggl" 582 builtin_folders.removeDuplicates();
584 plugin_folders += builtin_folders;
585 plugin_folders.removeDuplicates();
587 for (
const auto& folder : plugin_folders)
592 settings.setValue(
"Preferences::builtin_plugin_folders", builtin_folders);
597 static std::set<QString> loaded_plugins;
598 QStringList loaded_out;
600 qDebug() <<
"Loading compatible plugins from directory: " << directory_name;
601 int loaded_count = 0;
603 QDir pluginsDir(directory_name);
605 for (
const QString& filename : pluginsDir.entryList(QDir::Files))
607 QFileInfo fileinfo(filename);
608 if (fileinfo.suffix() !=
"so" && fileinfo.suffix() !=
"dll" &&
609 fileinfo.suffix() !=
"dylib")
614 if (loaded_plugins.find(filename) != loaded_plugins.end())
619 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename),
this);
624 plugin = pluginLoader.instance();
626 catch (std::runtime_error& err)
628 qDebug() << QString(
"%1: skipping, because it threw the following exception: %2").arg(filename).arg(err.what());
631 if (plugin && dynamic_cast<PlotJugglerPlugin*>(plugin))
633 auto class_name = pluginLoader.metaData().value(
"className").toString();
634 loaded_out.push_back(class_name);
644 bool is_debug_plugin =
dynamic_cast<PlotJugglerPlugin*
>(plugin)->isDebugPlugin();
648 plugin_name = loader->
name();
649 plugin_type =
"DataLoader";
653 plugin_name = publisher->name();
654 plugin_type =
"StatePublisher";
658 plugin_name = streamer->name();
659 plugin_type =
"DataStreamer";
661 else if (message_parser)
663 plugin_name = message_parser->name();
664 plugin_type =
"MessageParser";
668 plugin_name = toolbox->name();
669 plugin_type =
"Toolbox";
672 QString message = QString(
"%1 is a %2 plugin").arg(filename).arg(plugin_type);
677 qDebug() << message <<
" ...skipping, because it is not explicitly enabled";
683 qDebug() << message <<
" ...skipping, because it is explicitly disabled";
688 qDebug() << message <<
" ...disabled, unless option -t is used";
691 if (loaded_plugins.find(plugin_name) != loaded_plugins.end())
693 qDebug() << message <<
" ...skipping, because already loaded";
699 loaded_plugins.insert(plugin_name);
704 _data_loader.insert(std::make_pair(plugin_name, loader));
711 ui->layoutPublishers->setColumnStretch(0, 1.0);
714 auto label =
new QLabel(plugin_name,
ui->framePublishers);
715 ui->layoutPublishers->addWidget(label, row, 0);
717 auto start_checkbox =
new QCheckBox(
ui->framePublishers);
718 ui->layoutPublishers->addWidget(start_checkbox, row, 1);
719 start_checkbox->setFocusPolicy(Qt::FocusPolicy::NoFocus);
721 connect(start_checkbox, &QCheckBox::toggled,
this,
722 [=](
bool enable) { publisher->setEnabled(enable); });
724 connect(publisher, &StatePublisher::closed, start_checkbox,
725 [=]() { start_checkbox->setChecked(
false); });
727 if (publisher->availableActions().empty())
729 QFrame* empty =
new QFrame(
ui->framePublishers);
730 empty->setFixedSize({ 22, 22 });
731 ui->layoutPublishers->addWidget(empty, row, 2);
735 auto options_button =
new QPushButton(
ui->framePublishers);
736 options_button->setFlat(
true);
737 options_button->setFixedSize({ 24, 24 });
738 ui->layoutPublishers->addWidget(options_button, row, 2);
740 options_button->setIcon(
LoadSvg(
":/resources/svg/settings_cog.svg",
"light"));
741 options_button->setIconSize({ 16, 16 });
743 auto optionsMenu = [=]() {
745 for (
auto action : publisher->availableActions())
747 menu->addAction(action);
752 connect(options_button, &QPushButton::clicked, options_button, optionsMenu);
756 options_button->setIcon(
757 LoadSvg(
":/resources/svg/settings_cog.svg", style));
761 else if (message_parser)
764 std::make_pair(message_parser->encoding(), message_parser));
774 connect(streamer, &DataStreamer::closed,
this,
777 connect(streamer, &DataStreamer::clearBuffers,
this,
786 connect(streamer, &DataStreamer::removeGroup,
this,
789 connect(streamer, &DataStreamer::dataReceived,
this, [
this]() {
797 connect(streamer, &DataStreamer::notificationsChanged,
this,
804 _toolboxes.insert(std::make_pair(plugin_name, toolbox));
806 auto action =
ui->menuTools->addAction(toolbox->name());
808 int new_index =
ui->widgetStack->count();
809 auto provided = toolbox->providedWidget();
810 auto widget = provided.first;
811 ui->widgetStack->addWidget(widget);
813 connect(action, &QAction::triggered, toolbox, &ToolboxPlugin::onShowWidget);
815 connect(action, &QAction::triggered,
this,
816 [=]() {
ui->widgetStack->setCurrentIndex(new_index); });
818 connect(toolbox, &ToolboxPlugin::closed,
this,
819 [=]() {
ui->widgetStack->setCurrentIndex(0); });
821 connect(toolbox, &ToolboxPlugin::plotCreated,
this, [=](std::string
name) {
830 if (pluginLoader.errorString().contains(
"is not an ELF object") ==
false)
832 qDebug() << filename <<
": " << pluginLoader.errorString();
847 if (!_data_streamer.empty())
849 QSignalBlocker
block(
ui->comboStreaming);
850 ui->comboStreaming->setEnabled(
true);
851 ui->buttonStreamingStart->setEnabled(
true);
853 for (
const auto& it : _data_streamer)
855 if (
ui->comboStreaming->findText(it.first) == -1)
857 ui->comboStreaming->addItem(it.first);
863 QString streaming_name =
865 .value(
"MainWindow.previousStreamingPlugin",
ui->comboStreaming->itemText(0))
868 auto streamer_it = _data_streamer.find(streaming_name);
869 if (streamer_it == _data_streamer.end())
871 streamer_it = _data_streamer.begin();
872 streaming_name = streamer_it->first;
875 ui->comboStreaming->setCurrentText(streaming_name);
877 bool contains_options = !streamer_it->second->availableActions().empty();
878 ui->buttonStreamingOptions->setEnabled(contains_options);
880 qDebug() <<
"Number of plugins loaded: " << loaded_count <<
"\n";
895 int totalWidth = sizes[0] + sizes[1];
898 static bool first =
true;
899 if (sizes[0] != 0 && first)
903 int splitter_width = settings.value(
"MainWindow.splitterWidth", 200).toInt();
904 auto sizes =
ui->mainSplitter->sizes();
905 int tot_splitter_width = sizes[0] + sizes[1];
906 sizes[0] = splitter_width;
907 sizes[1] = tot_splitter_width - splitter_width;
908 ui->mainSplitter->setSizes(sizes);
912 if (sizes[0] > max_left_size)
914 sizes[0] = max_left_size;
915 sizes[1] = totalWidth - max_left_size;
916 ui->mainSplitter->setSizes(sizes);
938 connect(&
_time_offset, SIGNAL(valueChanged(
double)), plot,
939 SLOT(on_changeTimeOffset(
double)));
941 connect(
ui->pushButtonUseDateTime, &QPushButton::toggled, plot,
950 p->setLegendSize(point_size);
967 if (
ui->pushButtonLink->isChecked())
970 if (plot != modified_plot && !plot->
isEmpty() && !plot->isXYPlot() &&
971 plot->isZoomLinkEnabled())
973 QRectF bound_act = plot->currentBoundingRect();
974 bound_act.setLeft(new_range.left());
975 bound_act.setRight(new_range.right());
976 plot->setZoomRectangle(bound_act,
false);
977 plot->on_zoomOutVertical_triggered(
false);
1001 QDomProcessingInstruction instr = doc.createProcessingInstruction(
"xml",
"version='1.0'" 1005 doc.appendChild(instr);
1007 QDomElement root = doc.createElement(
"root");
1011 QDomElement tabbed_area = it.second->xmlSaveState(doc);
1012 root.appendChild(tabbed_area);
1015 doc.appendChild(root);
1017 QDomElement relative_time = doc.createElement(
"use_relative_time_offset");
1018 relative_time.setAttribute(
"enabled",
ui->pushButtonRemoveTimeOffset->isChecked());
1019 root.appendChild(relative_time);
1026 std::set<std::string> curves;
1028 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); !tw.isNull();
1029 tw = tw.nextSiblingElement(
"tabbed_widget"))
1031 for (QDomElement pm = tw.firstChildElement(
"plotmatrix"); !pm.isNull();
1032 pm = pm.nextSiblingElement(
"plotmatrix"))
1034 for (QDomElement pl = pm.firstChildElement(
"plot"); !pl.isNull();
1035 pl = pl.nextSiblingElement(
"plot"))
1037 QDomElement tran_elem = pl.firstChildElement(
"transform");
1038 std::string trans = tran_elem.attribute(
"value").toStdString();
1039 bool is_XY_plot = (trans ==
"XYPlot");
1041 for (QDomElement cv = pl.firstChildElement(
"curve"); !cv.isNull();
1042 cv = cv.nextSiblingElement(
"curve"))
1046 curves.insert(cv.attribute(
"curve_x").toStdString());
1047 curves.insert(cv.attribute(
"curve_y").toStdString());
1051 curves.insert(cv.attribute(
"name").toStdString());
1058 std::vector<std::string> missing_curves;
1060 for (
auto& curve_name : curves)
1064 missing_curves.push_back(curve_name);
1068 missing_curves.push_back(curve_name);
1071 if (missing_curves.size() > 0)
1073 QMessageBox msgBox(
this);
1074 msgBox.setWindowTitle(
"Warning");
1075 msgBox.setText(tr(
"One or more timeseries in the layout haven't been loaded yet\n" 1076 "What do you want to do?"));
1078 QPushButton* buttonRemove =
1079 msgBox.addButton(tr(
"Remove curves from plots"), QMessageBox::RejectRole);
1080 QPushButton* buttonPlaceholder =
1081 msgBox.addButton(tr(
"Create empty placeholders"), QMessageBox::YesRole);
1082 msgBox.setDefaultButton(buttonPlaceholder);
1084 if (msgBox.clickedButton() == buttonPlaceholder)
1086 for (
auto&
name : missing_curves)
1098 QDomElement root = state_document.namedItem(
"root").toElement();
1101 qWarning() <<
"No <root> element found at the top-level of the XML file!";
1105 size_t num_floating = 0;
1106 std::map<QString, QDomElement> tabbed_widgets_with_name;
1108 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); tw.isNull() ==
false;
1109 tw = tw.nextSiblingElement(
"tabbed_widget"))
1111 if (tw.attribute(
"parent") != (
"main_window"))
1115 tabbed_widgets_with_name[tw.attribute(
"name")] = tw;
1119 for (
const auto& it : tabbed_widgets_with_name)
1130 if (tabbed_widgets_with_name.count(it.first) == 0)
1132 it.second->deleteLater();
1140 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); tw.isNull() ==
false;
1141 tw = tw.nextSiblingElement(
"tabbed_widget"))
1147 QDomElement relative_time = root.firstChildElement(
"use_relative_time_offset");
1148 if (!relative_time.isNull())
1150 bool remove_offset = (relative_time.attribute(
"enabled") == QString(
"1"));
1151 ui->pushButtonRemoveTimeOffset->setChecked(remove_offset);
1158 std::set<std::string> to_be_deleted;
1159 for (
auto&
name : curve_names)
1161 to_be_deleted.insert(
name);
1164 size_t prev_size = 0;
1165 while (prev_size < to_be_deleted.size())
1167 prev_size = to_be_deleted.size();
1170 for (
const auto&
source : transform->dataSources())
1172 if (to_be_deleted.count(
source->plotName()) > 0)
1174 to_be_deleted.insert(trans_name);
1180 for (
const auto& curve_name : to_be_deleted)
1195 QAction* separator =
nullptr;
1196 QStringList prev_filenames;
1197 for (QAction* action : menu->actions())
1199 if (action->isSeparator())
1204 if (new_filenames.contains(action->text()) ==
false)
1206 prev_filenames.push_back(action->text());
1208 menu->removeAction(action);
1211 new_filenames.append(prev_filenames);
1212 while (new_filenames.size() > 10)
1214 new_filenames.removeLast();
1217 for (
const auto& filename : new_filenames)
1219 QAction* action =
new QAction(filename,
nullptr);
1220 connect(action, &QAction::triggered,
this,
1222 menu->insertAction(separator, action);
1226 settings.setValue(
"MainWindow.recentlyLoadedDatafile", new_filenames);
1227 menu->setEnabled(new_filenames.size() > 0);
1234 QAction* separator =
nullptr;
1235 QStringList prev_filenames;
1236 for (QAction* action : menu->actions())
1238 if (action->isSeparator())
1243 if (new_filenames.contains(action->text()) ==
false)
1245 prev_filenames.push_back(action->text());
1247 menu->removeAction(action);
1250 new_filenames.append(prev_filenames);
1251 while (new_filenames.size() > 10)
1253 new_filenames.removeLast();
1256 for (
const auto& filename : new_filenames)
1258 QAction* action =
new QAction(filename,
nullptr);
1259 connect(action, &QAction::triggered,
this, [
this, filename] {
1265 menu->insertAction(separator, action);
1269 settings.setValue(
"MainWindow.recentlyLoadedLayout", new_filenames);
1270 menu->setEnabled(new_filenames.size() > 0);
1284 bool stopped =
false;
1286 for (
int idx = 0; idx <
ui->layoutPublishers->count(); idx++)
1288 QLayoutItem* item =
ui->layoutPublishers->itemAt(idx);
1289 if (dynamic_cast<QWidgetItem*>(item))
1291 if (
auto checkbox = dynamic_cast<QCheckBox*>(item->widget()))
1293 if (checkbox->isChecked())
1295 checkbox->setChecked(
false);
1304 QMessageBox::warning(
this,
"State publishers stopped",
1305 "All the state publishers have been stopped because old data " 1306 "has been deleted.");
1314 auto ClearOldSeries = [](
auto& prev_plot_data,
auto& new_plot_data) {
1315 for (
auto& it : prev_plot_data)
1318 if (new_plot_data.count(it.first) != 0)
1330 auto [added_curves, curve_updated, data_pushed] =
1333 for (
const auto& added_curve : added_curves)
1352 std::map<QString, QString> filename_prefix;
1354 if (filenames.size() > 1)
1357 int ret = dialog.exec();
1358 if (ret != QDialog::Accepted)
1367 QStringList loaded_filenames;
1369 for (
int i = 0; i < filenames.size(); i++)
1373 if (filename_prefix.count(info.
filename) > 0)
1378 if (!added_names.empty())
1380 loaded_filenames.push_back(filenames[i]);
1382 for (
const auto&
name : added_names)
1384 previous_names.erase(
name);
1388 bool data_replaced_entirely =
false;
1390 if (previous_names.empty())
1392 data_replaced_entirely =
true;
1396 QMessageBox::StandardButton reply;
1397 reply = QMessageBox::question(
1398 this, tr(
"Warning"), tr(
"Do you want to remove the previously loaded data?\n"),
1399 QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton);
1401 if (reply == QMessageBox::Yes)
1403 std::vector<std::string> to_delete;
1404 for (
const auto&
name : previous_names)
1406 to_delete.push_back(
name);
1409 data_replaced_entirely =
true;
1414 if (loaded_filenames.size() == 1 && data_replaced_entirely &&
1421 if (loaded_filenames.size() > 0)
1432 ui->pushButtonPlay->setChecked(
false);
1434 const QString extension = QFileInfo(info.
filename).suffix().toLower();
1436 typedef std::map<QString, DataLoaderPtr>::iterator MapIterator;
1438 std::vector<MapIterator> compatible_loaders;
1443 std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
1445 for (
auto& ext : extensions)
1447 if (extension == QString(ext).toLower())
1449 compatible_loaders.push_back(it);
1456 std::unordered_set<std::string> added_names;
1458 if (compatible_loaders.size() == 1)
1460 dataloader = compatible_loaders.front()->second;
1464 static QString last_plugin_name_used;
1467 for (
auto& cl : compatible_loaders)
1469 const auto&
name = cl->first;
1471 if (
name == last_plugin_name_used)
1473 names.push_front(
name);
1477 names.push_back(
name);
1482 QString plugin_name =
1483 QInputDialog::getItem(
this, tr(
"QInputDialog::getItem()"),
1484 tr(
"Select the loader to use:"), names, 0,
false, &ok);
1488 last_plugin_name_used = plugin_name;
1496 if (!file.open(QFile::ReadOnly | QFile::Text))
1498 QMessageBox::warning(
1499 this, tr(
"Datafile"),
1500 tr(
"Cannot read file %1:\n%2.").
arg(info.
filename).arg(file.errorString()));
1510 if (dataloader->readDataFromFile(&new_info, mapped_data))
1518 QDomElement plugin_elem = dataloader->xmlSaveState(new_info.
plugin_config);
1521 bool duplicate =
false;
1526 if (prev_loaded.filename == new_info.
filename &&
1527 prev_loaded.prefix == new_info.
prefix)
1529 prev_loaded = new_info;
1537 _loaded_datafiles.push_back(new_info);
1541 catch (std::exception& ex)
1543 QMessageBox::warning(
this, tr(
"Exception from the plugin"),
1544 tr(
"The plugin [%1] thrown the following exception: \n\n %3\n")
1545 .
arg(dataloader->name())
1552 QMessageBox::warning(
this, tr(
"Error"),
1553 tr(
"Cannot read files with extension %1.\n No plugin can handle " 1567 custom_it.second->reset();
1572 ui->timeSlider->setRealValue(
ui->timeSlider->getMinimum());
1584 QAction* notification_button_action = streamer->notificationAction().first;
1585 if (notification_button_action !=
nullptr)
1587 notification_button_action->trigger();
1598 ui->pushButtonRemoveTimeOffset->setEnabled(paused);
1599 ui->widgetPlay->setEnabled(paused);
1601 if (!paused &&
ui->pushButtonPlay->isChecked())
1603 ui->pushButtonPlay->setChecked(
false);
1625 bool prev_state =
ui->buttonStreamingPause->isChecked();
1626 ui->buttonStreamingPause->setChecked(!prev_state);
1632 ui->comboStreaming->setEnabled(
true);
1633 ui->buttonStreamingStart->setText(
"Start");
1634 ui->buttonStreamingPause->setEnabled(
false);
1635 ui->labelStreamingAnimation->setHidden(
true);
1639 if (
ui->buttonStreamingPause->isChecked())
1642 ui->buttonStreamingPause->setChecked(
false);
1658 ui->actionDeleteAllData->setToolTip(
"");
1675 qDebug() <<
"Error, no streamer loaded";
1686 qDebug() <<
"Error. The streamer " << streamer_name <<
" can't be loaded";
1691 bool started =
false;
1697 catch (std::runtime_error& err)
1699 QMessageBox::warning(
1700 this, tr(
"Exception from the plugin"),
1701 tr(
"The plugin thrown the following exception: \n\n %1\n").
arg(err.what()));
1714 ui->actionClearBuffer->setEnabled(
true);
1715 ui->actionDeleteAllData->setToolTip(
"Stop streaming to be able to delete the data");
1717 ui->buttonStreamingStart->setText(
"Stop");
1718 ui->buttonStreamingPause->setEnabled(
true);
1719 ui->buttonStreamingPause->setChecked(
false);
1720 ui->comboStreaming->setEnabled(
false);
1721 ui->labelStreamingAnimation->setHidden(
false);
1730 QSignalBlocker
block(
ui->buttonStreamingStart);
1731 ui->buttonStreamingStart->setChecked(
false);
1732 qDebug() <<
"Failed to launch the streamer";
1739 ui->buttonStreamingNotifications->setEnabled(enabled);
1742 QString theme = settings.value(
"Preferences::theme",
"light").toString();
1746 ui->buttonStreamingNotifications->setIcon(
1747 LoadSvg(
":/resources/svg/alarm-bell-active.svg", theme));
1751 ui->buttonStreamingNotifications->setIcon(
1752 LoadSvg(
":/resources/svg/alarm-bell.svg", theme));
1758 QFile styleFile(file_path);
1759 styleFile.open(QFile::ReadOnly);
1769 catch (std::runtime_error& err)
1771 QMessageBox::warning(
this, tr(
"Error loading StyleSheet"), tr(err.what()));
1784 std::unordered_set<std::string> updated_curves;
1786 bool curve_added =
false;
1789 if (
auto reactive_function =
1790 std::dynamic_pointer_cast<PJ::ReactiveLuaFunction>(it.second))
1793 reactive_function->calculate();
1795 for (
auto&
name : reactive_function->createdCurves())
1798 updated_curves.insert(
name);
1810 if (updated_curves.count(curve.src_name) != 0)
1820 if (event->mimeData()->hasUrls())
1822 event->acceptProposedAction();
1828 QStringList file_names;
1829 const auto urls =
event->mimeData()->urls();
1831 for (
const auto& url : urls)
1833 file_names << QDir::toNativeSeparators(url.toLocalFile());
1841 ui->pushButtonLoadDatafile->setIcon(
LoadSvg(
":/resources/svg/import.svg", theme));
1842 ui->buttonStreamingPause->setIcon(
LoadSvg(
":/resources/svg/pause.svg", theme));
1843 if (
ui->buttonStreamingNotifications->isEnabled())
1845 ui->buttonStreamingNotifications->setIcon(
1846 LoadSvg(
":/resources/svg/alarm-bell-active.svg", theme));
1850 ui->buttonStreamingNotifications->setIcon(
1851 LoadSvg(
":/resources/svg/alarm-bell.svg", theme));
1853 ui->buttonRecentData->setIcon(
LoadSvg(
":/resources/svg/right-arrow.svg", theme));
1854 ui->buttonRecentLayout->setIcon(
LoadSvg(
":/resources/svg/right-arrow.svg", theme));
1856 ui->pushButtonZoomOut->setIcon(
LoadSvg(
":/resources/svg/zoom_max.svg", theme));
1857 ui->playbackLoop->setIcon(
LoadSvg(
":/resources/svg/loop.svg", theme));
1858 ui->pushButtonPlay->setIcon(
LoadSvg(
":/resources/svg/play_arrow.svg", theme));
1859 ui->pushButtonUseDateTime->setIcon(
LoadSvg(
":/resources/svg/datetime.svg", theme));
1860 ui->pushButtonActivateGrid->setIcon(
LoadSvg(
":/resources/svg/grid.svg", theme));
1861 ui->pushButtonRatio->setIcon(
LoadSvg(
":/resources/svg/ratio.svg", theme));
1863 ui->pushButtonLoadLayout->setIcon(
LoadSvg(
":/resources/svg/import.svg", theme));
1864 ui->pushButtonSaveLayout->setIcon(
LoadSvg(
":/resources/svg/export.svg", theme));
1866 ui->pushButtonLink->setIcon(
LoadSvg(
":/resources/svg/link.svg", theme));
1867 ui->pushButtonRemoveTimeOffset->setIcon(
LoadSvg(
":/resources/svg/t0.svg", theme));
1868 ui->pushButtonLegend->setIcon(
LoadSvg(
":/resources/svg/legend.svg", theme));
1870 ui->buttonStreamingOptions->setIcon(
LoadSvg(
":/resources/svg/settings_cog.svg", theme));
1875 QDomElement plugins = root.firstChildElement(
"Plugins");
1877 for (QDomElement plugin_elem = plugins.firstChildElement();
1878 plugin_elem.isNull() ==
false; plugin_elem = plugin_elem.nextSiblingElement())
1880 const QString plugin_name = plugin_elem.attribute(
"ID");
1882 if (plugin_elem.nodeName() !=
"plugin" || plugin_name.isEmpty())
1884 QMessageBox::warning(
this, tr(
"Error loading Plugin State from Layout"),
1885 tr(
"The method xmlSaveState() must return a node like this " 1886 "<plugin ID=\"PluginName\" "));
1899 _toolboxes[plugin_name]->xmlLoadState(plugin_elem);
1904 publisher->xmlLoadState(plugin_elem);
1908 publisher->setEnabled(
true);
1916 QDomElement list_plugins = doc.createElement(
"Plugins");
1918 auto AddPlugins = [&](
auto& plugins) {
1919 for (
auto& [
name, plugin] : plugins)
1921 QDomElement elem = plugin->xmlSaveState(doc);
1922 list_plugins.appendChild(elem);
1933 const auto& state_publisher = it.second;
1934 QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
1935 plugin_elem.setAttribute(
"status", state_publisher->enabled() ?
"active" :
"idle");
1938 return list_plugins;
1944 double min_time = std::numeric_limits<double>::max();
1945 double max_time = std::numeric_limits<double>::lowest();
1951 const auto& curve_name = it.src_name;
1958 const auto&
data = plot_it->second;
1959 if (
data.size() >= 1)
1961 const double t0 =
data.front().x;
1962 const double t1 =
data.back().x;
1963 min_time = std::min(min_time, t0);
1964 max_time = std::max(max_time, t1);
1965 max_steps = std::max(max_steps, (
int)
data.size());
1971 if (max_steps == 0 || max_time < min_time)
1976 if (data.
size() >= 1)
1978 const double t0 = data.
front().x;
1979 const double t1 = data.
back().x;
1980 min_time = std::min(min_time, t0);
1981 max_time = std::max(max_time, t1);
1982 max_steps = std::max(max_steps, (
int)data.
size());
1988 if (max_steps == 0 || max_time < min_time)
1994 return std::tuple<double, double, int>(min_time, max_time, max_steps);
2001 QFile file(filename);
2002 if (!file.open(QFile::ReadOnly | QFile::Text))
2004 QMessageBox::warning(
2006 tr(
"Cannot read file %1:\n%2.").
arg(filename).
arg(file.errorString()));
2011 int errorLine, errorColumn;
2013 QDomDocument domDocument;
2015 if (!domDocument.setContent(&file,
true, &errorStr, &errorLine, &errorColumn))
2017 QMessageBox::information(
2018 window(), tr(
"XML Layout"),
2019 tr(
"Parse error at line %1:\n%2").
arg(errorLine).
arg(errorStr));
2025 QDomElement root = domDocument.namedItem(
"root").toElement();
2029 QDomElement previously_loaded_datafile = root.firstChildElement(
"previouslyLoaded_" 2032 QDomElement datafile_elem = previously_loaded_datafile.firstChildElement(
"fileInfo");
2033 while (!datafile_elem.isNull())
2035 QString datafile_path = datafile_elem.attribute(
"filename");
2036 if (QDir(datafile_path).isRelative())
2038 QDir layout_directory = QFileInfo(filename).absoluteDir();
2039 QString new_path = layout_directory.filePath(datafile_path);
2040 datafile_path = QFileInfo(new_path).absoluteFilePath();
2045 info.
prefix = datafile_elem.attribute(
"prefix");
2047 QDomElement datasources_elem = datafile_elem.firstChildElement(
"selected_" 2049 QString topics_list = datasources_elem.attribute(
"value");
2052 auto plugin_elem = datafile_elem.firstChildElement(
"plugin");
2056 datafile_elem = datafile_elem.nextSiblingElement(
"fileInfo");
2059 QDomElement previous_streamer = root.firstChildElement(
"previouslyLoaded_Streamer");
2060 if (!previous_streamer.isNull())
2062 QString streamer_name = previous_streamer.attribute(
"name");
2064 QMessageBox msgBox(
this);
2065 msgBox.setWindowTitle(
"Start Streaming?");
2067 tr(
"Start the previously used streaming plugin?\n\n %1 \n\n").
arg(streamer_name));
2068 QPushButton* yes = msgBox.addButton(tr(
"Yes"), QMessageBox::YesRole);
2069 QPushButton* no = msgBox.addButton(tr(
"No"), QMessageBox::RejectRole);
2070 msgBox.setDefaultButton(yes);
2073 if (msgBox.clickedButton() == yes)
2080 for (
auto curve_name : allCurves)
2082 std::string curve_str = curve_name.toStdString();
2093 QMessageBox::warning(
2094 this, tr(
"Error Loading Streamer"),
2095 tr(
"The streamer named %1 can not be loaded.").
arg(streamer_name));
2101 QDomElement plugins = root.firstChildElement(
"Plugins");
2105 for (QDomElement plugin_elem = plugins.firstChildElement();
2106 plugin_elem.isNull() ==
false; plugin_elem = plugin_elem.nextSiblingElement())
2108 const QString plugin_name = plugin_elem.nodeName();
2113 if (plugin_elem.attribute(
"status") ==
"active")
2115 publisher->setEnabled(
true);
2121 auto custom_equations = root.firstChildElement(
"customMathEquations");
2123 if (!custom_equations.isNull())
2125 using SnippetPair = std::pair<SnippetData, QDomElement>;
2126 std::vector<SnippetPair> snippets;
2128 for (QDomElement custom_eq = custom_equations.firstChildElement(
"snippet");
2129 custom_eq.isNull() ==
false; custom_eq = custom_eq.nextSiblingElement(
"snippet"))
2135 auto DependOn = [](
const SnippetPair& a,
const SnippetPair& b) {
2136 if (b.first.linked_source == a.first.alias_name)
2140 for (
const auto&
source : b.first.additional_sources)
2142 if (
source == a.first.alias_name)
2149 std::sort(snippets.begin(), snippets.end(), DependOn);
2151 for (
const auto& [snippet, custom_eq] : snippets)
2155 CustomPlotPtr new_custom_plot = std::make_shared<LuaCustomFunction>(snippet);
2156 new_custom_plot->xmlLoadState(custom_eq);
2159 const auto& alias_name = new_custom_plot->aliasName();
2164 catch (std::runtime_error& err)
2166 QMessageBox::warning(
this, tr(
"Exception"),
2167 tr(
"Failed to load customMathEquation [%1] \n\n %2\n")
2168 .
arg(snippet.alias_name)
2175 auto colormaps = root.firstChildElement(
"colorMaps");
2177 if (!colormaps.isNull())
2179 for (
auto colormap = colormaps.firstChildElement(
"colorMap");
2180 colormap.isNull() ==
false; colormap = colormap.nextSiblingElement(
"colorMap"))
2182 QString
name = colormap.attribute(
"name");
2187 QByteArray snippets_saved_xml =
2188 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
2190 auto snippets_element = root.firstChildElement(
"snippets");
2191 if (!snippets_element.isNull())
2196 bool snippets_are_different =
false;
2197 for (
const auto& snippet_it : snippets_layout)
2199 auto prev_it = snippets_previous.find(snippet_it.first);
2201 if (prev_it == snippets_previous.end() ||
2202 prev_it->second.function != snippet_it.second.function ||
2203 prev_it->second.global_vars != snippet_it.second.global_vars)
2205 snippets_are_different =
true;
2210 if (snippets_are_different)
2212 QMessageBox msgBox(
this);
2213 msgBox.setWindowTitle(
"Overwrite custom transforms?");
2214 msgBox.setText(
"Your layout file contains a set of custom transforms different " 2216 "the last one you used.\nWant to load these transformations?");
2217 msgBox.addButton(QMessageBox::No);
2218 msgBox.addButton(QMessageBox::Yes);
2219 msgBox.setDefaultButton(QMessageBox::Yes);
2221 if (msgBox.exec() == QMessageBox::Yes)
2223 for (
const auto& snippet_it : snippets_layout)
2225 snippets_previous[snippet_it.first] = snippet_it.second;
2228 auto snippets_root_element =
ExportSnippets(snippets_previous, doc);
2229 doc.appendChild(snippets_root_element);
2230 settings.setValue(
"AddCustomPlotDialog.savedXML", doc.toByteArray(2));
2248 if (
ui->pushButtonLink->isChecked())
2252 auto tabs = it.second->tabWidget();
2253 for (
int t = 0; t < tabs->count(); t++)
2255 if (
PlotDocker* matrix = dynamic_cast<PlotDocker*>(tabs->widget(t)))
2271 range.
min = rect.left();
2272 range.
max = rect.right();
2277 range.
min = std::min(rect.left(), range.
min);
2278 range.
max = std::max(rect.right(), range.
max);
2290 bound_act.setLeft(range.
min);
2291 bound_act.setRight(range.
max);
2313 auto func = [&](QTabWidget* tabs) {
2314 for (
int t = 0; t < tabs->count(); t++)
2325 operation(plot, matrix,
index);
2332 func(it.second->tabWidget());
2345 ui->timeSlider->setLimits(std::get<0>(range), std::get<1>(range), std::get<2>(range));
2348 _tracker_time = std::min(_tracker_time,
ui->timeSlider->getMaximum());
2354 double min_time = std::get<0>(range);
2356 const bool remove_offset =
ui->pushButtonRemoveTimeOffset->isChecked();
2357 if (remove_offset && min_time != std::numeric_limits<double>::max())
2396 std::vector<TransformFunction*> transforms;
2400 transforms.push_back(
function.
get());
2403 transforms.begin(), transforms.end(),
2410 for (
auto&
function : transforms)
2412 if (dynamic_cast<ReactiveLuaFunction*>(
function) ==
nullptr)
2414 function->calculate();
2422 if (is_streaming_active)
2425 double max_time = std::get<1>(range);
2440 double real_value = value;
2467 if (this->signalsBlocked() ==
false)
2475 QLineEdit* timeLine =
ui->displayTime;
2477 if (
ui->pushButtonUseDateTime->isChecked())
2479 if (
ui->pushButtonRemoveTimeOffset->isChecked())
2481 QTime
time = QTime::fromMSecsSinceStartOfDay(std::round(relative_time * 1000.0));
2482 timeLine->setText(time.toString(
"HH:mm::ss.zzz"));
2486 QDateTime datetime =
2487 QDateTime::fromMSecsSinceEpoch(std::round(
_tracker_time * 1000.0));
2488 timeLine->setText(datetime.toString(
"[yyyy MMM dd] HH:mm::ss.zzz"));
2493 timeLine->setText(QString::number(relative_time,
'f', 3));
2496 QFontMetrics fm(timeLine->font());
2497 int width = fm.width(timeLine->text()) + 10;
2498 timeLine->setFixedWidth(std::max(100, width));
2560 std::vector<std::string> names;
2562 auto AddFromGroup = [&](
auto& series) {
2563 for (
auto& it : series)
2565 const auto& group = it.second.group();
2566 if (group && group->name() == group_name)
2568 names.push_back(it.first);
2585 QString tooltipText = QString(
"%1 has %2 outstanding notitication%3")
2588 .arg(active_count > 1 ?
"s" :
"");
2589 ui->buttonStreamingNotifications->setToolTip(tooltipText);
2594 ui->buttonStreamingNotifications->setToolTip(
"View streaming alerts");
2600 static bool first =
true;
2601 if (checked &&
ui->pushButtonRemoveTimeOffset->isChecked())
2605 QMessageBox::information(
this, tr(
"Note"),
2606 tr(
"When \"Use Date Time\" is checked, the option " 2607 "\"Remove Time Offset\" " 2608 "is automatically disabled.\n" 2609 "This message will be shown only once."));
2612 ui->pushButtonRemoveTimeOffset->setChecked(
false);
2650 settings.setValue(
"MainWindow.geometry", saveGeometry());
2651 settings.setValue(
"MainWindow.state", saveState());
2653 settings.setValue(
"MainWindow.activateGrid",
ui->pushButtonActivateGrid->isChecked());
2654 settings.setValue(
"MainWindow.removeTimeOffset",
2655 ui->pushButtonRemoveTimeOffset->isChecked());
2656 settings.setValue(
"MainWindow.dateTimeDisplay",
ui->pushButtonUseDateTime->isChecked());
2657 settings.setValue(
"MainWindow.buttonLink",
ui->pushButtonLink->isChecked());
2658 settings.setValue(
"MainWindow.buttonRatio",
ui->pushButtonRatio->isChecked());
2660 settings.setValue(
"MainWindow.streamingBufferValue",
ui->streamingSpinBox->value());
2661 settings.setValue(
"MainWindow.timeTrackerSetting", (
int)
_tracker_param);
2662 settings.setValue(
"MainWindow.splitterWidth",
ui->mainSplitter->sizes()[0]);
2672 ui->widgetStack->setCurrentIndex(1);
2679 ui->widgetStack->setCurrentIndex(1);
2683 qWarning(
"failed to find custom equation");
2687 std::dynamic_pointer_cast<LuaCustomFunction>(custom_it->second));
2697 qWarning(
"failed to find custom equation");
2706 catch (
const std::runtime_error& e)
2708 QMessageBox::critical(
this,
"error",
2709 "Failed to refresh data : " + QString::fromStdString(e.what()));
2718 delta_ms = std::max((qint64)
_publish_timer->interval(), delta_ms);
2721 if (_tracker_time >=
ui->timeSlider->getMaximum())
2723 if (!
ui->playbackLoop->isChecked())
2725 ui->pushButtonPlay->setChecked(
false);
2727 _tracker_time =
ui->timeSlider->getMinimum();
2730 auto prev =
ui->timeSlider->blockSignals(
true);
2731 ui->timeSlider->setRealValue(_tracker_time);
2732 ui->timeSlider->blockSignals(prev);
2741 it.second->play(_tracker_time);
2752 std::set<PlotWidget*> widget_to_replot;
2754 for (
auto custom_plot : custom_plots)
2756 const std::string& curve_name = custom_plot->aliasName().toStdString();
2761 data_it->second.clear();
2767 catch (std::exception& ex)
2769 QMessageBox::warning(
this, tr(
"Warning"),
2770 tr(
"Failed to create the custom timeseries. " 2784 custom_it->second = custom_plot;
2790 widget_to_replot.insert(plot);
2796 ui->widgetStack->setCurrentIndex(0);
2799 for (
auto plot : widget_to_replot)
2801 plot->updateCurves(
true);
2809 QDesktopServices::openUrl(QUrl(
"https://github.com/facontidavide/PlotJuggler/issues"));
2814 QDesktopServices::openUrl(QUrl(
"https://twitter.com/intent/" 2815 "tweet?hashtags=PlotJuggler"));
2820 QDialog* dialog =
new QDialog(
this);
2821 auto ui =
new Ui::AboutDialog();
2822 ui->setupUi(dialog);
2824 ui->label_version->setText(QString(
"version: ") + QApplication::applicationVersion());
2825 dialog->setAttribute(Qt::WA_DeleteOnClose);
2827 QFile fileTitle(
_skin_path +
"/about_window_title.html");
2828 if (fileTitle.open(QIODevice::ReadOnly))
2830 ui->titleTextBrowser->setHtml(fileTitle.readAll());
2833 QFile fileBody(
_skin_path +
"/about_window_body.html");
2834 if (fileBody.open(QIODevice::ReadOnly))
2836 ui->bodyTextBrowser->setHtml(fileBody.readAll());
2839 dialog->setAttribute(Qt::WA_DeleteOnClose);
2848 dialog->restoreGeometry(settings.value(
"Cheatsheet.geometry").toByteArray());
2850 settings.setValue(
"Cheatsheet.geometry", dialog->saveGeometry());
2851 dialog->deleteLater();
2856 QDialog* dialog =
new QDialog(
this);
2857 auto ui =
new Ui::SupportDialog();
2858 ui->setupUi(dialog);
2860 dialog->setAttribute(Qt::WA_DeleteOnClose);
2948 QMessageBox::warning(
this, tr(
"Warning"),
2949 tr(
"No plugin was loaded to process a data file\n"));
2955 QString file_extension_filter;
2957 std::set<QString> extensions;
2962 for (QString extension : loader->compatibleFileExtensions())
2964 extensions.insert(extension.toLower());
2968 for (
const auto& it : extensions)
2970 file_extension_filter.append(QString(
" *.") + it);
2973 QString directory_path =
2974 settings.value(
"MainWindow.lastDatafileDirectory", QDir::currentPath()).toString();
2976 QFileDialog loadDialog(
this);
2977 loadDialog.setFileMode(QFileDialog::ExistingFiles);
2978 loadDialog.setViewMode(QFileDialog::Detail);
2979 loadDialog.setNameFilter(file_extension_filter);
2980 loadDialog.setDirectory(directory_path);
2982 QStringList fileNames;
2983 if (loadDialog.exec())
2985 fileNames = loadDialog.selectedFiles();
2988 if (fileNames.isEmpty())
2993 directory_path = QFileInfo(fileNames[0]).absolutePath();
2994 settings.setValue(
"MainWindow.lastDatafileDirectory", directory_path);
3006 QString directory_path =
3007 settings.value(
"MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3009 QFileDialog::getOpenFileName(
this,
"Open Layout", directory_path,
"*.xml");
3010 if (filename.isEmpty())
3020 directory_path = QFileInfo(filename).absolutePath();
3021 settings.setValue(
"MainWindow.lastLayoutDirectory", directory_path);
3030 QString directory_path =
3031 settings.value(
"MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3033 QFileDialog saveDialog(
this);
3034 saveDialog.setOption(QFileDialog::DontUseNativeDialog,
true);
3036 QGridLayout* save_layout =
static_cast<QGridLayout*
>(saveDialog.layout());
3038 QFrame* frame =
new QFrame;
3039 frame->setFrameStyle(QFrame::Box | QFrame::Plain);
3040 frame->setLineWidth(1);
3042 QVBoxLayout* vbox =
new QVBoxLayout;
3043 QLabel* title =
new QLabel(
"Save Layout options");
3044 QFrame* separator =
new QFrame;
3045 separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
3047 auto checkbox_datasource =
new QCheckBox(
"Save data source");
3048 checkbox_datasource->setToolTip(
"the layout will remember the source of your data,\n" 3049 "i.e. the Datafile used or the Streaming Plugin loaded " 3051 checkbox_datasource->setFocusPolicy(Qt::NoFocus);
3052 checkbox_datasource->setChecked(
3053 settings.value(
"MainWindow.saveLayoutDataSource",
true).toBool());
3055 auto checkbox_snippets =
new QCheckBox(
"Save Scripts (transforms and colormaps)");
3056 checkbox_snippets->setToolTip(
"Do you want the layout to save your Lua scripts?");
3057 checkbox_snippets->setFocusPolicy(Qt::NoFocus);
3058 checkbox_snippets->setChecked(
3059 settings.value(
"MainWindow.saveLayoutSnippets",
true).toBool());
3061 vbox->addWidget(title);
3062 vbox->addWidget(separator);
3063 vbox->addWidget(checkbox_datasource);
3064 vbox->addWidget(checkbox_snippets);
3065 frame->setLayout(vbox);
3067 int rows = save_layout->rowCount();
3068 int col = save_layout->columnCount();
3069 save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
3071 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
3072 saveDialog.setDefaultSuffix(
"xml");
3073 saveDialog.setNameFilter(
"XML (*.xml)");
3074 saveDialog.setDirectory(directory_path);
3077 if (saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
3082 QString fileName = saveDialog.selectedFiles().first();
3084 if (fileName.isEmpty())
3089 directory_path = QFileInfo(fileName).absolutePath();
3090 settings.setValue(
"MainWindow.lastLayoutDirectory", directory_path);
3091 settings.setValue(
"MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked());
3092 settings.setValue(
"MainWindow.saveLayoutSnippets", checkbox_snippets->isChecked());
3094 QDomElement root = doc.namedItem(
"root").toElement();
3096 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3098 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3102 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3104 if (checkbox_datasource->isChecked())
3106 QDomElement loaded_list = doc.createElement(
"previouslyLoaded_Datafiles");
3110 QString loaded_datafile = QDir(directory_path).relativeFilePath(loaded.filename);
3112 QDomElement file_elem = doc.createElement(
"fileInfo");
3113 file_elem.setAttribute(
"filename", loaded_datafile);
3114 file_elem.setAttribute(
"prefix", loaded.prefix);
3116 QDomElement datasources_elem = doc.createElement(
"selected_datasources");
3117 QString topics_list = loaded.selected_datasources.join(
";");
3118 datasources_elem.setAttribute(
"value", topics_list);
3119 file_elem.appendChild(datasources_elem);
3121 file_elem.appendChild(loaded.plugin_config.firstChild());
3122 loaded_list.appendChild(file_elem);
3124 root.appendChild(loaded_list);
3128 QDomElement loaded_streamer = doc.createElement(
"previouslyLoaded_Streamer");
3130 loaded_streamer.setAttribute(
"name", streamer_name);
3131 root.appendChild(loaded_streamer);
3135 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3136 if (checkbox_snippets->isChecked())
3138 QDomElement custom_equations = doc.createElement(
"customMathEquations");
3141 const auto& custom_plot = custom_it.second;
3142 custom_plot->xmlSaveState(doc, custom_equations);
3144 root.appendChild(custom_equations);
3146 QByteArray snippets_xml_text =
3147 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
3150 root.appendChild(snippets_root);
3152 QDomElement color_maps = doc.createElement(
"colorMaps");
3155 QString colormap_name = it.first;
3156 QDomElement colormap = doc.createElement(
"colorMap");
3157 QDomText colormap_script = doc.createTextNode(it.second->script());
3158 colormap.setAttribute(
"name", colormap_name);
3159 colormap.appendChild(colormap_script);
3160 color_maps.appendChild(colormap);
3163 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3165 QFile file(fileName);
3166 if (file.open(QIODevice::WriteOnly))
3168 QTextStream stream(&file);
3169 stream << doc.toString() << endl;
3175 static bool first_call =
true;
3179 QMessageBox::information(
this,
"Remember!",
3180 "Press F10 to switch back to the normal view");
3199 for (QAction* action : menu->actions())
3201 if (action->isSeparator())
3205 menu->removeAction(action);
3207 menu->setEnabled(
false);
3209 settings.setValue(
"MainWindow.recentlyLoadedDatafile", {});
3215 for (QAction* action : menu->actions())
3217 if (action->isSeparator())
3221 menu->removeAction(action);
3223 menu->setEnabled(
false);
3225 settings.setValue(
"MainWindow.recentlyLoadedLayout", {});
3230 QMessageBox msgBox(
this);
3231 msgBox.setWindowTitle(
"Warning. Can't be undone.");
3232 msgBox.setText(tr(
"Do you want to remove the previously loaded data?\n"));
3233 msgBox.addButton(QMessageBox::No);
3234 msgBox.addButton(QMessageBox::Yes);
3235 msgBox.setDefaultButton(QMessageBox::Yes);
3236 auto reply = msgBox.exec();
3238 if (reply == QMessageBox::No)
3249 QString prev_style = settings.value(
"Preferences::theme",
"light").toString();
3254 QString theme = settings.value(
"Preferences::theme").toString();
3256 if (!theme.isEmpty() && theme != prev_style)
3264 ui->timeSlider->setFocus();
3265 ui->timeSlider->setRealStepValue(step);
3271 QString directory_path =
3272 settings.value(
"MainWindow.loadStyleSheetDirectory", QDir::currentPath())
3275 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load StyleSheet"),
3276 directory_path, tr(
"(*.qss)"));
3277 if (fileName.isEmpty())
3284 directory_path = QFileInfo(fileName).absolutePath();
3285 settings.setValue(
"MainWindow.loadStyleSheetDirectory", directory_path);
3292 case LabelStatus::LEFT:
3295 case LabelStatus::RIGHT:
3298 case LabelStatus::HIDDEN:
3308 plot->setLegendAlignment(Qt::AlignLeft);
3312 plot->setLegendAlignment(Qt::AlignRight);
3329 settings.setValue(
"MainWindow.previousStreamingPlugin", current_text);
3331 ui->buttonStreamingOptions->setEnabled(!streamer->availableActions().empty());
3333 std::pair<QAction*, int> notifications_pair = streamer->notificationAction();
3334 if (notifications_pair.first ==
nullptr)
3336 ui->buttonStreamingNotifications->setEnabled(
false);
3346 ui->buttonStreamingStart->setEnabled(
false);
3347 if (
ui->buttonStreamingStart->text() ==
"Start")
3355 ui->buttonStreamingStart->setEnabled(
true);
3359 : QMenu(parent), _w(relative_widget)
3365 QPoint p =
_w->mapToGlobal({});
3366 QRect geo =
_w->geometry();
3367 this->
move(p.x() + geo.width(), p.y());
3377 _w->setAttribute(Qt::WA_UnderMouse,
false);
3384 for (
auto action : _recent_data_files->actions())
3386 menu->addAction(action);
3393 if (_data_streamer.empty())
3397 auto streamer = _data_streamer.at(ui->comboStreaming->currentText());
3400 for (
auto action : streamer->availableActions())
3402 menu->addAction(action);
3409 bool hidden = !ui->frameFile->isHidden();
3410 ui->buttonHideFileFrame->setText(hidden ?
"+" :
" -");
3411 ui->frameFile->setHidden(hidden);
3414 settings.setValue(
"MainWindow.hiddenFileFrame", hidden);
3419 bool hidden = !ui->frameStreaming->isHidden();
3420 ui->buttonHideStreamingFrame->setText(hidden ?
"+" :
" -");
3421 ui->frameStreaming->setHidden(hidden);
3424 settings.setValue(
"MainWindow.hiddenStreamingFrame", hidden);
3429 bool hidden = !ui->framePublishers->isHidden();
3430 ui->buttonHidePublishersFrame->setText(hidden ?
"+" :
" -");
3431 ui->framePublishers->setHidden(hidden);
3434 settings.setValue(
"MainWindow.hiddenPublishersFrame", hidden);
3441 for (
auto action : _recent_layout_files->actions())
3443 menu->addAction(action);
3452 QStringList level_names = {
"tabbed_widget",
"Tab",
"Container",
"DockSplitter",
3453 "DockArea",
"plot",
"curve" };
3455 std::function<void(int, QDomElement)> recursiveXmlStream;
3456 recursiveXmlStream = [&](
int level, QDomElement parent_elem) {
3457 QString level_name = level_names[level];
3458 for (
auto elem = parent_elem.firstChildElement(level_name); elem.isNull() ==
false;
3459 elem = elem.nextSiblingElement(level_name))
3461 if (level_name ==
"curve")
3463 curves.push_back(elem.attribute(
"name"));
3467 recursiveXmlStream(level + 1, elem);
3473 recursiveXmlStream(0, root_node);
void on_actionExit_triggered()
QTimer * _animated_streaming_timer
void on_comboStreaming_currentIndexChanged(const QString ¤t_text)
void on_actionClearBuffer_triggered()
MoveDataRet MoveData(PlotDataMapRef &source, PlotDataMapRef &destination, bool remove_older)
void onActionFullscreenTriggered()
QShortcut _streaming_shortcut
void on_actionColorMap_Editor_triggered()
void on_actionSupportPlotJuggler_triggered()
void dataSourceRemoved(const std::string &name)
void on_pushButtonLoadDatafile_clicked()
void BuildDummyData(PlotDataMapRef &datamap)
void on_actionShare_the_love_triggered()
QShortcut _fullscreen_shortcut
PlotWidget * plotAt(int index)
void onEditCustomPlot(const std::string &plot_name)
void on_buttonStreamingStart_clicked()
void updateReactivePlots()
void onPlotTabAdded(PlotDocker *docker)
void editMathPlot(const std::string &plot_name)
void on_actionPreferences_triggered()
void plotWidgetAdded(PlotWidget *)
CurveListPanel * _curvelist_widget
void calculateAndAdd(PlotDataMapRef &src_data)
void on_stylesheetChanged(QString style_name)
void updatedDisplayTime()
std::vector< FileLoadInfo > _loaded_datafiles
void resizeEvent(QResizeEvent *)
std::map< QString, ColorMap::Ptr > & ColorMapLibrary()
bool loadDataFromFiles(QStringList filenames)
void checkAllCurvesFromLayout(const QDomElement &root)
The DataLoader plugin type is used to load files.
PlotDataMapRef _mapped_plot_data
void closeEvent(QCloseEvent *event)
std::map< CurveTracker::Parameter, QIcon > _tracker_button_icons
bool xmlLoadState(QDomDocument state_document)
bool loadLayoutFromFile(QString filename)
void forEachWidget(std::function< void(PlotWidget *, PlotDocker *, int)> op)
std::unordered_set< std::string > getAllNames() const
void on_actionReportBug_triggered()
The DataStreamer base classm used to read streaming of data.
bool erase(const std::string &name)
QStringList selected_datasources
Optional list of pre-selected datasource.
void on_actionClearRecentLayout_triggered()
SnippetData GetSnippetFromXML(const QDomElement &element)
void dropEvent(QDropEvent *event)
void on_actionCheatsheet_triggered()
void onTrackerMovedFromWidget(QPointF pos)
void on_pushButtonZoomOut_clicked()
std::map< QString, ToolboxPluginPtr > _toolboxes
QMovie * _animated_streaming_movie
void on_actionClearRecentData_triggered()
std::deque< QDomDocument > _redo_states
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
void onPlotAdded(PlotWidget *plot)
QString filename
name of the file to open
void updateDataAndReplot(bool replot_hidden_tabs)
void refreshMathPlot(const std::string &curve_name)
void onTimeSlider_valueChanged(double abs_time)
void createMathPlot(const std::string &linked_plot)
QStringList initializePlugins(QString subdir_name)
std::map< QString, DataStreamerPtr > _data_streamer
void startStreamingPlugin(QString streamer_name)
void on_buttonStreamingNotifications_clicked()
void setMaximumRangeX(double range)
MainWindow(const QCommandLineParser &commandline_parser, QWidget *parent=nullptr)
const QPixmap & LoadSvg(QString filename, QString style_name="light")
QStringList readAllCurvesFromXML(QDomElement root_node)
TransformsMap _transform_functions
std::map< QString, StatePublisherPtr > _state_publisher
QDomElement ExportSnippets(const SnippetsMap &snippets, QDomDocument &doc)
void updateDerivedSeries()
void on_pushButtonPlay_toggled(bool checked)
void onPlotZoomChanged(PlotWidget *modified_plot, QRectF new_range)
QElapsedTimer _undo_timer
TimeseriesMap numeric
Numerical timeseries.
virtual size_t size() const
void realValueChanged(double)
void on_pushButtonLegend_clicked()
void on_pushButtonLoadLayout_clicked()
void onTrackerTimeUpdated(double absolute_time, bool do_replot)
TabbedPlotWidget * _main_tabbed_widget
void loadAllPlugins(QStringList command_line_plugin_folders)
NLOHMANN_BASIC_JSON_TPL_DECLARATION void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression) is_nothrow_move_assignable< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
void updateRecentLayoutMenu(QStringList new_filenames)
QShortcut _playback_shotcut
const Point & front() const
void on_buttonHidePublishersFrame_clicked()
void requestDeleteAll(int)
void loadPluginState(const QDomElement &root)
void on_deleteSerieFromGroup(std::string group_name)
void loadStyleSheet(QString file_path)
QString prefix
prefix to be added to the name of the series (optional)
QStringList _disabled_plugins
std::shared_ptr< CustomFunction > CustomPlotPtr
void deleteCurves(const std::vector< std::string > &curve_names)
static void block(LexState *ls)
void on_actionLoadStyleSheet_triggered()
StringSeriesMap strings
Series of strings.
void on_buttonHideFileFrame_clicked()
void on_stylesheetChanged(QString theme)
SnippetsMap GetSnippetsFromXML(const QString &xml_text)
void on_tabbedAreaDestroyed(QObject *object)
LabelStatus _labels_status
QDateTime _prev_publish_time
CurveTracker::Parameter _tracker_param
TimeseriesMap::iterator addNumeric(const std::string &name, PlotGroup::Ptr group={})
bool addCurve(const std::string &plot_name)
void on_pushButtonRatio_toggled(bool checked)
void on_streamingSpinBox_valueChanged(int value)
static int sort(lua_State *L)
QMenu * _recent_data_files
void stopStreamingPlugin()
void enableStreamingNotificationsButton(bool enabled)
void stylesheetChanged(QString style_name)
void onAddCustomPlot(const std::string &plot_name)
std::map< QString, QString > getPrefixes() const
QDomElement savePluginState(QDomDocument &doc)
void dragEnterEvent(QDragEnterEvent *event)
PJ::DelayedCallback _tracker_delay
void on_actionDeleteAllData_triggered()
void on_buttonRecentData_clicked()
void on_buttonHideStreamingFrame_clicked()
FunctionEditorWidget * _function_editor
void onDeleteMultipleCurves(const std::vector< std::string > &curve_names)
const T & move(const T &v)
void addCustom(const QString &item_name)
bool _autostart_publishers
MonitoredValue _time_offset
void on_actionAbout_triggered()
std::deque< QDomDocument > _undo_states
ParserFactories _parser_factories
const T & first(const T &value, const Tail &...)
void set(double newValue)
void LoadColorMapFromSettings()
std::shared_ptr< StatePublisher > StatePublisherPtr
std::map< QString, DataLoaderPtr > _data_loader
void triggerSignal(int delay_ms)
void on_buttonRecentLayout_clicked()
void on_streamingToggled()
std::shared_ptr< DataStreamer > _active_streamer_plugin
void on_stylesheetChanged(QString theme)
bool isStreamingActive() const
void hiddenItemsChanged()
void on_pushButtonUseDateTime_toggled(bool checked)
std::vector< std::string > added_curves
void onRefreshCustomPlot(const std::string &plot_name)
void on_buttonStreamingPause_toggled(bool paused)
QStringList _enabled_plugins
void onCustomPlotCreated(std::vector< CustomPlotPtr > plot)
virtual const char * name() const =0
Name of the plugin type, NOT the particular instance.
void AddPrefixToPlotData(const std::string &prefix, std::unordered_map< std::string, Value > &data)
AnySeriesMap user_defined
const Point & back() const
void on_pushButtonActivateGrid_toggled(bool checked)
void importPlotDataMap(PlotDataMapRef &new_data, bool remove_old)
void on_buttonStreamingOptions_clicked()
void on_playbackStep_valueChanged(double arg1)
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
void on_pushButtonTimeTracker_pressed()
bool _disable_undo_logging
QMenu * _recent_layout_files
QString SetApplicationStyleSheet(QString style)
QString _default_streamer
QDomDocument xmlSaveState() const
void on_splitterMoved(int, int)
void on_pushButtonRemoveTimeOffset_toggled(bool checked)
void connectCallback(Function callback)
std::shared_ptr< DataLoader > DataLoaderPtr
void update2ndColumnValues(double time)
void on_pushButtonSaveLayout_clicked()
void updateRecentDataMenu(QStringList new_filenames)
void onUpdateLeftTableValues()
std::unordered_set< std::string > loadDataFromFile(const FileLoadInfo &info)
void on_streamingNotificationsChanged(int active_notifications_count)
The PlotJugglerPlugin is the base class of all the plugins.
void removeCurve(const std::string &name)
std::tuple< double, double, int > calculateVisibleRangeX()