From 011b70ee4968104cc618a2de0e151b03c12238bf Mon Sep 17 00:00:00 2001
From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com>
Date: Thu, 4 May 2023 12:20:53 +0200
Subject: [PATCH] Mon GUI: Added Raw Data Widget (#1088)
The Raw Data Widget displays the eCAL Monitoring Information in textual form. The Protobuf `DebugString()` is used for generating the text. Therefore, the Raw Data Widget will never need to be adapted, even if we add / remove elements from the monitoring layer.
Features:
- Display raw text (monospace)
- Syntax highlighting (different ones for dark- and light mode)
- Save-to-file
- Full text search with result highlighting and keyboard shortcuts
---
app/iconset/FlorianReimold/arrow_down.svg | 69 ++++
app/iconset/FlorianReimold/arrow_left.svg | 69 ++++
app/iconset/FlorianReimold/arrow_right.svg | 69 ++++
app/iconset/FlorianReimold/arrow_up.svg | 69 ++++
app/iconset/ecalicons.qrc | 8 +
app/iconset/lightmode/arrow_down.svg | 69 ++++
app/iconset/lightmode/arrow_left.svg | 69 ++++
app/iconset/lightmode/arrow_right.svg | 69 ++++
app/iconset/lightmode/arrow_up.svg | 69 ++++
app/mon/mon_gui/.clang-tidy | 8 +
app/mon/mon_gui/CMakeLists.txt | 10 +-
app/mon/mon_gui/src/ecalmon.cpp | 29 +-
app/mon/mon_gui/src/ecalmon.h | 2 +
app/mon/mon_gui/src/main_window.ui | 42 ++-
.../protobuf_highlighter.cpp | 94 ++++++
.../protobuf_highlighter.h | 63 ++++
.../raw_monitoring_data_widget.cpp | 296 ++++++++++++++++++
.../raw_monitoring_data_widget.h | 83 +++++
.../raw_monitoring_data_widget.ui | 163 ++++++++++
.../search_lineedit.cpp | 161 ++++++++++
.../search_lineedit.h | 126 ++++++++
21 files changed, 1621 insertions(+), 16 deletions(-)
create mode 100644 app/iconset/FlorianReimold/arrow_down.svg
create mode 100644 app/iconset/FlorianReimold/arrow_left.svg
create mode 100644 app/iconset/FlorianReimold/arrow_right.svg
create mode 100644 app/iconset/FlorianReimold/arrow_up.svg
create mode 100644 app/iconset/lightmode/arrow_down.svg
create mode 100644 app/iconset/lightmode/arrow_left.svg
create mode 100644 app/iconset/lightmode/arrow_right.svg
create mode 100644 app/iconset/lightmode/arrow_up.svg
create mode 100644 app/mon/mon_gui/.clang-tidy
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.cpp
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.h
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.cpp
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.ui
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.cpp
create mode 100644 app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.h
diff --git a/app/iconset/FlorianReimold/arrow_down.svg b/app/iconset/FlorianReimold/arrow_down.svg
new file mode 100644
index 0000000000..f22801157c
--- /dev/null
+++ b/app/iconset/FlorianReimold/arrow_down.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/FlorianReimold/arrow_left.svg b/app/iconset/FlorianReimold/arrow_left.svg
new file mode 100644
index 0000000000..da2a24cb77
--- /dev/null
+++ b/app/iconset/FlorianReimold/arrow_left.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/FlorianReimold/arrow_right.svg b/app/iconset/FlorianReimold/arrow_right.svg
new file mode 100644
index 0000000000..05018ed853
--- /dev/null
+++ b/app/iconset/FlorianReimold/arrow_right.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/FlorianReimold/arrow_up.svg b/app/iconset/FlorianReimold/arrow_up.svg
new file mode 100644
index 0000000000..c514fde170
--- /dev/null
+++ b/app/iconset/FlorianReimold/arrow_up.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/ecalicons.qrc b/app/iconset/ecalicons.qrc
index 297bd044ef..b91124d021 100644
--- a/app/iconset/ecalicons.qrc
+++ b/app/iconset/ecalicons.qrc
@@ -95,8 +95,16 @@
FlorianReimold/add_file.svg
MuraruMihai/show.svg
MuraruMihai/hide.svg
+ FlorianReimold/arrow_left.svg
+ FlorianReimold/arrow_right.svg
+ FlorianReimold/arrow_down.svg
+ FlorianReimold/arrow_up.svg
darkmode/console.svg
+ lightmode/arrow_down.svg
+ lightmode/arrow_left.svg
+ lightmode/arrow_right.svg
+ lightmode/arrow_up.svg
diff --git a/app/iconset/lightmode/arrow_down.svg b/app/iconset/lightmode/arrow_down.svg
new file mode 100644
index 0000000000..0302b04c2a
--- /dev/null
+++ b/app/iconset/lightmode/arrow_down.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/lightmode/arrow_left.svg b/app/iconset/lightmode/arrow_left.svg
new file mode 100644
index 0000000000..c480de70f2
--- /dev/null
+++ b/app/iconset/lightmode/arrow_left.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/lightmode/arrow_right.svg b/app/iconset/lightmode/arrow_right.svg
new file mode 100644
index 0000000000..221c70e294
--- /dev/null
+++ b/app/iconset/lightmode/arrow_right.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/iconset/lightmode/arrow_up.svg b/app/iconset/lightmode/arrow_up.svg
new file mode 100644
index 0000000000..0eafac9d34
--- /dev/null
+++ b/app/iconset/lightmode/arrow_up.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/app/mon/mon_gui/.clang-tidy b/app/mon/mon_gui/.clang-tidy
new file mode 100644
index 0000000000..baa40c6f51
--- /dev/null
+++ b/app/mon/mon_gui/.clang-tidy
@@ -0,0 +1,8 @@
+---
+# This disables the owning-memory check (warn about the usage of "new").
+# The architecture of Qt requires the massive usage of raw pointers.
+
+Checks: "-cppcoreguidelines-owning-memory,
+"
+
+InheritParentConfig: true
\ No newline at end of file
diff --git a/app/mon/mon_gui/CMakeLists.txt b/app/mon/mon_gui/CMakeLists.txt
index 0f62c46012..5c0af5f981 100644
--- a/app/mon/mon_gui/CMakeLists.txt
+++ b/app/mon/mon_gui/CMakeLists.txt
@@ -35,8 +35,6 @@ set(CMAKE_AUTORCC OFF) # Reason for being turned off: AutoRCC will create an ent
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(source_files
- #src/convert_utf.cpp
- #src/convert_utf.h
src/ecalmon.cpp
src/ecalmon.h
src/ecalmon_globals.h
@@ -112,6 +110,13 @@ set(source_files
src/widgets/plugin_settings_dialog/plugin_settings_dialog.cpp
src/widgets/plugin_settings_dialog/plugin_settings_dialog.h
+ src/widgets/raw_monitoring_data_widget/protobuf_highlighter.cpp
+ src/widgets/raw_monitoring_data_widget/protobuf_highlighter.h
+ src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.cpp
+ src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h
+ src/widgets/raw_monitoring_data_widget/search_lineedit.cpp
+ src/widgets/raw_monitoring_data_widget/search_lineedit.h
+
src/widgets/visualisation_widget/visualisation_widget.cpp
src/widgets/visualisation_widget/visualisation_widget.h
src/widgets/visualisation_widget/visualisation_window.cpp
@@ -145,6 +150,7 @@ set(ui_files
src/widgets/license_dialog/license_dialog.ui
src/widgets/log_widget/log_widget.ui
src/widgets/plugin_settings_dialog/plugin_settings_dialog.ui
+ src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.ui
src/widgets/system_information_widget/system_information_widget.ui
src/widgets/tree_item_view_widget/tree_item_view_widget.ui
src/widgets/visualisation_widget/visualisation_widget.ui
diff --git a/app/mon/mon_gui/src/ecalmon.cpp b/app/mon/mon_gui/src/ecalmon.cpp
index 5095c66927..aff5aae2a3 100644
--- a/app/mon/mon_gui/src/ecalmon.cpp
+++ b/app/mon/mon_gui/src/ecalmon.cpp
@@ -145,21 +145,24 @@ Ecalmon::Ecalmon(QWidget *parent)
tabifyDockWidget(ui_.topics_dockwidget, ui_.processes_dockwidget);
tabifyDockWidget(ui_.topics_dockwidget, ui_.host_dockwidget);
tabifyDockWidget(ui_.topics_dockwidget, ui_.service_dockwidget);
+ tabifyDockWidget(ui_.topics_dockwidget, ui_.raw_monitoring_data_dockwidget);
ui_.topics_dockwidget->raise();
- log_widget_ = new LogWidget(this);
- topic_widget_ = new TopicWidget(this);
- process_widget_ = new ProcessWidget(this);
- host_widget_ = new HostWidget(this);
- service_widget_ = new ServiceWidget(this);
- syste_information_widget_ = new SystemInformationWidget(this);
-
- ui_.logging_dockwidget_content_frame_layout ->addWidget(log_widget_);
- ui_.topics_dockwidget_content_frame_layout ->addWidget(topic_widget_);
- ui_.processes_dockwidget_content_frame_layout ->addWidget(process_widget_);
- ui_.host_dockwidget_content_frame_layout ->addWidget(host_widget_);
- ui_.service_dockwidget_content_frame_layout ->addWidget(service_widget_);
- ui_.system_information_dockwidget_content_frame_layout->addWidget(syste_information_widget_);
+ log_widget_ = new LogWidget(this);
+ topic_widget_ = new TopicWidget(this);
+ process_widget_ = new ProcessWidget(this);
+ host_widget_ = new HostWidget(this);
+ service_widget_ = new ServiceWidget(this);
+ raw_monitoring_data_widget_ = new RawMonitoringDataWidget(this);
+ syste_information_widget_ = new SystemInformationWidget(this);
+
+ ui_.logging_dockwidget_content_frame_layout ->addWidget(log_widget_);
+ ui_.topics_dockwidget_content_frame_layout ->addWidget(topic_widget_);
+ ui_.processes_dockwidget_content_frame_layout ->addWidget(process_widget_);
+ ui_.host_dockwidget_content_frame_layout ->addWidget(host_widget_);
+ ui_.service_dockwidget_content_frame_layout ->addWidget(service_widget_);
+ ui_.raw_monitoring_data_dockwidget_content_frame_layout->addWidget(raw_monitoring_data_widget_);
+ ui_.system_information_dockwidget_content_frame_layout ->addWidget(syste_information_widget_);
monitor_update_timer_ = new QTimer(this);
connect(monitor_update_timer_, &QTimer::timeout, [this](){updateMonitor();});
diff --git a/app/mon/mon_gui/src/ecalmon.h b/app/mon/mon_gui/src/ecalmon.h
index 46002e5fd9..b77461f7ea 100644
--- a/app/mon/mon_gui/src/ecalmon.h
+++ b/app/mon/mon_gui/src/ecalmon.h
@@ -30,6 +30,7 @@
#include "widgets/ecalmon_tree_widget/process_widget.h"
#include "widgets/ecalmon_tree_widget/host_widget.h"
#include "widgets/ecalmon_tree_widget/service_widget.h"
+#include "widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h"
#include "widgets/system_information_widget/system_information_widget.h"
#ifdef _MSC_VER
@@ -90,6 +91,7 @@ private slots:
ProcessWidget* process_widget_;
HostWidget* host_widget_;
ServiceWidget* service_widget_;
+ RawMonitoringDataWidget* raw_monitoring_data_widget_;
LogWidget* log_widget_;
SystemInformationWidget* syste_information_widget_;
diff --git a/app/mon/mon_gui/src/main_window.ui b/app/mon/mon_gui/src/main_window.ui
index c94545e1ca..d7082285f9 100644
--- a/app/mon/mon_gui/src/main_window.ui
+++ b/app/mon/mon_gui/src/main_window.ui
@@ -332,6 +332,41 @@
+
+
+ Raw
+
+
+ 4
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+
+
+
+
+
@@ -541,7 +576,7 @@
- Plugin settings...
+ Pl&ugin settings...
@@ -568,6 +603,11 @@
Dark
+
+
+ Show raw monitoring &data...
+
+
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.cpp b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.cpp
new file mode 100644
index 0000000000..6c3807ca52
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.cpp
@@ -0,0 +1,94 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#include "protobuf_highlighter.h"
+
+ProtobufHighlighter::ProtobufHighlighter(QTextDocument* parent)
+ : ProtobufHighlighter(false, parent)
+{}
+
+ProtobufHighlighter::ProtobufHighlighter(bool darkmode_optimized, QTextDocument* parent)
+ : QSyntaxHighlighter(parent)
+{
+ // Curly braces
+ {
+ HighlightingRule rule;
+ rule.pattern_ = QRegularExpression ("\\{|\\}");
+ rule.format_.setForeground(darkmode_optimized ? QColor(86, 156, 214) : Qt::darkBlue);
+ rule.format_.setFontWeight(QFont::Bold);
+ highlighting_rules_.push_back(std::move(rule));
+ }
+
+ // HighlightingRule that highlights everything after the first colon in a line
+ // This is used to highlight enums (or basically everything that is neither a string nor a number, as those two will be overridden by the next rules)
+ {
+ HighlightingRule rule;
+ rule.pattern_ = QRegularExpression ("\\:.*");
+ rule.format_.setForeground(darkmode_optimized ? QColor(218, 99, 161) : Qt::darkMagenta);
+ rule.format_.setFontWeight(QFont::Bold);
+ highlighting_rules_.push_back(std::move(rule));
+ }
+
+ // Format the colons themselves in bold dark blue, just like the curly braces
+ {
+ HighlightingRule rule;
+ rule.pattern_ = QRegularExpression ("\\:");
+ rule.format_.setForeground(darkmode_optimized ? QColor(86, 156, 214) : Qt::darkBlue);
+ rule.format_.setFontWeight(QFont::Bold);
+ highlighting_rules_.push_back(std::move(rule));
+ }
+
+ // Numbers
+ // regular expression that matches numbers with optional sign, decimal point and exponent
+ {
+ HighlightingRule rule;
+ rule.pattern_ = QRegularExpression("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)");
+ rule.format_.setForeground(darkmode_optimized ? QColor(206, 145, 120) : Qt::darkRed);
+ rule.format_.setFontWeight(QFont::Bold);
+ highlighting_rules_.push_back(rule);
+ }
+
+ // Strings
+ // Strings are started and ended by double quotes.
+ // Double quotes inside strings are escaped by a backslash.
+ // The backslash itself can be escaped by another backslash.
+ // Binary characters can be escaped by a backslash followed by a number.
+ // Special characters like line endings can be escabed by a backslash followed by a letter.
+ // --> The backslash just escapes everything that follows.
+ {
+ HighlightingRule rule;
+ rule.pattern_ = QRegularExpression("\"(?:[^\"\\\\]|\\\\.)*\"");
+ rule.format_.setForeground(darkmode_optimized ? QColor(87, 166, 74) : Qt::darkGreen);
+ rule.format_.setFontWeight(QFont::Bold);
+ highlighting_rules_.push_back(std::move(rule));
+ }
+}
+
+void ProtobufHighlighter::highlightBlock(const QString &text)
+{
+ for (const HighlightingRule& rule : highlighting_rules_)
+ {
+ QRegularExpressionMatchIterator matchIterator = rule.pattern_.globalMatch(text);
+ while (matchIterator.hasNext())
+ {
+ const QRegularExpressionMatch match = matchIterator.next();
+ setFormat(match.capturedStart(), match.capturedLength(), rule.format_);
+ }
+ }
+}
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.h b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.h
new file mode 100644
index 0000000000..5e3cf298f1
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/protobuf_highlighter.h
@@ -0,0 +1,63 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+QT_BEGIN_NAMESPACE
+class QTextDocument;
+QT_END_NAMESPACE
+
+/**
+ * @brief A QSyntaxHighlighter that can highlight the DebugString Protobuf Syntax
+ */
+class ProtobufHighlighter : public QSyntaxHighlighter
+{
+ Q_OBJECT
+
+////////////////////////////////////////////
+// Constructor & Destructor
+////////////////////////////////////////////
+public:
+ ProtobufHighlighter(QTextDocument* parent = nullptr);
+ ProtobufHighlighter(bool darkmode_optimized = false, QTextDocument *parent = nullptr);
+
+////////////////////////////////////////////
+// QSyntaxHighlighter overrides
+////////////////////////////////////////////
+protected:
+ void highlightBlock(const QString &text) override;
+
+////////////////////////////////////////////
+// Member Variables
+////////////////////////////////////////////
+private:
+ struct HighlightingRule
+ {
+ QRegularExpression pattern_;
+ QTextCharFormat format_;
+ };
+
+ std::vector highlighting_rules_; //!< A list of highlighting rules. They are evaluated one after another and may ovewrite each other.
+};
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.cpp b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.cpp
new file mode 100644
index 0000000000..a928d2f98e
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.cpp
@@ -0,0 +1,296 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#include "raw_monitoring_data_widget.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+////////////////////////////////////////////
+// Constructor & Destructor
+////////////////////////////////////////////
+
+RawMonitoringDataWidget::RawMonitoringDataWidget(QWidget *parent)
+ : QWidget(parent)
+ , ui_{}
+ , protobuf_highlighter_(nullptr)
+{
+ ui_.setupUi(this);
+
+ chooseCorrectHighlighting();
+
+ // Update Button
+ connect(ui_.update_button, &QPushButton::clicked, this, &RawMonitoringDataWidget::updateRawMonitoringData);
+
+ // Save to file button
+ connect(ui_.save_to_file_button, &QPushButton::clicked, this, &RawMonitoringDataWidget::saveToFile);
+
+ // Search input
+ connect(ui_.search_lineedit, &SearchLineedit::searchNextTriggered, this, &RawMonitoringDataWidget::searchForward);
+ connect(ui_.search_lineedit, &SearchLineedit::searchPreviousTriggered, this, &RawMonitoringDataWidget::searchBackward);
+ connect(ui_.search_lineedit, &SearchLineedit::newSearchTriggered, this, &RawMonitoringDataWidget::updateSearchHighlighting);
+}
+
+////////////////////////////////////////////
+// Plaintext handling
+////////////////////////////////////////////
+
+void RawMonitoringDataWidget::setRawMonitoringData(const eCAL::pb::Monitoring& monitoring_data_pb)
+{
+ ui_.raw_monitoring_data_textedit->setPlainText(QString::fromStdString(monitoring_data_pb.DebugString()));
+ ui_.save_to_file_button->setEnabled(true);
+ ui_.search_lineedit->setEnabled(true);
+}
+
+void RawMonitoringDataWidget::updateRawMonitoringData()
+{
+ std::string monitoring_string;
+ eCAL::pb::Monitoring monitoring_pb;
+
+ if ((eCAL::Monitoring::GetMonitoring(monitoring_string) != 0) && !monitoring_string.empty() && monitoring_pb.ParseFromString(monitoring_string))
+ {
+ setRawMonitoringData(monitoring_pb);
+ }
+ else
+ {
+ ui_.raw_monitoring_data_textedit->setPlainText("- No monitoring data available -");
+ ui_.save_to_file_button->setEnabled(false);
+ ui_.search_lineedit->setEnabled(false);
+ }
+
+ ui_.search_lineedit->clear();
+}
+
+void RawMonitoringDataWidget::saveToFile()
+{
+ const QString filename = QFileDialog::getSaveFileName(this, tr("Save raw monitoring data"), "mon_raw.txt", tr("Text files (*.txt)"));
+
+ if (!filename.isEmpty())
+ {
+ QFile file(filename);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Text))
+ {
+ QTextStream out(&file);
+ out << ui_.raw_monitoring_data_textedit->toPlainText();
+ }
+ else
+ {
+ // Display error message
+ QMessageBox error_message(
+ QMessageBox::Icon::Critical
+ , tr("Error")
+ , tr("Failed to save raw data to file \"") + filename + "\""
+ , QMessageBox::Button::Ok
+ , this);
+ error_message.exec();
+ }
+ }
+}
+
+void RawMonitoringDataWidget::chooseCorrectHighlighting()
+{
+ // Get background of input fields
+ const QColor background = palette().color(QPalette::ColorRole::Base);
+
+ // Check if the bg color is dark or light
+ const bool dark_mode = (background.toHsl().lightness() < 128);
+
+ if (protobuf_highlighter_ != nullptr)
+ protobuf_highlighter_->deleteLater();
+
+ protobuf_highlighter_ = new ProtobufHighlighter(dark_mode, ui_.raw_monitoring_data_textedit->document());
+}
+
+////////////////////////////////////////////
+// Search handling
+////////////////////////////////////////////
+
+// Function updateSearchHighlighting that gets the tex from the search_lineedit as input and searches the search_string in the raw_monitoring_data_textedit. If found, the search_string is selected and the cursor is moved to the found search_string.
+void RawMonitoringDataWidget::updateSearchHighlighting(const QString& search_string)
+{
+ // Un-select the text from the raw_monitoring_data_textedit
+ QTextCursor cursor = ui_.raw_monitoring_data_textedit->textCursor();
+ cursor.setPosition(cursor.anchor(), QTextCursor::MoveMode::KeepAnchor);
+ ui_.raw_monitoring_data_textedit->setTextCursor(cursor);
+
+ if (search_string.isEmpty())
+ {
+ // If search string is empty, clear the find-highlighting and do nothing
+ ui_.raw_monitoring_data_textedit->setExtraSelections({});
+ ui_.occurences_label->setText("0 occurences");
+ ui_.occurences_label->setEnabled(false);
+ return;
+ }
+ else
+ {
+ // search in the textedit for all positions of the search string
+ QList extra_selections;
+ {
+ int index = ui_.raw_monitoring_data_textedit->toPlainText().indexOf(search_string, 0, Qt::CaseSensitivity::CaseInsensitive);
+ while (index != -1)
+ {
+ QTextEdit::ExtraSelection extra_selection;
+ extra_selection.cursor = ui_.raw_monitoring_data_textedit->textCursor();
+ extra_selection.cursor.setPosition(index);
+ extra_selection.cursor.setPosition(index + search_string.length(), QTextCursor::MoveMode::KeepAnchor);
+ extra_selection.format.setBackground(QBrush(QColor(243, 168, 29, 128)));
+ extra_selections << extra_selection;
+
+ index = ui_.raw_monitoring_data_textedit->toPlainText().indexOf(search_string, index + search_string.length(), Qt::CaseSensitivity::CaseInsensitive);
+ }
+ }
+
+ ui_.raw_monitoring_data_textedit->setExtraSelections(extra_selections);
+
+ // Set the number of occurences in the label
+ ui_.occurences_label->setText(QString::number(extra_selections.size()) + " occurence" + (extra_selections.size() != 1 ? "s" : ""));
+ ui_.occurences_label->setEnabled(true);
+ }
+}
+
+void RawMonitoringDataWidget::searchForward(const QString& search_string)
+{
+ if (search_string.isEmpty())
+ {
+ // if search string is empty, do nothing
+ return;
+ }
+ else
+ {
+ const bool found = ui_.raw_monitoring_data_textedit->find(search_string);
+ if (!found)
+ {
+ // if not found, search from the beginning
+ const QTextCursor cursor = ui_.raw_monitoring_data_textedit->document()->find(search_string, 0);
+ if (!cursor.isNull())
+ {
+ ui_.raw_monitoring_data_textedit->setTextCursor(cursor);
+ }
+ else
+ {
+ // If there are no occurences, flash the label and the search lineedit
+ flashSearchBar();
+ }
+ }
+ }
+}
+
+void RawMonitoringDataWidget::searchBackward(const QString& search_string)
+{
+ if (search_string.isEmpty())
+ {
+ // if search string is empty, do nothing
+ return;
+ }
+ else
+ {
+ const bool found = ui_.raw_monitoring_data_textedit->find(search_string, QTextDocument::FindFlag::FindBackward);
+
+ if (!found)
+ {
+ // if not found, search from the end
+ const int end_position = ui_.raw_monitoring_data_textedit->document()->characterCount();
+ const QTextCursor cursor = ui_.raw_monitoring_data_textedit->document()->find(search_string, end_position, QTextDocument::FindFlag::FindBackward);
+ if (!cursor.isNull())
+ {
+ ui_.raw_monitoring_data_textedit->setTextCursor(cursor);
+ }
+ else
+ {
+ // If there are no occurences, flash the label and the search lineedit
+ flashSearchBar();
+ }
+ }
+ }
+}
+
+void RawMonitoringDataWidget::flashSearchBar()
+{
+ ui_.occurences_label->setStyleSheet("QLabel { color: red; }");
+ ui_.search_lineedit->setStyleSheet("QLineEdit { background-color: red; }");
+ QTimer::singleShot(100, this, [this]()
+ {
+ ui_.occurences_label->setStyleSheet("");
+ ui_.search_lineedit->setStyleSheet("");
+ });
+ QTimer::singleShot(200, this, [this]()
+ {
+ ui_.occurences_label->setStyleSheet("QLabel { color: red; }");
+ ui_.search_lineedit->setStyleSheet("QLineEdit { background-color: red; }");
+ });
+ QTimer::singleShot(300, this, [this]()
+ {
+ ui_.occurences_label->setStyleSheet("");
+ ui_.search_lineedit->setStyleSheet("");
+ });
+ QTimer::singleShot(400, this, [this]()
+ {
+ ui_.occurences_label->setStyleSheet("QLabel { color: red; }");
+ ui_.search_lineedit->setStyleSheet("QLineEdit { background-color: red; }");
+ });
+ QTimer::singleShot(500, this, [this]()
+ {
+ ui_.occurences_label->setStyleSheet("");
+ ui_.search_lineedit->setStyleSheet("");
+ });
+}
+
+////////////////////////////////////////////
+// QWidget overrides (events)
+////////////////////////////////////////////
+
+void RawMonitoringDataWidget::keyPressEvent(QKeyEvent* key_event)
+{
+ if (key_event->matches(QKeySequence::StandardKey::Find))
+ {
+ ui_.search_lineedit->setFocus();
+ ui_.search_lineedit->selectAll();
+ }
+ else if (key_event->matches(QKeySequence::StandardKey::FindNext))
+ {
+ ui_.search_lineedit->searchNext();
+ }
+ else if (key_event->matches(QKeySequence::StandardKey::FindPrevious))
+ {
+ ui_.search_lineedit->searchPrevious();
+ }
+ else
+ {
+ QWidget::keyPressEvent(key_event);
+ }
+}
+
+void RawMonitoringDataWidget::changeEvent(QEvent* event)
+{
+ QWidget::changeEvent(event);
+
+ if (event->type() == QEvent::Type::PaletteChange)
+ {
+ chooseCorrectHighlighting();
+ event->accept();
+ }
+}
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h
new file mode 100644
index 0000000000..6929927391
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.h
@@ -0,0 +1,83 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#pragma once
+
+#include
+#include "ui_raw_monitoring_data_widget.h"
+
+#include "protobuf_highlighter.h"
+
+// Include the monitoring pb header
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4100 4127 4146 4505 4800 4189 4592) // disable proto warnings
+#endif
+#include
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+class RawMonitoringDataWidget : public QWidget
+{
+ Q_OBJECT
+
+////////////////////////////////////////////
+// Constructor & Destructor
+////////////////////////////////////////////
+public:
+ RawMonitoringDataWidget(QWidget *parent = Q_NULLPTR);
+
+////////////////////////////////////////////
+// Plaintext handling
+////////////////////////////////////////////
+public slots:
+ void setRawMonitoringData(const eCAL::pb::Monitoring& monitoring_data_pb);
+ void updateRawMonitoringData();
+
+private slots:
+ void saveToFile();
+
+private:
+ void chooseCorrectHighlighting();
+
+////////////////////////////////////////////
+// Search handling
+////////////////////////////////////////////
+private slots:
+ void updateSearchHighlighting(const QString& search_string);
+ void searchForward (const QString& search_string);
+ void searchBackward (const QString& search_string);
+
+ void flashSearchBar();
+
+////////////////////////////////////////////
+// QWidget overrides (events)
+////////////////////////////////////////////
+protected:
+ void keyPressEvent(QKeyEvent* key_event) override;
+ void changeEvent(QEvent* event) override;
+
+////////////////////////////////////////////
+// Member variables
+////////////////////////////////////////////
+private:
+ Ui::RawMonitoringDataWidget ui_;
+ ProtobufHighlighter* protobuf_highlighter_; //!< Syntax highlighter for protobuf that will be changed for dark / light mode
+};
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.ui b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.ui
new file mode 100644
index 0000000000..959402f3fe
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/raw_monitoring_data_widget.ui
@@ -0,0 +1,163 @@
+
+
+ RawMonitoringDataWidget
+
+
+
+ 0
+ 0
+ 830
+ 591
+
+
+
+ GroupWidget
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 20
+ 0
+
+
+
+ Update Raw Data
+
+
+
+ :/ecalicons/UPDATE:/ecalicons/UPDATE
+
+
+
+ -
+
+
+ false
+
+
+
+ 20
+ 0
+
+
+
+ Find
+
+
+
+ -
+
+
+ false
+
+
+
+ 20
+ 0
+
+
+
+ 0 occurences
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 20
+ 0
+
+
+
+ Save to file...
+
+
+
+ :/ecalicons/SAVE_AS:/ecalicons/SAVE_AS
+
+
+
+
+
+
+ -
+
+
+
+ 20
+ 20
+
+
+
+
+ Courier New
+
+
+
+ true
+
+
+ No data, yet. Press "Update Raw Data".
+
+
+
+
+
+
+
+ SearchLineedit
+ QLineEdit
+ widgets/raw_monitoring_data_widget/search_lineedit.h
+
+
+
+
+
+
+
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.cpp b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.cpp
new file mode 100644
index 0000000000..7b1ff160e7
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.cpp
@@ -0,0 +1,161 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#include "search_lineedit.h"
+
+#include
+#include
+#include
+#include
+
+/////////////////////////////////////////
+// Constructors & Destructor
+/////////////////////////////////////////
+
+SearchLineedit::SearchLineedit(QWidget *parent)
+ : SearchLineedit("", parent)
+{}
+
+SearchLineedit::SearchLineedit(const QString &contents, QWidget *parent)
+ : QLineEdit(contents, parent)
+ , clear_lineedit_action_(nullptr)
+ , search_next_action (nullptr)
+ , search_previous_action(nullptr)
+{
+ // Clear lineedit action
+ clear_lineedit_action_ = new QAction(tr("Clear"), this);
+ addAction(clear_lineedit_action_, QLineEdit::ActionPosition::TrailingPosition);
+ clear_lineedit_action_->setIcon(QIcon(QApplication::style()->standardIcon(QStyle::StandardPixmap::SP_LineEditClearButton)));
+ connect(clear_lineedit_action_, &QAction::triggered, this, [this]() {clear(); });
+ clear_lineedit_action_->setEnabled(!contents.isEmpty());
+
+ connect(this, &QLineEdit::textChanged
+ , [this](const QString& text)
+ {
+ clear_lineedit_action_->setEnabled(!text.isEmpty());
+ search_next_action ->setEnabled(!text.isEmpty());
+ search_previous_action->setEnabled(!text.isEmpty());
+
+ if (text.isEmpty())
+ {
+ // If the text is empty, we trigger a "new search", so the highlighting is cleared.
+ last_search_text_ = "";
+ emit newSearchTriggered("");
+ }
+ });
+
+ // Find next / Find previous actions
+ search_next_action = new QAction(tr("Find next"), this);
+ search_previous_action = new QAction(tr("Find previous"), this);
+
+ connect(search_next_action, &QAction::triggered, this, [this]() { searchNext(); });
+ connect(search_previous_action, &QAction::triggered, this, [this]() { searchPrevious(); });
+
+ addAction(search_previous_action, QLineEdit::TrailingPosition);
+ addAction(search_next_action, QLineEdit::TrailingPosition);
+
+ chooseThemeIcons();
+}
+
+/////////////////////////////////////////
+// Icons
+/////////////////////////////////////////
+
+void SearchLineedit::setClearIcon(const QIcon& icon)
+{
+ clear_lineedit_action_->setIcon(icon);
+}
+
+void SearchLineedit::chooseThemeIcons()
+{
+ // Get background of input fields
+ const QColor background = palette().color(QPalette::ColorRole::Base);
+
+ // Check if the bg color is dark or light
+ const bool dark_mode = (background.toHsl().lightness() < 128);
+
+ //TODO: change icon of clear_lineedit_action_
+ search_next_action ->setIcon(QIcon(QString(":/ecalicons/") + (dark_mode ? "darkmode/" : "") + "ARROW_DOWN"));
+ search_previous_action->setIcon(QIcon(QString(":/ecalicons/") + (dark_mode ? "darkmode/" : "") + "ARROW_UP"));
+}
+
+/////////////////////////////////////////
+// Signals and slots for search functionality
+/////////////////////////////////////////
+
+void SearchLineedit::searchNext()
+{
+ if (last_search_text_ != text())
+ {
+ last_search_text_ = text();
+ emit newSearchTriggered(text());
+ }
+
+ emit searchNextTriggered(text());
+}
+
+void SearchLineedit::searchPrevious()
+{
+ if (last_search_text_ != text())
+ {
+ last_search_text_ = text();
+ emit newSearchTriggered(text());
+ }
+
+ emit searchPreviousTriggered(text());
+}
+
+/////////////////////////////////////////
+// Qt Events override
+/////////////////////////////////////////
+
+void SearchLineedit::keyPressEvent(QKeyEvent* key_event)
+{
+ if (key_event->matches(QKeySequence::StandardKey::Cancel))
+ {
+ clear();
+ }
+ else if (key_event->matches(QKeySequence::StandardKey::FindNext)
+ || key_event->key() == Qt::Key_Enter
+ || key_event->key() == Qt::Key_Return)
+ {
+ searchNext();
+ }
+ else if (key_event->matches(QKeySequence::StandardKey::FindPrevious))
+ {
+ searchPrevious();
+ }
+ else
+ {
+ QLineEdit::keyPressEvent(key_event);
+ }
+}
+
+void SearchLineedit::changeEvent(QEvent* event)
+{
+ QLineEdit::changeEvent(event);
+
+ if (event->type() == QEvent::Type::PaletteChange)
+ {
+ chooseThemeIcons();
+ event->accept();
+ }
+}
+
+
diff --git a/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.h b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.h
new file mode 100644
index 0000000000..6f1e6747ab
--- /dev/null
+++ b/app/mon/mon_gui/src/widgets/raw_monitoring_data_widget/search_lineedit.h
@@ -0,0 +1,126 @@
+/* ========================= eCAL LICENSE =================================
+ *
+ * Copyright (C) 2016 - 2023 Continental Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ========================= eCAL LICENSE =================================
+*/
+
+#pragma once
+
+#include
+
+/**
+ * @brief A search-bar with some special buttons / actions and find next / previous functionality
+ *
+ * The Widget using this class has to connect the following singals:
+ * - newSearchTriggered
+ * - searchNextTriggered
+ * - searchPreviousTriggered
+ */
+class SearchLineedit : public QLineEdit
+{
+ Q_OBJECT
+
+/////////////////////////////////////////
+// Constructors & Destructor
+/////////////////////////////////////////
+public:
+ SearchLineedit(QWidget *parent = Q_NULLPTR);
+ SearchLineedit(const QString &contents, QWidget *parent = Q_NULLPTR);
+
+/////////////////////////////////////////
+// Icons
+/////////////////////////////////////////
+public:
+ /**
+ * @brief Set the "clear" icon to a custom icon
+ *
+ * If not used, the default Qt icon will be used.
+ *
+ * @param icon The new icon to set
+ */
+ void setClearIcon(const QIcon& icon);
+
+private:
+ /** @brief Choose icons based on dark / light mode */
+ void chooseThemeIcons();
+
+/////////////////////////////////////////
+// Signals and slots for search functionality
+/////////////////////////////////////////
+public slots:
+ /**
+ * @brief request searching for the next occurrence.
+ *
+ * This slot doesn't need to be triggered from the outside, as this widget
+ * will do that itself. It may however be helpfull to trigger the search
+ * functionality from the outside, e.g. when the user clicked on a menu item
+ * or pressed a special key sequence.
+ */
+ void searchNext();
+
+ /**
+ * @brief request searching for the previous occurrence.
+ *
+ * This slot doesn't need to be triggered from the outside, as this widget
+ * will do that itself. It may however be helpfull to trigger the search
+ * functionality from the outside, e.g. when the user clicked on a menu item
+ * or pressed a special key sequence.
+ */
+ void searchPrevious();
+
+signals:
+ /**
+ * @brief The user requested to find a new string.
+ *
+ * This this signal is always emitted, when BOTH is true:
+ * - The user has changed the input in the search field
+ * - The user has requested to find the next / previous occurence
+ *
+ * In this case, the searchNextTriggered / searchPreviousTriggered signals
+ * are also emitted
+ *
+ * It is ALSO triggered, when the input was cleared. In this case, the
+ * searchNextTriggered / searchPreviousTriggered are not emitted, as the user
+ * didn't actually search for anything.
+ *
+ * Another widget doesn't have to use this signal. It is however usefull e.g.
+ * to update the SpecialSelections / Result highlighting.
+ */
+ void newSearchTriggered (const QString& search_text);
+
+ /** @brief The user wants to find the next occurence. */
+ void searchNextTriggered (const QString& search_text);
+
+ /** @brief The user wants to find the previous occurence. */
+ void searchPreviousTriggered(const QString& search_text);
+
+/////////////////////////////////////////
+// Qt Events override
+/////////////////////////////////////////
+protected:
+ void keyPressEvent(QKeyEvent* key_event) override;
+ void changeEvent(QEvent* event) override;
+
+/////////////////////////////////////////
+// Member variables
+/////////////////////////////////////////
+private:
+ QAction* clear_lineedit_action_; /**< Action for clearing the search string */
+ QAction* search_next_action; /**< Action for finding the next occurence */
+ QAction* search_previous_action; /**< Action for finding the previous occurence */
+
+ QString last_search_text_; /**< The last search text. Used to determine if a new search was triggered. */
+};