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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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. */ +};