10 #include <QApplication>
11 #include <QActionGroup>
13 #include <QCommandLineParser>
15 #include <QDesktopServices>
16 #include <QDomDocument>
17 #include <QDoubleSpinBox>
18 #include <QElapsedTimer>
19 #include <QFileDialog>
20 #include <QInputDialog>
23 #include <QMessageBox>
25 #include <QMouseEvent>
26 #include <QPluginLoader>
27 #include <QPushButton>
28 #include <QKeySequence>
31 #include <QStringListModel>
34 #include <QTextStream>
36 #include <QHeaderView>
37 #include <QStandardPaths>
38 #include <QXmlStreamReader>
53 #include "ui_aboutdialog.h"
54 #include "ui_support_dialog.h"
60 #ifdef COMPILED_WITH_CATKIN
63 #ifdef COMPILED_WITH_AMENT
64 #include <ament_index_cpp/get_package_prefix.hpp>
65 #include <ament_index_cpp/get_package_share_directory.hpp>
71 , _undo_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Z), this)
72 , _redo_shortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z), this)
73 , _fullscreen_shortcut(Qt::Key_F10, this)
74 , _streaming_shortcut(QKeySequence(Qt::CTRL + Qt::Key_Space), this)
75 , _playback_shotcut(Qt::Key_Space, this)
77 , _active_streamer_plugin(
nullptr)
78 , _disable_undo_logging(false)
82 , _recent_data_files(new QMenu())
83 , _recent_layout_files(new QMenu())
85 QLocale::setDefault(QLocale::c());
91 if (commandline_parser.isSet(
"enabled_plugins"))
102 if (commandline_parser.isSet(
"disabled_plugins"))
114 if (commandline_parser.isSet(
"skin_path"))
116 QDir path(commandline_parser.value(
"skin_path"));
122 if (commandline_parser.isSet(
"window_title"))
124 setWindowTitle(commandline_parser.value(
"window_title"));
128 QFile fileTitle(
_skin_path +
"/mainwindow_title.txt");
129 if (fileTitle.open(QIODevice::ReadOnly))
131 QString title = fileTitle.readAll().trimmed();
132 setWindowTitle(title);
138 ui->playbackLoop->setText(
"");
139 ui->buttonZoomOut->setText(
"");
140 ui->buttonPlay->setText(
"");
141 ui->buttonUseDateTime->setText(
"");
142 ui->buttonActivateGrid->setText(
"");
143 ui->buttonRatio->setText(
"");
144 ui->buttonLink->setText(
"");
145 ui->buttonTimeTracker->setText(
"");
146 ui->buttonLoadDatafile->setText(
"");
147 ui->buttonRemoveTimeOffset->setText(
"");
148 ui->buttonLegend->setText(
"");
150 ui->widgetStatusBar->setHidden(
true);
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)
227 ui->mainSplitter->setCollapsible(0,
true);
228 ui->mainSplitter->setStretchFactor(0, 2);
229 ui->mainSplitter->setStretchFactor(1, 6);
231 ui->layoutTimescale->removeWidget(
ui->widgetButtons);
234 connect(
ui->mainSplitter, SIGNAL(splitterMoved(
int,
int)),
242 auto plugin_extra_folders =
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->buttonActivateGrid->setChecked(activate_grid);
293 bool zoom_link_active = settings.value(
"MainWindow.buttonLink",
true).toBool();
294 ui->buttonLink->setChecked(zoom_link_active);
296 bool ration_active = settings.value(
"MainWindow.buttonRatio",
true).toBool();
297 ui->buttonRatio->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->buttonUseDateTime->setChecked(datetime_display);
306 bool remove_time_offset = settings.value(
"MainWindow.removeTimeOffset",
true).toBool();
307 ui->buttonRemoveTimeOffset->setChecked(remove_time_offset);
309 if (settings.value(
"MainWindow.hiddenFileFrame",
false).toBool())
311 ui->buttonHideFileFrame->setText(
"+");
312 ui->frameFile->setHidden(
true);
314 if (settings.value(
"MainWindow.hiddenStreamingFrame",
false).toBool())
316 ui->buttonHideStreamingFrame->setText(
"+");
317 ui->frameStreaming->setHidden(
true);
319 if (settings.value(
"MainWindow.hiddenPublishersFrame",
false).toBool())
321 ui->buttonHidePublishersFrame->setText(
"+");
322 ui->framePublishers->setHidden(
true);
326 QIcon trackerIconA, trackerIconB, trackerIconC;
328 trackerIconA.addFile(QStringLiteral(
":/style_light/line_tracker.png"), QSize(36, 36));
329 trackerIconB.addFile(QStringLiteral(
":/style_light/line_tracker_1.png"), QSize(36, 36));
330 trackerIconC.addFile(QStringLiteral(
":/style_light/line_tracker_a.png"), QSize(36, 36));
336 int tracker_setting =
344 auto editor_layout =
new QVBoxLayout();
345 editor_layout->setMargin(0);
346 ui->formulaPage->setLayout(editor_layout);
352 [
this]() {
ui->widgetStack->setCurrentIndex(0); });
360 QString theme = settings.value(
"Preferences::theme",
"light").toString();
368 auto json_parser = std::make_shared<JSON_ParserFactory>();
371 auto cbor_parser = std::make_shared<CBOR_ParserFactory>();
374 auto bson_parser = std::make_shared<BSON_ParserFactory>();
377 auto msgpack = std::make_shared<MessagePack_ParserFactory>();
385 ui->comboStreaming->setCurrentIndex(index);
407 if (elapsed_ms < 100)
464 auto prev =
ui->timeSlider->blockSignals(
true);
466 ui->timeSlider->blockSignals(prev);
483 it.second->updateState(absolute_time);
508 &QPushButton::toggle);
512 QShortcut* open_menu_shortcut =
new QShortcut(QKeySequence(Qt::ALT + Qt::Key_F),
this);
513 connect(open_menu_shortcut, &QShortcut::activated,
514 [
this]() {
ui->menuFile->exec(
ui->menuBar->mapToGlobal(QPoint(0, 25))); });
516 QShortcut* open_help_shortcut =
new QShortcut(QKeySequence(Qt::ALT + Qt::Key_H),
this);
517 connect(open_help_shortcut, &QShortcut::activated,
518 [
this]() {
ui->menuHelp->exec(
ui->menuBar->mapToGlobal(QPoint(230, 25))); });
524 settings.value(
"MainWindow.recentlyLoadedDatafile").toStringList());
526 settings.value(
"MainWindow.recentlyLoadedLayout").toStringList());
533 QStringList plugin_folders;
534 QStringList builtin_folders;
536 plugin_folders += command_line_plugin_folders;
538 settings.value(
"Preferences::plugin_folders", QStringList()).toStringList();
540 builtin_folders += QCoreApplication::applicationDirPath();
544 #ifdef COMPILED_WITH_CATKIN
545 builtin_folders += QCoreApplication::applicationDirPath() +
"_ros";
547 const char* env = std::getenv(
"CMAKE_PREFIX_PATH");
550 QString env_catkin_paths = QString::fromStdString(env);
551 env_catkin_paths.replace(
";",
":");
552 auto catkin_paths = env_catkin_paths.split(
":");
554 for (
const auto& path : catkin_paths)
556 builtin_folders += path +
"/lib/plotjuggler_ros";
560 #ifdef COMPILED_WITH_AMENT
561 auto ros2_path = QString::fromStdString(ament_index_cpp::get_package_prefix(
"plotjugg"
564 ros2_path +=
"/lib/plotjuggler_ros";
570 QMessageBox::warning(
nullptr,
"Missing package [plotjuggler-ros]",
571 "If you just upgraded from PlotJuggler 2.x to 3.x , try "
572 "installing this package:\n\n"
573 "sudo apt install ros-${ROS_DISTRO}-plotjuggler-ros",
574 QMessageBox::Cancel, QMessageBox::Cancel);
578 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
"/PlotJuggl"
580 builtin_folders.removeDuplicates();
582 plugin_folders += builtin_folders;
583 plugin_folders.removeDuplicates();
585 for (
const auto& folder : plugin_folders)
590 settings.setValue(
"Preferences::builtin_plugin_folders", builtin_folders);
595 static std::set<QString> loaded_plugins;
596 QStringList loaded_out;
598 qDebug() <<
"Loading compatible plugins from directory: " << directory_name;
599 int loaded_count = 0;
601 QDir pluginsDir(directory_name);
603 for (
const QString& filename : pluginsDir.entryList(QDir::Files))
605 QFileInfo fileinfo(filename);
606 if (fileinfo.suffix() !=
"so" && fileinfo.suffix() !=
"dll" &&
607 fileinfo.suffix() !=
"dylib")
612 if (loaded_plugins.find(filename) != loaded_plugins.end())
617 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename),
this);
622 plugin = pluginLoader.instance();
624 catch (std::runtime_error& err)
626 qDebug() << QString(
"%1: skipping, because it threw the following exception: %2")
633 auto class_name = pluginLoader.metaData().value(
"className").toString();
634 loaded_out.push_back(class_name);
636 DataLoader* loader = qobject_cast<DataLoader*>(plugin);
637 StatePublisher* publisher = qobject_cast<StatePublisher*>(plugin);
638 DataStreamer* streamer = qobject_cast<DataStreamer*>(plugin);
640 ToolboxPlugin* toolbox = qobject_cast<ToolboxPlugin*>(plugin);
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,
724 connect(publisher, &StatePublisher::closed, start_checkbox,
725 [=]() { start_checkbox->setChecked(
false); });
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 = [=]() {
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)
763 QStringList encodings = QString(message_parser->
encoding()).split(
";");
764 auto parser_ptr = std::shared_ptr<ParserFactoryPlugin>(message_parser);
765 for (
const QString& encoding : encodings)
778 connect(streamer, &DataStreamer::closed,
this,
781 connect(streamer, &DataStreamer::clearBuffers,
this,
790 connect(streamer, &DataStreamer::removeGroup,
this,
793 connect(streamer, &DataStreamer::dataReceived,
this, [
this]() {
796 _replot_timer->setSingleShot(true);
797 _replot_timer->start(40);
801 connect(streamer, &DataStreamer::notificationsChanged,
this,
808 _toolboxes.insert(std::make_pair(plugin_name, toolbox));
810 auto action =
ui->menuTools->addAction(toolbox->
name());
812 int new_index =
ui->widgetStack->count();
814 auto widget = provided.first;
815 ui->widgetStack->addWidget(widget);
817 connect(action, &QAction::triggered, toolbox, &ToolboxPlugin::onShowWidget);
819 connect(action, &QAction::triggered,
this,
820 [=]() {
ui->widgetStack->setCurrentIndex(new_index); });
822 connect(toolbox, &ToolboxPlugin::closed,
this,
823 [=]() {
ui->widgetStack->setCurrentIndex(0); });
825 connect(toolbox, &ToolboxPlugin::importData,
this,
831 connect(toolbox, &ToolboxPlugin::plotCreated,
this,
832 [=](std::string name,
bool is_custom) {
848 if (pluginLoader.errorString().contains(
"is not an ELF object") ==
false)
850 qDebug() << filename <<
": " << pluginLoader.errorString();
872 QSignalBlocker
block(
ui->comboStreaming);
873 ui->comboStreaming->setEnabled(
true);
874 ui->buttonStreamingStart->setEnabled(
true);
878 if (
ui->comboStreaming->findText(it.first) == -1)
880 ui->comboStreaming->addItem(it.first);
886 QString streaming_name =
888 .value(
"MainWindow.previousStreamingPlugin",
ui->comboStreaming->itemText(0))
895 streaming_name = streamer_it->first;
898 ui->comboStreaming->setCurrentText(streaming_name);
900 bool contains_options = !streamer_it->second->availableActions().empty();
901 ui->buttonStreamingOptions->setEnabled(contains_options);
903 qDebug() <<
"Number of plugins loaded: " << loaded_count <<
"\n";
918 int totalWidth = sizes[0] + sizes[1];
921 static bool first =
true;
922 if (sizes[0] != 0 &&
first)
926 int splitter_width = settings.value(
"MainWindow.splitterWidth", 200).toInt();
927 auto sizes =
ui->mainSplitter->sizes();
928 int tot_splitter_width = sizes[0] + sizes[1];
929 sizes[0] = splitter_width;
930 sizes[1] = tot_splitter_width - splitter_width;
931 ui->mainSplitter->setSizes(sizes);
935 if (sizes[0] > max_left_size)
937 sizes[0] = max_left_size;
938 sizes[1] = totalWidth - max_left_size;
939 ui->mainSplitter->setSizes(sizes);
961 connect(&
_time_offset, SIGNAL(valueChanged(
double)), plot,
962 SLOT(on_changeTimeOffset(
double)));
964 connect(
ui->buttonUseDateTime, &QPushButton::toggled, plot,
973 p->setLegendSize(point_size);
990 if (
ui->buttonLink->isChecked())
993 if (plot != modified_plot && !plot->
isEmpty() && !plot->isXYPlot() &&
994 plot->isZoomLinkEnabled())
996 QRectF bound_act = plot->currentBoundingRect();
997 bound_act.setLeft(new_range.left());
998 bound_act.setRight(new_range.right());
999 plot->setZoomRectangle(bound_act,
false);
1000 plot->on_zoomOutVertical_triggered(
false);
1024 QDomProcessingInstruction instr = doc.createProcessingInstruction(
"xml",
"version='1.0'"
1028 doc.appendChild(instr);
1030 QDomElement root = doc.createElement(
"root");
1034 QDomElement tabbed_area = it.second->xmlSaveState(doc);
1035 root.appendChild(tabbed_area);
1038 doc.appendChild(root);
1040 QDomElement relative_time = doc.createElement(
"use_relative_time_offset");
1041 relative_time.setAttribute(
"enabled",
ui->buttonRemoveTimeOffset->isChecked());
1042 root.appendChild(relative_time);
1049 std::set<std::string> curves;
1051 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); !tw.isNull();
1052 tw = tw.nextSiblingElement(
"tabbed_widget"))
1054 for (QDomElement pm = tw.firstChildElement(
"plotmatrix"); !pm.isNull();
1055 pm = pm.nextSiblingElement(
"plotmatrix"))
1057 for (QDomElement pl = pm.firstChildElement(
"plot"); !pl.isNull();
1058 pl = pl.nextSiblingElement(
"plot"))
1060 QDomElement tran_elem = pl.firstChildElement(
"transform");
1061 std::string trans = tran_elem.attribute(
"value").toStdString();
1062 bool is_XY_plot = (trans ==
"XYPlot");
1064 for (QDomElement cv = pl.firstChildElement(
"curve"); !cv.isNull();
1065 cv = cv.nextSiblingElement(
"curve"))
1069 curves.insert(cv.attribute(
"curve_x").toStdString());
1070 curves.insert(cv.attribute(
"curve_y").toStdString());
1074 curves.insert(cv.attribute(
"name").toStdString());
1081 std::vector<std::string> missing_curves;
1083 for (
auto& curve_name : curves)
1087 missing_curves.push_back(curve_name);
1091 missing_curves.push_back(curve_name);
1094 if (missing_curves.size() > 0)
1096 QMessageBox msgBox(
this);
1097 msgBox.setWindowTitle(
"Warning");
1098 msgBox.setText(tr(
"One or more timeseries in the layout haven't been loaded yet\n"
1099 "What do you want to do?"));
1101 QPushButton* buttonRemove =
1102 msgBox.addButton(tr(
"Remove curves from plots"), QMessageBox::RejectRole);
1103 QPushButton* buttonPlaceholder =
1104 msgBox.addButton(tr(
"Create empty placeholders"), QMessageBox::YesRole);
1105 msgBox.setDefaultButton(buttonPlaceholder);
1107 if (msgBox.clickedButton() == buttonPlaceholder)
1109 for (
auto& name : missing_curves)
1121 QDomElement root = state_document.namedItem(
"root").toElement();
1124 qWarning() <<
"No <root> element found at the top-level of the XML file!";
1128 size_t num_floating = 0;
1129 std::map<QString, QDomElement> tabbed_widgets_with_name;
1131 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); tw.isNull() ==
false;
1132 tw = tw.nextSiblingElement(
"tabbed_widget"))
1134 if (tw.attribute(
"parent") != (
"main_window"))
1138 tabbed_widgets_with_name[tw.attribute(
"name")] = tw;
1142 for (
const auto& it : tabbed_widgets_with_name)
1153 if (tabbed_widgets_with_name.count(it.first) == 0)
1155 it.second->deleteLater();
1163 for (QDomElement tw = root.firstChildElement(
"tabbed_widget"); tw.isNull() ==
false;
1164 tw = tw.nextSiblingElement(
"tabbed_widget"))
1170 QDomElement relative_time = root.firstChildElement(
"use_relative_time_offset");
1171 if (!relative_time.isNull())
1173 bool remove_offset = (relative_time.attribute(
"enabled") == QString(
"1"));
1174 ui->buttonRemoveTimeOffset->setChecked(remove_offset);
1181 std::set<std::string> to_be_deleted;
1182 for (
auto& name : curve_names)
1184 to_be_deleted.insert(name);
1187 size_t prev_size = 0;
1188 while (prev_size < to_be_deleted.size())
1190 prev_size = to_be_deleted.size();
1193 for (
const auto&
source : transform->dataSources())
1195 if (to_be_deleted.count(
source->plotName()) > 0)
1197 to_be_deleted.insert(trans_name);
1203 for (
const auto& curve_name : to_be_deleted)
1218 QAction* separator =
nullptr;
1219 QStringList prev_filenames;
1220 for (QAction* action : menu->actions())
1222 if (action->isSeparator())
1227 if (new_filenames.contains(action->text()) ==
false)
1229 prev_filenames.push_back(action->text());
1231 menu->removeAction(action);
1234 new_filenames.append(prev_filenames);
1235 while (new_filenames.size() > 10)
1237 new_filenames.removeLast();
1240 for (
const auto& filename : new_filenames)
1242 QAction* action =
new QAction(filename,
nullptr);
1243 connect(action, &QAction::triggered,
this,
1245 menu->insertAction(separator, action);
1249 settings.setValue(
"MainWindow.recentlyLoadedDatafile", new_filenames);
1250 menu->setEnabled(new_filenames.size() > 0);
1257 QAction* separator =
nullptr;
1258 QStringList prev_filenames;
1259 for (QAction* action : menu->actions())
1261 if (action->isSeparator())
1266 if (new_filenames.contains(action->text()) ==
false)
1268 prev_filenames.push_back(action->text());
1270 menu->removeAction(action);
1273 new_filenames.append(prev_filenames);
1274 while (new_filenames.size() > 10)
1276 new_filenames.removeLast();
1279 for (
const auto& filename : new_filenames)
1281 QAction* action =
new QAction(filename,
nullptr);
1282 connect(action, &QAction::triggered,
this, [
this, filename] {
1288 menu->insertAction(separator, action);
1292 settings.setValue(
"MainWindow.recentlyLoadedLayout", new_filenames);
1293 menu->setEnabled(new_filenames.size() > 0);
1307 bool stopped =
false;
1309 for (
int idx = 0; idx <
ui->layoutPublishers->count(); idx++)
1311 QLayoutItem* item =
ui->layoutPublishers->itemAt(idx);
1312 if (
dynamic_cast<QWidgetItem*
>(item))
1314 if (
auto checkbox =
dynamic_cast<QCheckBox*
>(item->widget()))
1316 if (checkbox->isChecked())
1318 checkbox->setChecked(
false);
1327 QMessageBox::warning(
this,
"State publishers stopped",
1328 "All the state publishers have been stopped because old data "
1329 "has been deleted.");
1337 auto ClearOldSeries = [](
auto& prev_plot_data,
auto& new_plot_data) {
1338 for (
auto& it : prev_plot_data)
1341 if (new_plot_data.count(it.first) != 0)
1353 auto [added_curves, curve_updated, data_pushed] =
1356 for (
const auto& added_curve : added_curves)
1375 std::map<QString, QString> filename_prefix;
1377 if (filenames.size() > 1 ||
ui->checkBoxAddPrefixAndMerge->isChecked())
1380 int ret = dialog.exec();
1381 if (
ret != QDialog::Accepted)
1390 QStringList loaded_filenames;
1393 for (
int i = 0; i < filenames.size(); i++)
1397 if (filename_prefix.count(info.
filename) > 0)
1402 if (!added_names.empty())
1404 loaded_filenames.push_back(filenames[i]);
1406 for (
const auto& name : added_names)
1408 previous_names.erase(name);
1412 bool data_replaced_entirely =
false;
1414 if (previous_names.empty())
1416 data_replaced_entirely =
true;
1418 else if (!
ui->checkBoxAddPrefixAndMerge->isChecked())
1420 QMessageBox::StandardButton reply;
1421 reply = QMessageBox::question(
1422 this, tr(
"Warning"), tr(
"Do you want to remove the previously loaded data?\n"),
1423 QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton);
1425 if (reply == QMessageBox::Yes)
1427 std::vector<std::string> to_delete;
1428 for (
const auto& name : previous_names)
1430 to_delete.push_back(name);
1433 data_replaced_entirely =
true;
1438 if (loaded_filenames.size() == 1 && data_replaced_entirely &&
1445 ui->buttonReloadData->setEnabled(!loaded_filenames.empty());
1447 if (loaded_filenames.size() > 0)
1458 ui->buttonPlay->setChecked(
false);
1460 const QString extension = QFileInfo(info.
filename).suffix().toLower();
1462 typedef std::map<QString, DataLoaderPtr>::iterator MapIterator;
1464 std::vector<MapIterator> compatible_loaders;
1469 std::vector<const char*> extensions = data_loader->compatibleFileExtensions();
1471 for (
auto& ext : extensions)
1473 if (extension == QString(ext).toLower())
1475 compatible_loaders.push_back(it);
1482 std::unordered_set<std::string> added_names;
1484 if (compatible_loaders.size() == 1)
1486 dataloader = compatible_loaders.front()->second;
1490 static QString last_plugin_name_used;
1493 for (
auto& cl : compatible_loaders)
1495 const auto& name = cl->first;
1497 if (name == last_plugin_name_used)
1499 names.push_front(name);
1503 names.push_back(name);
1508 QString plugin_name =
1509 QInputDialog::getItem(
this, tr(
"QInputDialog::getItem()"),
1510 tr(
"Select the loader to use:"), names, 0,
false, &
ok);
1511 if (
ok && !plugin_name.isEmpty() &&
1515 last_plugin_name_used = plugin_name;
1523 if (!file.open(QFile::ReadOnly | QFile::Text))
1525 QMessageBox::warning(
1526 this, tr(
"Datafile"),
1527 tr(
"Cannot read file %1:\n%2.").
arg(info.
filename).arg(file.errorString()));
1539 dataloader->xmlLoadState(info.
plugin_config.firstChildElement());
1542 if (dataloader->readDataFromFile(&new_info, mapped_data))
1550 QDomElement plugin_elem = dataloader->xmlSaveState(new_info.
plugin_config);
1554 bool duplicate =
false;
1559 if (prev_loaded.filename == new_info.
filename &&
1560 prev_loaded.prefix == new_info.
prefix)
1562 prev_loaded = new_info;
1574 catch (std::exception& ex)
1576 QMessageBox::warning(
this, tr(
"Exception from the plugin"),
1577 tr(
"The plugin [%1] thrown the following exception: \n\n %3\n")
1578 .
arg(dataloader->name())
1585 QMessageBox::warning(
this, tr(
"Error"),
1586 tr(
"Cannot read files with extension %1.\n No plugin can handle "
1600 custom_it.second->reset();
1605 ui->timeSlider->setRealValue(
ui->timeSlider->getMinimum());
1617 QAction* notification_button_action = streamer->notificationAction().first;
1618 if (notification_button_action !=
nullptr)
1620 notification_button_action->trigger();
1631 ui->buttonRemoveTimeOffset->setEnabled(paused);
1632 ui->widgetPlay->setEnabled(paused);
1634 if (!paused &&
ui->buttonPlay->isChecked())
1636 ui->buttonPlay->setChecked(
false);
1658 bool prev_state =
ui->buttonStreamingPause->isChecked();
1659 ui->buttonStreamingPause->setChecked(!prev_state);
1665 ui->comboStreaming->setEnabled(
true);
1666 ui->buttonStreamingStart->setText(
"Start");
1667 ui->buttonStreamingPause->setEnabled(
false);
1668 ui->labelStreamingAnimation->setHidden(
true);
1672 if (
ui->buttonStreamingPause->isChecked())
1675 ui->buttonStreamingPause->setChecked(
false);
1691 ui->actionDeleteAllData->setToolTip(
"");
1708 qDebug() <<
"Error, no streamer loaded";
1719 qDebug() <<
"Error. The streamer " << streamer_name <<
" can't be loaded";
1724 bool started =
false;
1730 catch (std::runtime_error& err)
1732 QMessageBox::warning(
1733 this, tr(
"Exception from the plugin"),
1734 tr(
"The plugin thrown the following exception: \n\n %1\n").
arg(err.what()));
1747 ui->actionClearBuffer->setEnabled(
true);
1748 ui->actionDeleteAllData->setToolTip(
"Stop streaming to be able to delete the data");
1750 ui->buttonStreamingStart->setText(
"Stop");
1751 ui->buttonStreamingPause->setEnabled(
true);
1752 ui->buttonStreamingPause->setChecked(
false);
1753 ui->comboStreaming->setEnabled(
false);
1754 ui->labelStreamingAnimation->setHidden(
false);
1763 QSignalBlocker
block(
ui->buttonStreamingStart);
1764 ui->buttonStreamingStart->setChecked(
false);
1765 qDebug() <<
"Failed to launch the streamer";
1772 ui->buttonStreamingNotifications->setEnabled(enabled);
1775 QString theme = settings.value(
"Preferences::theme",
"light").toString();
1779 ui->buttonStreamingNotifications->setIcon(
1780 LoadSvg(
":/resources/svg/alarm-bell-active.svg", theme));
1784 ui->buttonStreamingNotifications->setIcon(
1785 LoadSvg(
":/resources/svg/alarm-bell.svg", theme));
1791 ui->statusLabel->setText(message);
1792 ui->widgetStatusBar->setHidden(message.isEmpty());
1793 QTimer::singleShot(7000,
this, [
this]() {
ui->widgetStatusBar->setHidden(
true); });
1798 QFile styleFile(file_path);
1799 styleFile.open(QFile::ReadOnly);
1809 catch (std::runtime_error& err)
1811 QMessageBox::warning(
this, tr(
"Error loading StyleSheet"), tr(err.what()));
1824 std::unordered_set<std::string> updated_curves;
1826 bool curve_added =
false;
1829 if (
auto reactive_function =
1830 std::dynamic_pointer_cast<PJ::ReactiveLuaFunction>(it.second))
1833 reactive_function->calculate();
1835 for (
auto& name : reactive_function->createdCurves())
1838 updated_curves.insert(name);
1850 if (updated_curves.count(curve.src_name) != 0)
1860 if (event->mimeData()->hasUrls())
1862 event->acceptProposedAction();
1868 QStringList file_names;
1869 const auto urls =
event->mimeData()->urls();
1871 for (
const auto& url : urls)
1873 file_names << QDir::toNativeSeparators(url.toLocalFile());
1881 ui->buttonLoadDatafile->setIcon(
LoadSvg(
":/resources/svg/import.svg", theme));
1882 ui->buttonStreamingPause->setIcon(
LoadSvg(
":/resources/svg/pause.svg", theme));
1883 if (
ui->buttonStreamingNotifications->isEnabled())
1885 ui->buttonStreamingNotifications->setIcon(
1886 LoadSvg(
":/resources/svg/alarm-bell-active.svg", theme));
1890 ui->buttonStreamingNotifications->setIcon(
1891 LoadSvg(
":/resources/svg/alarm-bell.svg", theme));
1893 ui->buttonRecentData->setIcon(
LoadSvg(
":/resources/svg/right-arrow.svg", theme));
1894 ui->buttonRecentLayout->setIcon(
LoadSvg(
":/resources/svg/right-arrow.svg", theme));
1896 ui->buttonZoomOut->setIcon(
LoadSvg(
":/resources/svg/zoom_max.svg", theme));
1897 ui->playbackLoop->setIcon(
LoadSvg(
":/resources/svg/loop.svg", theme));
1898 ui->buttonPlay->setIcon(
LoadSvg(
":/resources/svg/play_arrow.svg", theme));
1899 ui->buttonUseDateTime->setIcon(
LoadSvg(
":/resources/svg/datetime.svg", theme));
1900 ui->buttonActivateGrid->setIcon(
LoadSvg(
":/resources/svg/grid.svg", theme));
1901 ui->buttonRatio->setIcon(
LoadSvg(
":/resources/svg/ratio.svg", theme));
1903 ui->buttonLoadLayout->setIcon(
LoadSvg(
":/resources/svg/import.svg", theme));
1904 ui->buttonSaveLayout->setIcon(
LoadSvg(
":/resources/svg/export.svg", theme));
1906 ui->buttonLink->setIcon(
LoadSvg(
":/resources/svg/link.svg", theme));
1907 ui->buttonRemoveTimeOffset->setIcon(
LoadSvg(
":/resources/svg/t0.svg", theme));
1908 ui->buttonLegend->setIcon(
LoadSvg(
":/resources/svg/legend.svg", theme));
1910 ui->buttonStreamingOptions->setIcon(
LoadSvg(
":/resources/svg/settings_cog.svg", theme));
1915 QDomElement plugins = root.firstChildElement(
"Plugins");
1917 for (QDomElement plugin_elem = plugins.firstChildElement();
1918 plugin_elem.isNull() ==
false; plugin_elem = plugin_elem.nextSiblingElement())
1920 const QString plugin_name = plugin_elem.attribute(
"ID");
1922 if (plugin_elem.nodeName() !=
"plugin" || plugin_name.isEmpty())
1924 QMessageBox::warning(
this, tr(
"Error loading Plugin State from Layout"),
1925 tr(
"The method xmlSaveState() must return a node like this "
1926 "<plugin ID=\"PluginName\" "));
1939 _toolboxes[plugin_name]->xmlLoadState(plugin_elem);
1944 publisher->xmlLoadState(plugin_elem);
1948 publisher->setEnabled(
true);
1956 QDomElement list_plugins = doc.createElement(
"Plugins");
1958 auto AddPlugins = [&](
auto& plugins) {
1959 for (
auto& [name, plugin] : plugins)
1961 QDomElement elem = plugin->xmlSaveState(doc);
1962 list_plugins.appendChild(elem);
1973 const auto& state_publisher = it.second;
1974 QDomElement plugin_elem = state_publisher->xmlSaveState(doc);
1975 plugin_elem.setAttribute(
"status", state_publisher->enabled() ?
"active" :
"idle");
1978 return list_plugins;
1984 double min_time = std::numeric_limits<double>::max();
1985 double max_time = std::numeric_limits<double>::lowest();
1991 const auto& curve_name = it.src_name;
1993 auto plot_it = _mapped_plot_data.numeric.find(curve_name);
1994 if (plot_it == _mapped_plot_data.numeric.end())
1998 const auto&
data = plot_it->second;
1999 if (
data.size() >= 1)
2001 const double t0 = data.front().x;
2002 const double t1 = data.back().x;
2003 min_time = std::min(min_time, t0);
2004 max_time = std::max(max_time, t1);
2005 max_steps = std::max(max_steps, (int)data.size());
2011 if (max_steps == 0 || max_time < min_time)
2013 for (
const auto& it : _mapped_plot_data.numeric)
2016 if (
data.size() >= 1)
2018 const double t0 =
data.front().x;
2019 const double t1 =
data.back().x;
2020 min_time = std::min(min_time, t0);
2021 max_time = std::max(max_time, t1);
2022 max_steps = std::max(max_steps, (
int)
data.size());
2028 if (max_steps == 0 || max_time < min_time)
2034 return std::tuple<double, double, int>(min_time, max_time, max_steps);
2041 QFile file(filename);
2042 if (!file.open(QFile::ReadOnly | QFile::Text))
2044 QMessageBox::warning(
2046 tr(
"Cannot read file %1:\n%2.").
arg(filename).
arg(file.errorString()));
2051 int errorLine, errorColumn;
2053 QDomDocument domDocument;
2055 if (!domDocument.setContent(&file,
true, &errorStr, &errorLine, &errorColumn))
2057 QMessageBox::information(
2058 window(), tr(
"XML Layout"),
2059 tr(
"Parse error at line %1:\n%2").
arg(errorLine).
arg(errorStr));
2065 QDomElement root = domDocument.namedItem(
"root").toElement();
2069 QDomElement previously_loaded_datafile = root.firstChildElement(
"previouslyLoaded_"
2072 QDomElement datafile_elem = previously_loaded_datafile.firstChildElement(
"fileInfo");
2073 while (!datafile_elem.isNull())
2075 QString datafile_path = datafile_elem.attribute(
"filename");
2076 if (QDir(datafile_path).isRelative())
2078 QDir layout_directory = QFileInfo(filename).absoluteDir();
2079 QString new_path = layout_directory.filePath(datafile_path);
2080 datafile_path = QFileInfo(new_path).absoluteFilePath();
2085 info.
prefix = datafile_elem.attribute(
"prefix");
2087 auto plugin_elem = datafile_elem.firstChildElement(
"plugin");
2091 datafile_elem = datafile_elem.nextSiblingElement(
"fileInfo");
2094 QDomElement previous_streamer = root.firstChildElement(
"previouslyLoaded_Streamer");
2095 if (!previous_streamer.isNull())
2097 QString streamer_name = previous_streamer.attribute(
"name");
2099 QMessageBox msgBox(
this);
2100 msgBox.setWindowTitle(
"Start Streaming?");
2102 tr(
"Start the previously used streaming plugin?\n\n %1 \n\n").
arg(streamer_name));
2103 QPushButton* yes = msgBox.addButton(tr(
"Yes"), QMessageBox::YesRole);
2104 QPushButton* no = msgBox.addButton(tr(
"No"), QMessageBox::RejectRole);
2105 msgBox.setDefaultButton(yes);
2108 if (msgBox.clickedButton() == yes)
2115 for (
auto curve_name : allCurves)
2117 std::string curve_str = curve_name.toStdString();
2128 QMessageBox::warning(
2129 this, tr(
"Error Loading Streamer"),
2130 tr(
"The streamer named %1 can not be loaded.").
arg(streamer_name));
2136 QDomElement plugins = root.firstChildElement(
"Plugins");
2140 for (QDomElement plugin_elem = plugins.firstChildElement();
2141 plugin_elem.isNull() ==
false; plugin_elem = plugin_elem.nextSiblingElement())
2143 const QString plugin_name = plugin_elem.nodeName();
2148 if (plugin_elem.attribute(
"status") ==
"active")
2150 publisher->setEnabled(
true);
2156 auto custom_equations = root.firstChildElement(
"customMathEquations");
2158 if (!custom_equations.isNull())
2160 using SnippetPair = std::pair<SnippetData, QDomElement>;
2161 std::vector<SnippetPair> snippets;
2163 for (QDomElement custom_eq = custom_equations.firstChildElement(
"snippet");
2164 custom_eq.isNull() ==
false; custom_eq = custom_eq.nextSiblingElement(
"snippet"))
2170 auto DependOn = [](
const SnippetPair& a,
const SnippetPair& b) {
2171 if (b.first.linked_source == a.first.alias_name)
2175 for (
const auto&
source : b.first.additional_sources)
2177 if (
source == a.first.alias_name)
2184 std::sort(snippets.begin(), snippets.end(), DependOn);
2186 for (
const auto& [snippet, custom_eq] : snippets)
2190 CustomPlotPtr new_custom_plot = std::make_shared<LuaCustomFunction>(snippet);
2191 new_custom_plot->xmlLoadState(custom_eq);
2194 const auto& alias_name = new_custom_plot->aliasName();
2199 catch (std::runtime_error& err)
2201 QMessageBox::warning(
this, tr(
"Exception"),
2202 tr(
"Failed to load customMathEquation [%1] \n\n %2\n")
2203 .
arg(snippet.alias_name)
2210 auto colormaps = root.firstChildElement(
"colorMaps");
2212 if (!colormaps.isNull())
2214 for (
auto colormap = colormaps.firstChildElement(
"colorMap");
2215 colormap.isNull() ==
false; colormap = colormap.nextSiblingElement(
"colorMap"))
2217 QString name = colormap.attribute(
"name");
2222 QByteArray snippets_saved_xml =
2223 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
2225 auto snippets_element = root.firstChildElement(
"snippets");
2226 if (!snippets_element.isNull())
2231 bool snippets_are_different =
false;
2232 for (
const auto& snippet_it : snippets_layout)
2234 auto prev_it = snippets_previous.find(snippet_it.first);
2236 if (prev_it == snippets_previous.end() ||
2237 prev_it->second.function != snippet_it.second.function ||
2238 prev_it->second.global_vars != snippet_it.second.global_vars)
2240 snippets_are_different =
true;
2245 if (snippets_are_different)
2247 QMessageBox msgBox(
this);
2248 msgBox.setWindowTitle(
"Overwrite custom transforms?");
2249 msgBox.setText(
"Your layout file contains a set of custom transforms different "
2251 "the last one you used.\nWant to load these transformations?");
2252 msgBox.addButton(QMessageBox::No);
2253 msgBox.addButton(QMessageBox::Yes);
2254 msgBox.setDefaultButton(QMessageBox::Yes);
2256 if (msgBox.exec() == QMessageBox::Yes)
2258 for (
const auto& snippet_it : snippets_layout)
2260 snippets_previous[snippet_it.first] = snippet_it.second;
2263 auto snippets_root_element =
ExportSnippets(snippets_previous, doc);
2264 doc.appendChild(snippets_root_element);
2265 settings.setValue(
"AddCustomPlotDialog.savedXML", doc.toByteArray(2));
2283 if (
ui->buttonLink->isChecked())
2287 auto tabs = it.second->tabWidget();
2288 for (
int t = 0; t < tabs->count(); t++)
2295 for (
int index = 0; index < matrix->plotCount(); index++)
2306 range.
min = rect.left();
2307 range.
max = rect.right();
2312 range.
min = std::min(rect.left(), range.
min);
2313 range.
max = std::max(rect.right(), range.
max);
2317 for (
int index = 0; index < matrix->plotCount() && !
first; index++)
2325 bound_act.setLeft(range.
min);
2326 bound_act.setRight(range.
max);
2348 auto func = [&](QTabWidget* tabs) {
2349 for (
int t = 0; t < tabs->count(); t++)
2357 for (
int index = 0; index < matrix->
plotCount(); index++)
2360 operation(plot, matrix, index);
2367 func(it.second->tabWidget());
2380 ui->timeSlider->setLimits(std::get<0>(range), std::get<1>(range), std::get<2>(range));
2389 double min_time = std::get<0>(range);
2391 const bool remove_offset =
ui->buttonRemoveTimeOffset->isChecked();
2392 if (remove_offset && min_time != std::numeric_limits<double>::max())
2425 if (
ui->streamingSpinBox->value() ==
ui->streamingSpinBox->maximum())
2438 std::vector<TransformFunction*> transforms;
2442 transforms.push_back(
function.
get());
2445 transforms.begin(), transforms.end(),
2452 for (
auto&
function : transforms)
2456 function->calculate();
2464 if (is_streaming_active)
2467 double max_time = std::get<1>(range);
2482 double real_value = value;
2484 if (value ==
ui->streamingSpinBox->maximum())
2486 real_value = std::numeric_limits<double>::max();
2487 ui->streamingSpinBox->setStyleSheet(
"QSpinBox { color: red; }");
2488 ui->streamingSpinBox->setSuffix(
"=inf");
2492 ui->streamingSpinBox->setStyleSheet(
"QSpinBox { color: black; }");
2493 ui->streamingSpinBox->setSuffix(
" sec");
2521 if (this->signalsBlocked() ==
false)
2529 QLineEdit* timeLine =
ui->displayTime;
2531 if (
ui->buttonUseDateTime->isChecked())
2533 if (
ui->buttonRemoveTimeOffset->isChecked())
2535 QTime
time = QTime::fromMSecsSinceStartOfDay(std::round(relative_time * 1000.0));
2536 timeLine->setText(
time.toString(
"HH:mm::ss.zzz"));
2540 QDateTime datetime =
2541 QDateTime::fromMSecsSinceEpoch(std::round(
_tracker_time * 1000.0));
2542 timeLine->setText(datetime.toString(
"[yyyy MMM dd] HH:mm::ss.zzz"));
2547 timeLine->setText(QString::number(relative_time,
'f', 3));
2550 QFontMetrics fm(timeLine->font());
2551 int width = fm.width(timeLine->text()) + 10;
2552 timeLine->setFixedWidth(std::max(100, width));
2614 std::vector<std::string> names;
2616 auto AddFromGroup = [&](
auto& series) {
2617 for (
auto& it : series)
2619 const auto& group = it.second.group();
2620 if (group && group->name() == group_name)
2622 names.push_back(it.first);
2639 QString tooltipText = QString(
"%1 has %2 outstanding notitication%3")
2642 .arg(active_count > 1 ?
"s" :
"");
2643 ui->buttonStreamingNotifications->setToolTip(tooltipText);
2648 ui->buttonStreamingNotifications->setToolTip(
"View streaming alerts");
2654 static bool first =
true;
2655 if (checked &&
ui->buttonRemoveTimeOffset->isChecked())
2659 QMessageBox::information(
this, tr(
"Note"),
2660 tr(
"When \"Use Date Time\" is checked, the option "
2661 "\"Remove Time Offset\" "
2662 "is automatically disabled.\n"
2663 "This message will be shown only once."));
2666 ui->buttonRemoveTimeOffset->setChecked(
false);
2704 settings.setValue(
"MainWindow.geometry", saveGeometry());
2705 settings.setValue(
"MainWindow.state", saveState());
2707 settings.setValue(
"MainWindow.activateGrid",
ui->buttonActivateGrid->isChecked());
2708 settings.setValue(
"MainWindow.removeTimeOffset",
2709 ui->buttonRemoveTimeOffset->isChecked());
2710 settings.setValue(
"MainWindow.dateTimeDisplay",
ui->buttonUseDateTime->isChecked());
2711 settings.setValue(
"MainWindow.buttonLink",
ui->buttonLink->isChecked());
2712 settings.setValue(
"MainWindow.buttonRatio",
ui->buttonRatio->isChecked());
2714 settings.setValue(
"MainWindow.streamingBufferValue",
ui->streamingSpinBox->value());
2715 settings.setValue(
"MainWindow.timeTrackerSetting", (
int)
_tracker_param);
2716 settings.setValue(
"MainWindow.splitterWidth",
ui->mainSplitter->sizes()[0]);
2726 ui->widgetStack->setCurrentIndex(1);
2733 ui->widgetStack->setCurrentIndex(1);
2737 qWarning(
"failed to find custom equation");
2741 std::dynamic_pointer_cast<LuaCustomFunction>(custom_it->second));
2751 qWarning(
"failed to find custom equation");
2754 CustomPlotPtr ce = std::dynamic_pointer_cast<LuaCustomFunction>(custom_it->second);
2760 catch (
const std::runtime_error& e)
2762 QMessageBox::critical(
this,
"error",
2763 "Failed to refresh data : " + QString::fromStdString(e.what()));
2772 delta_ms = std::max((qint64)
_publish_timer->interval(), delta_ms);
2777 if (!
ui->playbackLoop->isChecked())
2779 ui->buttonPlay->setChecked(
false);
2784 auto prev =
ui->timeSlider->blockSignals(
true);
2786 ui->timeSlider->blockSignals(prev);
2806 std::set<PlotWidget*> widget_to_replot;
2808 for (
auto custom_plot : custom_plots)
2810 const std::string& curve_name = custom_plot->aliasName().toStdString();
2815 data_it->second.clear();
2821 catch (std::exception& ex)
2823 QMessageBox::warning(
this, tr(
"Warning"),
2824 tr(
"Failed to create the custom timeseries. "
2838 custom_it->second = custom_plot;
2844 widget_to_replot.insert(plot);
2850 ui->widgetStack->setCurrentIndex(0);
2853 for (
auto plot : widget_to_replot)
2855 plot->updateCurves(
true);
2863 QDesktopServices::openUrl(QUrl(
"https://github.com/facontidavide/PlotJuggler/issues"));
2868 QDesktopServices::openUrl(QUrl(
"https://twitter.com/intent/"
2869 "tweet?hashtags=PlotJuggler"));
2874 QDialog* dialog =
new QDialog(
this);
2875 auto ui =
new Ui::AboutDialog();
2876 ui->setupUi(dialog);
2878 ui->label_version->setText(QString(
"version: ") + QApplication::applicationVersion());
2879 dialog->setAttribute(Qt::WA_DeleteOnClose);
2881 QFile fileTitle(
_skin_path +
"/about_window_title.html");
2882 if (fileTitle.open(QIODevice::ReadOnly))
2884 ui->titleTextBrowser->setHtml(fileTitle.readAll());
2887 QFile fileBody(
_skin_path +
"/about_window_body.html");
2888 if (fileBody.open(QIODevice::ReadOnly))
2890 ui->bodyTextBrowser->setHtml(fileBody.readAll());
2893 dialog->setAttribute(Qt::WA_DeleteOnClose);
2902 dialog->restoreGeometry(settings.value(
"Cheatsheet.geometry").toByteArray());
2904 settings.setValue(
"Cheatsheet.geometry", dialog->saveGeometry());
2905 dialog->deleteLater();
2910 QDialog* dialog =
new QDialog(
this);
2911 auto ui =
new Ui::SupportDialog();
2912 ui->setupUi(dialog);
2914 dialog->setAttribute(Qt::WA_DeleteOnClose);
3002 QMessageBox::warning(
this, tr(
"Warning"),
3003 tr(
"No plugin was loaded to process a data file\n"));
3009 QString file_extension_filter;
3011 std::set<QString> extensions;
3016 for (QString extension : loader->compatibleFileExtensions())
3018 extensions.insert(extension.toLower());
3022 for (
const auto& it : extensions)
3024 file_extension_filter.append(QString(
" *.") + it);
3027 QString directory_path =
3028 settings.value(
"MainWindow.lastDatafileDirectory", QDir::currentPath()).toString();
3030 QFileDialog loadDialog(
this);
3031 loadDialog.setFileMode(QFileDialog::ExistingFiles);
3032 loadDialog.setViewMode(QFileDialog::Detail);
3033 loadDialog.setNameFilter(file_extension_filter);
3034 loadDialog.setDirectory(directory_path);
3036 QStringList fileNames;
3037 if (loadDialog.exec())
3039 fileNames = loadDialog.selectedFiles();
3042 if (fileNames.isEmpty())
3047 directory_path = QFileInfo(fileNames[0]).absolutePath();
3048 settings.setValue(
"MainWindow.lastDatafileDirectory", directory_path);
3060 QString directory_path =
3061 settings.value(
"MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3063 QFileDialog::getOpenFileName(
this,
"Open Layout", directory_path,
"*.xml");
3064 if (filename.isEmpty())
3074 directory_path = QFileInfo(filename).absolutePath();
3075 settings.setValue(
"MainWindow.lastLayoutDirectory", directory_path);
3084 QString directory_path =
3085 settings.value(
"MainWindow.lastLayoutDirectory", QDir::currentPath()).toString();
3087 QFileDialog saveDialog(
this);
3088 saveDialog.setOption(QFileDialog::DontUseNativeDialog,
true);
3090 QGridLayout* save_layout =
static_cast<QGridLayout*
>(saveDialog.layout());
3092 QFrame* frame =
new QFrame;
3093 frame->setFrameStyle(QFrame::Box | QFrame::Plain);
3094 frame->setLineWidth(1);
3096 QVBoxLayout* vbox =
new QVBoxLayout;
3097 QLabel* title =
new QLabel(
"Save Layout options");
3098 QFrame* separator =
new QFrame;
3099 separator->setFrameStyle(QFrame::HLine | QFrame::Plain);
3101 auto checkbox_datasource =
new QCheckBox(
"Save data source");
3102 checkbox_datasource->setToolTip(
"the layout will remember the source of your data,\n"
3103 "i.e. the Datafile used or the Streaming Plugin loaded "
3105 checkbox_datasource->setFocusPolicy(Qt::NoFocus);
3106 checkbox_datasource->setChecked(
3107 settings.value(
"MainWindow.saveLayoutDataSource",
true).toBool());
3109 auto checkbox_snippets =
new QCheckBox(
"Save Scripts (transforms and colormaps)");
3110 checkbox_snippets->setToolTip(
"Do you want the layout to save your Lua scripts?");
3111 checkbox_snippets->setFocusPolicy(Qt::NoFocus);
3112 checkbox_snippets->setChecked(
3113 settings.value(
"MainWindow.saveLayoutSnippets",
true).toBool());
3115 vbox->addWidget(title);
3116 vbox->addWidget(separator);
3117 vbox->addWidget(checkbox_datasource);
3118 vbox->addWidget(checkbox_snippets);
3119 frame->setLayout(vbox);
3121 int rows = save_layout->rowCount();
3122 int col = save_layout->columnCount();
3123 save_layout->addWidget(frame, 0, col, rows, 1, Qt::AlignTop);
3125 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
3126 saveDialog.setDefaultSuffix(
"xml");
3127 saveDialog.setNameFilter(
"XML (*.xml)");
3128 saveDialog.setDirectory(directory_path);
3131 if (saveDialog.result() != QDialog::Accepted || saveDialog.selectedFiles().empty())
3136 QString fileName = saveDialog.selectedFiles().first();
3138 if (fileName.isEmpty())
3143 directory_path = QFileInfo(fileName).absolutePath();
3144 settings.setValue(
"MainWindow.lastLayoutDirectory", directory_path);
3145 settings.setValue(
"MainWindow.saveLayoutDataSource", checkbox_datasource->isChecked());
3146 settings.setValue(
"MainWindow.saveLayoutSnippets", checkbox_snippets->isChecked());
3148 QDomElement root = doc.namedItem(
"root").toElement();
3150 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3152 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3156 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3158 if (checkbox_datasource->isChecked())
3160 QDomElement loaded_list = doc.createElement(
"previouslyLoaded_Datafiles");
3164 QString loaded_datafile = QDir(directory_path).relativeFilePath(loaded.filename);
3166 QDomElement file_elem = doc.createElement(
"fileInfo");
3167 file_elem.setAttribute(
"filename", loaded_datafile);
3168 file_elem.setAttribute(
"prefix", loaded.prefix);
3170 file_elem.appendChild(loaded.plugin_config.firstChild());
3171 loaded_list.appendChild(file_elem);
3173 root.appendChild(loaded_list);
3177 QDomElement loaded_streamer = doc.createElement(
"previouslyLoaded_Streamer");
3179 loaded_streamer.setAttribute(
"name", streamer_name);
3180 root.appendChild(loaded_streamer);
3184 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3185 if (checkbox_snippets->isChecked())
3187 QDomElement custom_equations = doc.createElement(
"customMathEquations");
3190 const auto& custom_plot = custom_it.second;
3191 custom_plot->xmlSaveState(doc, custom_equations);
3193 root.appendChild(custom_equations);
3195 QByteArray snippets_xml_text =
3196 settings.value(
"AddCustomPlotDialog.savedXML", QByteArray()).toByteArray();
3199 root.appendChild(snippets_root);
3201 QDomElement color_maps = doc.createElement(
"colorMaps");
3204 QString colormap_name = it.first;
3205 QDomElement colormap = doc.createElement(
"colorMap");
3206 QDomText colormap_script = doc.createTextNode(it.second->script());
3207 colormap.setAttribute(
"name", colormap_name);
3208 colormap.appendChild(colormap_script);
3209 color_maps.appendChild(colormap);
3212 root.appendChild(doc.createComment(
" - - - - - - - - - - - - - - "));
3214 QFile file(fileName);
3215 if (file.open(QIODevice::WriteOnly))
3217 QTextStream stream(&file);
3218 stream << doc.toString() <<
"\n";
3224 static bool first_call =
true;
3228 QMessageBox::information(
this,
"Remember!",
3229 "Press F10 to switch back to the normal view");
3247 for (QAction* action : menu->actions())
3249 if (action->isSeparator())
3253 menu->removeAction(action);
3255 menu->setEnabled(
false);
3257 settings.setValue(
"MainWindow.recentlyLoadedDatafile", {});
3263 for (QAction* action : menu->actions())
3265 if (action->isSeparator())
3269 menu->removeAction(action);
3271 menu->setEnabled(
false);
3273 settings.setValue(
"MainWindow.recentlyLoadedLayout", {});
3278 QMessageBox msgBox(
this);
3279 msgBox.setWindowTitle(
"Warning. Can't be undone.");
3280 msgBox.setText(tr(
"Do you want to remove the previously loaded data?\n"));
3281 msgBox.addButton(QMessageBox::No);
3282 msgBox.addButton(QMessageBox::Yes);
3283 msgBox.setDefaultButton(QMessageBox::Yes);
3284 auto reply = msgBox.exec();
3286 if (reply == QMessageBox::No)
3297 QString prev_style = settings.value(
"Preferences::theme",
"light").toString();
3302 QString theme = settings.value(
"Preferences::theme").toString();
3304 if (!theme.isEmpty() && theme != prev_style)
3312 ui->timeSlider->setFocus();
3313 ui->timeSlider->setRealStepValue(step);
3319 QString directory_path =
3320 settings.value(
"MainWindow.loadStyleSheetDirectory", QDir::currentPath())
3323 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load StyleSheet"),
3324 directory_path, tr(
"(*.qss)"));
3325 if (fileName.isEmpty())
3332 directory_path = QFileInfo(fileName).absolutePath();
3333 settings.setValue(
"MainWindow.loadStyleSheetDirectory", directory_path);
3340 case LabelStatus::LEFT:
3343 case LabelStatus::RIGHT:
3346 case LabelStatus::HIDDEN:
3356 plot->setLegendAlignment(Qt::AlignLeft);
3360 plot->setLegendAlignment(Qt::AlignRight);
3377 settings.setValue(
"MainWindow.previousStreamingPlugin", current_text);
3379 ui->buttonStreamingOptions->setEnabled(!streamer->availableActions().empty());
3381 std::pair<QAction*, int> notifications_pair = streamer->notificationAction();
3382 if (notifications_pair.first ==
nullptr)
3384 ui->buttonStreamingNotifications->setEnabled(
false);
3394 ui->buttonStreamingStart->setEnabled(
false);
3395 if (
ui->buttonStreamingStart->text() ==
"Start")
3403 ui->buttonStreamingStart->setEnabled(
true);
3407 : QMenu(parent), _w(relative_widget)
3413 QPoint p =
_w->mapToGlobal({});
3414 QRect geo =
_w->geometry();
3415 this->
move(p.x() + geo.width(), p.y());
3425 _w->setAttribute(Qt::WA_UnderMouse,
false);
3434 menu->addAction(action);
3448 for (
auto action : streamer->availableActions())
3450 menu->addAction(action);
3457 bool hidden = !
ui->frameFile->isHidden();
3458 ui->buttonHideFileFrame->setText(hidden ?
"+" :
" -");
3459 ui->frameFile->setHidden(hidden);
3462 settings.setValue(
"MainWindow.hiddenFileFrame", hidden);
3467 bool hidden = !
ui->frameStreaming->isHidden();
3468 ui->buttonHideStreamingFrame->setText(hidden ?
"+" :
" -");
3469 ui->frameStreaming->setHidden(hidden);
3472 settings.setValue(
"MainWindow.hiddenStreamingFrame", hidden);
3477 bool hidden = !
ui->framePublishers->isHidden();
3478 ui->buttonHidePublishersFrame->setText(hidden ?
"+" :
" -");
3479 ui->framePublishers->setHidden(hidden);
3482 settings.setValue(
"MainWindow.hiddenPublishersFrame", hidden);
3491 menu->addAction(action);
3500 QStringList level_names = {
"tabbed_widget",
"Tab",
"Container",
"DockSplitter",
3501 "DockArea",
"plot",
"curve" };
3503 std::function<
void(
int, QDomElement)> recursiveXmlStream;
3504 recursiveXmlStream = [&](
int level, QDomElement parent_elem) {
3505 QString level_name = level_names[level];
3506 for (
auto elem = parent_elem.firstChildElement(level_name); elem.isNull() ==
false;
3507 elem = elem.nextSiblingElement(level_name))
3509 if (level_name ==
"curve")
3511 curves.push_back(elem.attribute(
"name"));
3515 recursiveXmlStream(level + 1, elem);
3521 recursiveXmlStream(0, root_node);
3536 for (
const auto& info : prev_infos)
3545 ui->widgetStatusBar->hide();