From 35ae9313e09a60e471cc814d73858d4b56d17fcc Mon Sep 17 00:00:00 2001 From: romanodanilo <62891297+romanodanilo@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:08:01 +0200 Subject: [PATCH 01/17] Remove config gui component (#134) Signed-off-by: romanodanilo Signed-off-by: romanodanilo <62891297+romanodanilo@users.noreply.github.com> --- doc/manual/file_formats.md | 3 - src/CMakeLists.txt | 3 +- src/runtime/CMakeLists.txt | 40 - src/runtime/src/c_configuration_validator.cpp | 55 -- src/runtime/src/c_configuration_validator.h | 32 - src/runtime/src/runtime.cpp | 84 --- src/runtime/src/runtime.h | 32 - src/runtime/src/stdafx.cpp | 9 - src/runtime/src/stdafx.h | 18 - src/runtime/src/ui/c_checker_dialog.cpp | 90 --- src/runtime/src/ui/c_checker_dialog.h | 42 -- src/runtime/src/ui/c_global_param_dialog.cpp | 103 --- src/runtime/src/ui/c_global_param_dialog.h | 44 -- src/runtime/src/ui/c_local_param_dialog.cpp | 40 - src/runtime/src/ui/c_local_param_dialog.h | 31 - src/runtime/src/ui/c_param_dialog.cpp | 68 -- src/runtime/src/ui/c_param_dialog.h | 49 -- src/runtime/src/ui/c_process_view.cpp | 700 ------------------ src/runtime/src/ui/c_process_view.h | 192 ----- src/runtime/src/ui/c_runtime_window.cpp | 439 ----------- src/runtime/src/ui/c_runtime_window.h | 107 --- test/function/CMakeLists.txt | 9 - .../files/DemoCheckerBundle_config.xml | 18 - .../files/XodrSchemaChecker_config.xml | 19 - .../files/XoscSchemaChecker_config.xml | 19 - test/function/runtime/src/CMakeLists.txt | 40 - test/function/runtime/src/runtime_tester.cpp | 32 - 27 files changed, 1 insertion(+), 2317 deletions(-) delete mode 100644 src/runtime/CMakeLists.txt delete mode 100644 src/runtime/src/c_configuration_validator.cpp delete mode 100644 src/runtime/src/c_configuration_validator.h delete mode 100644 src/runtime/src/runtime.cpp delete mode 100644 src/runtime/src/runtime.h delete mode 100644 src/runtime/src/stdafx.cpp delete mode 100644 src/runtime/src/stdafx.h delete mode 100644 src/runtime/src/ui/c_checker_dialog.cpp delete mode 100644 src/runtime/src/ui/c_checker_dialog.h delete mode 100644 src/runtime/src/ui/c_global_param_dialog.cpp delete mode 100644 src/runtime/src/ui/c_global_param_dialog.h delete mode 100644 src/runtime/src/ui/c_local_param_dialog.cpp delete mode 100644 src/runtime/src/ui/c_local_param_dialog.h delete mode 100644 src/runtime/src/ui/c_param_dialog.cpp delete mode 100644 src/runtime/src/ui/c_param_dialog.h delete mode 100644 src/runtime/src/ui/c_process_view.cpp delete mode 100644 src/runtime/src/ui/c_process_view.h delete mode 100644 src/runtime/src/ui/c_runtime_window.cpp delete mode 100644 src/runtime/src/ui/c_runtime_window.h delete mode 100644 test/function/runtime/files/DemoCheckerBundle_config.xml delete mode 100644 test/function/runtime/files/XodrSchemaChecker_config.xml delete mode 100644 test/function/runtime/files/XoscSchemaChecker_config.xml delete mode 100644 test/function/runtime/src/CMakeLists.txt delete mode 100644 test/function/runtime/src/runtime_tester.cpp diff --git a/doc/manual/file_formats.md b/doc/manual/file_formats.md index e23a3088..964277aa 100644 --- a/doc/manual/file_formats.md +++ b/doc/manual/file_formats.md @@ -38,9 +38,6 @@ to be used. Notes for the paths: -- "application" should contain the path of your executable relative to the path - of the ConfigGUI binary - it is recommended to place all executables in the - `./bin` directory. - Results will be stored in the directory from which the call is made. ## Result File (`*.xqar`) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7adab0b9..3a47b7c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,7 +9,6 @@ set(CMAKE_INSTALL_RPATH $ORIGIN/../lib) add_subdirectory(common) add_subdirectory(report_modules) add_subdirectory(result_pooling) -add_subdirectory(runtime) # FIXME: install()? ## copy common lib @@ -40,4 +39,4 @@ file( PATTERN "*.svn" EXCLUDE ) -file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin/plugin) \ No newline at end of file +file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin/plugin) diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt deleted file mode 100644 index 009733c6..00000000 --- a/src/runtime/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2023 CARIAD SE. -# -# This Source Code Form is subject to the terms of the Mozilla -# Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - -set(RUNTIME_PROJECT "ConfigGUI") -project(${RUNTIME_PROJECT}) - -set(CMAKE_AUTOMOC ON) - -add_executable(${RUNTIME_PROJECT} - src/c_configuration_validator.cpp - src/c_configuration_validator.h - src/runtime.cpp - src/runtime.h - src/stdafx.cpp - src/stdafx.h - src/ui/c_checker_dialog.cpp - src/ui/c_param_dialog.cpp - src/ui/c_local_param_dialog.cpp - src/ui/c_global_param_dialog.cpp - src/ui/c_process_view.cpp - src/ui/c_runtime_window.cpp -) - -target_link_libraries(${RUNTIME_PROJECT} PRIVATE - qc4openx-common - Qt5::Gui - Qt5::Widgets - $<$:stdc++fs> -) - -if (WIN32) - # FIXME: We need some adaptions that this is usable in Linux - install(TARGETS ${RUNTIME_PROJECT} DESTINATION bin) - qc4openx_install_qt(bin ${RUNTIME_PROJECT}${CMAKE_EXECUTABLE_SUFFIX}) -endif(WIN32) - -set_target_properties(${RUNTIME_PROJECT} PROPERTIES FOLDER runtime) diff --git a/src/runtime/src/c_configuration_validator.cpp b/src/runtime/src/c_configuration_validator.cpp deleted file mode 100644 index d96cde15..00000000 --- a/src/runtime/src/c_configuration_validator.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_configuration_validator.h" - -#include "common/config_format/c_configuration.h" - -bool cConfigurationValidator::ValidateConfiguration(cConfiguration *const configuration, std::string &message) -{ - message = "Unknown failure"; - - bool success = true; - - success = CheckNecessaryParametersExist(configuration, message); - if (!success) - return false; - - success = CheckParameterNotEmpty(configuration, PARAM_INPUT_FILE, message); - if (!success) - return false; - - return true; -} - -bool cConfigurationValidator::CheckNecessaryParametersExist(cConfiguration *const configuration, std::string &message) -{ - if (!configuration->HasParam(PARAM_INPUT_FILE)) - { - message = "Configuration needs a parameter '" + PARAM_INPUT_FILE + "' for running."; - return false; - } - - return true; -} - -bool cConfigurationValidator::CheckParameterNotEmpty(cConfiguration *const configuration, const std::string ¶mName, - std::string &message) -{ - if (configuration->HasParam(paramName)) - { - const std::string paramValue = configuration->GetParam(paramName); - - if (paramValue.empty()) - { - message = "Parameter '" + paramName + "' is empty. Please specify a value."; - return false; - } - } - - return true; -} diff --git a/src/runtime/src/c_configuration_validator.h b/src/runtime/src/c_configuration_validator.h deleted file mode 100644 index 478ddd53..00000000 --- a/src/runtime/src/c_configuration_validator.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef cConfigurationValidator_h__ -#define cConfigurationValidator_h__ - -#include - -class cConfiguration; - -// Validator for the configuration file -class cConfigurationValidator -{ - - public: - // Checks a Configuration for consistency - static bool ValidateConfiguration(cConfiguration *config, std::string &message); - - protected: - // Check if at least one of the necessary parameters exists - static bool CheckNecessaryParametersExist(cConfiguration *const configuration, std::string &message); - - // Check if parameter is valid (is not empty) - static bool CheckParameterNotEmpty(cConfiguration *const configuration, const std::string ¶mName, - std::string &message); -}; - -#endif diff --git a/src/runtime/src/runtime.cpp b/src/runtime/src/runtime.cpp deleted file mode 100644 index a7c46606..00000000 --- a/src/runtime/src/runtime.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "runtime.h" -#include "stdafx.h" - -#include "ui/c_runtime_window.h" - -XERCES_CPP_NAMESPACE_USE - -// Main program entry point -int main(int argc, char *argv[]) -{ - std::string strToolpath = argv[0]; - - std::string strConfigurationFilepath = ""; - std::string strInputFilepath = ""; - - std::cout << std::endl << std::endl; - - for (int i = 1; i < argc; i++) - { - if (strcmp(argv[i], "-config") == 0) - { - if (argc >= i + 1) - { - strConfigurationFilepath = argv[i + 1]; - std::cout << "Configuration: " << strConfigurationFilepath << std::endl; - } - } - else if (strcmp(argv[i], "-input") == 0) - { - if (argc >= i + 1) - { - strInputFilepath = argv[i + 1]; - std::cout << "Input: " << strInputFilepath << std::endl; - } - } - else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) - { - ShowHelp(strToolpath); - return 0; - } - } - - // Handle open of configuration with only one parameter - if (argc == 2) - strConfigurationFilepath = argv[1]; - - QApplication app(argc, argv); - RunConfigGUI(strConfigurationFilepath, strInputFilepath, app); - return 0; -} - -void ShowHelp(const std::string &toolPath) -{ - std::string applicationName = toolPath; - std::string applicationNameWithoutExt = toolPath; - GetFileName(&applicationName, false); - GetFileName(&applicationNameWithoutExt, true); - - std::cout << "\nUsage of " << applicationNameWithoutExt << ":" << std::endl; - std::cout << "\nOpen the application with DefaultConfiguration.xml: \n" - << applicationName << " myConfiguration.xml" << std::endl; - std::cout << "\nOpen the application with myConfiguration.xml and a given input file which is under " - "test. \n" - << applicationName << " -config myConfiguration.xml -input myTrack.xodr " << std::endl; - std::cout << "\n\n"; -} - -void RunConfigGUI(const std::string &strConfigurationFilepath, const std::string &strInputFilepath, - const QApplication &app) -{ - cRuntimeWindow mainWindow(strConfigurationFilepath, strInputFilepath); - mainWindow.show(); - - app.processEvents(); - - app.exec(); -} diff --git a/src/runtime/src/runtime.h b/src/runtime/src/runtime.h deleted file mode 100644 index bcfe818e..00000000 --- a/src/runtime/src/runtime.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "common/util.h" - -#include - -/** - * Main function for application - * - * @param [in] argc Number of arguments in shell - * @param [in] argv Pointer to arguments - * - * @return The standard return value - */ -int main(int argc, char *argv[]); - -/** - * Shows the help for the application - * @param [in] applicationName The name of the application - */ -void ShowHelp(const std::string &applicationName); - -/** - * Runs the config gui - */ -void RunConfigGUI(const std::string &strConfigurationFilepath, const std::string &strInputFilepath, - const QApplication &app); diff --git a/src/runtime/src/stdafx.cpp b/src/runtime/src/stdafx.cpp deleted file mode 100644 index 4a0e5a70..00000000 --- a/src/runtime/src/stdafx.cpp +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -#include "stdafx.h" diff --git a/src/runtime/src/stdafx.h b/src/runtime/src/stdafx.h deleted file mode 100644 index 370fd954..00000000 --- a/src/runtime/src/stdafx.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef _STD_INCLUDE_HEADER_ -#define _STD_INCLUDE_HEADER_ - -#include -#include -#include -#include - -#include "common/util.h" - -#endif // _STD_INCLUDE_HEADER_ diff --git a/src/runtime/src/ui/c_checker_dialog.cpp b/src/runtime/src/ui/c_checker_dialog.cpp deleted file mode 100644 index 4dea8fd4..00000000 --- a/src/runtime/src/ui/c_checker_dialog.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_checker_dialog.h" -#include - -cCheckerDialog::cCheckerDialog(eIssueLevel minLevel, eIssueLevel maxLevel, QWidget *parent) : QDialog(parent) -{ - QVBoxLayout *layout = new QVBoxLayout; - - QWidget *paramWidget = new QWidget(this); - QVBoxLayout *paramWidgetLayout = new QVBoxLayout; - - QLabel *minLevelLabel = new QLabel(paramWidget); - minLevelLabel->setText("Minimal warn level of issues"); - - _minLevelEdit = new QComboBox(this); - _minLevelEdit->setEditable(false); - - _minLevelEdit->addItem("Information", INFO_LVL); - _minLevelEdit->addItem("Warning", WARNING_LVL); - _minLevelEdit->addItem("Error", ERROR_LVL); - - int index = _minLevelEdit->findData(minLevel); - if (index != -1) - { - _minLevelEdit->setCurrentIndex(index); - } - - QLabel *maxLevelLabel = new QLabel(paramWidget); - maxLevelLabel->setText("Maximal warn level of issues"); - - _maxLevelEdit = new QComboBox(this); - _maxLevelEdit->setEditable(false); - - _maxLevelEdit->addItem("Error", ERROR_LVL); - _maxLevelEdit->addItem("Warning", WARNING_LVL); - _maxLevelEdit->addItem("Information", INFO_LVL); - - index = _maxLevelEdit->findData(maxLevel); - if (index != -1) - { - _maxLevelEdit->setCurrentIndex(index); - } - - paramWidgetLayout->setContentsMargins(3, 3, 3, 3); - paramWidgetLayout->addWidget(minLevelLabel, 0); - paramWidgetLayout->addWidget(_minLevelEdit, 0); - paramWidgetLayout->addWidget(maxLevelLabel, 0); - paramWidgetLayout->addWidget(_maxLevelEdit, 0); - paramWidget->setLayout(paramWidgetLayout); - - QPushButton *okay = new QPushButton(this); - okay->setText("Set"); - - connect(okay, SIGNAL(clicked()), this, SLOT(SaveAndClose())); - - layout->addWidget(paramWidget, 2); - layout->addWidget(okay, 0); - layout->setContentsMargins(2, 3, 2, 3); - - setLayout(layout); - setWindowFlags(Qt::Tool); - setMinimumWidth(250); - - setWindowTitle("Checker"); -} - -void cCheckerDialog::SaveAndClose() -{ - accept(); -} - -// Returns the min level -eIssueLevel cCheckerDialog::GetMinLevel() const -{ - assert(_minLevelEdit != nullptr); - return static_cast(_minLevelEdit->currentData().toInt()); -} - -// Returns the max level -eIssueLevel cCheckerDialog::GetMaxLevel() const -{ - assert(_maxLevelEdit != nullptr); - return static_cast(_maxLevelEdit->currentData().toInt()); -} diff --git a/src/runtime/src/ui/c_checker_dialog.h b/src/runtime/src/ui/c_checker_dialog.h deleted file mode 100644 index 6fac185a..00000000 --- a/src/runtime/src/ui/c_checker_dialog.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CCHECKER_DIALOG_H -#define CCHECKER_DIALOG_H - -#include -#include -#include -#include -#include -#include -#include - -#include "common/result_format/c_issue.h" - -class cCheckerDialog : public QDialog -{ - Q_OBJECT - - public: - cCheckerDialog(eIssueLevel minLevel, eIssueLevel maxLevel, QWidget *parent); - - // Returns the min level - eIssueLevel GetMinLevel() const; - - // Returns the max level - eIssueLevel GetMaxLevel() const; - - protected: - QComboBox *_minLevelEdit{nullptr}; - QComboBox *_maxLevelEdit{nullptr}; - - public slots: - void SaveAndClose(); -}; - -#endif diff --git a/src/runtime/src/ui/c_global_param_dialog.cpp b/src/runtime/src/ui/c_global_param_dialog.cpp deleted file mode 100644 index d975c061..00000000 --- a/src/runtime/src/ui/c_global_param_dialog.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_global_param_dialog.h" -#include "common/util.h" - -#include "c_process_view.h" - -#include "common/config_format/c_configuration.h" - -cGlobalParamDialog::cGlobalParamDialog(const QString &initalParamName, const QString &initalParamValue, - const bool paramNameEditable, cProcessView *parent, - const cConfiguration *currentConfig) - : cParamDialog(initalParamName, initalParamValue, paramNameEditable, parent) -{ - _processView = parent; - InitUIElements(initalParamName, initalParamValue, paramNameEditable, currentConfig); - AddWidgetsToLayout(initalParamName); -} - -void cGlobalParamDialog::InitUIElements(const QString &initalParamName, const QString &initalParamValue, - const bool paramNameEditable, const cConfiguration *currentConfig) -{ - InitBasicUIElements(initalParamName, initalParamValue); - - _paramNameComboBox = new QComboBox(this); - QStringList items; - - // Add default parameters - items << QString::fromStdString(PARAM_INPUT_FILE); - - // Add parameters, which are in current config - std::vector params = currentConfig->GetParams().GetParams(); - for (size_t i = 0; i < params.size(); ++i) - items << QString::fromStdString(params[i]); - - // Add initial parameter (just for safety) - items << initalParamName; - - items.removeDuplicates(); - _paramNameComboBox->addItems(items); - _paramNameComboBox->setCurrentText(initalParamName); - _paramNameComboBox->setEditable(true); - _paramNameComboBox->setEnabled(paramNameEditable); - - connect(_paramNameComboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(SwitchCall(const QString &))); - connect(_paramNameComboBox, SIGNAL(editTextChanged(const QString &)), this, SLOT(SwitchCall(const QString &))); -} - -void cGlobalParamDialog::AddWidgetsToLayout(const QString &initalParamName) -{ - _paramWidgetLayout->addWidget(_paramNameComboBox, 2); - AddBasicWidgetsToLayout(); - - SwitchCall(initalParamName); -} - -void cGlobalParamDialog::SwitchCall(const QString ¶mName) -{ - RemoveFileOpenButton(); - - if (paramName.toLower() == QString::fromStdString(PARAM_INPUT_FILE).toLower()) - AddFileOpenButton(SLOT(OpenInputFile())); -} - -void cGlobalParamDialog::AddFileOpenButton(const char *slot) -{ - _fileOpenButton = new QPushButton(this); - _fileOpenButton->setText("..."); - - connect(_fileOpenButton, SIGNAL(clicked()), this, slot); - _paramWidgetLayout->addWidget(_fileOpenButton, 0); -} - -void cGlobalParamDialog::RemoveFileOpenButton() -{ - if (_fileOpenButton != 0) - { - _paramWidgetLayout->removeWidget(_fileOpenButton); - delete _fileOpenButton; - _fileOpenButton = nullptr; - } -} - -void cGlobalParamDialog::SaveAndClose() -{ - _paramName = _paramNameComboBox->currentText(); - _paramValue = _paramValueEdit->text(); - - accept(); -} - -void cGlobalParamDialog::OpenInputFile() -{ - QString filePath = QFileDialog::getOpenFileName(this, tr("Open File"), "", ""); - - if (!filePath.isEmpty()) - _paramValueEdit->setText(filePath); -} diff --git a/src/runtime/src/ui/c_global_param_dialog.h b/src/runtime/src/ui/c_global_param_dialog.h deleted file mode 100644 index 10888dfc..00000000 --- a/src/runtime/src/ui/c_global_param_dialog.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CGLOBAL_PARAM_DIALOG_H -#define CGLOBAL_PARAM_DIALOG_H - -#include "c_param_dialog.h" - -// Forward declaration to avoid problems with circular dependencies (especially under Linux) -class cConfiguration; -class cProcessView; - -class cGlobalParamDialog : public cParamDialog -{ - Q_OBJECT - - public: - cGlobalParamDialog(const QString &initalParamName, const QString &initalParamValue, const bool paramNameEditable, - cProcessView *parent, const cConfiguration *currentConfig); - - protected: - cProcessView *_processView{nullptr}; - QComboBox *_paramNameComboBox{nullptr}; - QPushButton *_fileOpenButton{nullptr}; - - void InitUIElements(const QString &initalParamName, const QString &initalParamValue, const bool paramNameEditable, - const cConfiguration *currentConfig); - void AddWidgetsToLayout(const QString &initalParamName); - - private: - void AddFileOpenButton(const char *slot); - void RemoveFileOpenButton(); - - public slots: - void SwitchCall(const QString ¶mName); - void SaveAndClose(); - void OpenInputFile(); -}; - -#endif diff --git a/src/runtime/src/ui/c_local_param_dialog.cpp b/src/runtime/src/ui/c_local_param_dialog.cpp deleted file mode 100644 index ee272b49..00000000 --- a/src/runtime/src/ui/c_local_param_dialog.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_local_param_dialog.h" - -cLocalParamDialog::cLocalParamDialog(const QString &initalParamName, const QString &initalParamValue, - const bool paramNameEditable, QWidget *parent) - : cParamDialog(initalParamName, initalParamValue, paramNameEditable, parent) -{ - InitUIElements(initalParamName, initalParamValue, paramNameEditable); - AddWidgetsToLayout(); -} - -void cLocalParamDialog::InitUIElements(const QString &initalParamName, const QString &initalParamValue, - const bool paramNameEditable) -{ - InitBasicUIElements(initalParamName, initalParamValue); - - _paramNameEdit = new QLineEdit(this); - _paramNameEdit->setText(initalParamName); - _paramNameEdit->setEnabled(paramNameEditable); -} - -void cLocalParamDialog::AddWidgetsToLayout() -{ - _paramWidgetLayout->addWidget(_paramNameEdit, 2); - AddBasicWidgetsToLayout(); -} - -void cLocalParamDialog::SaveAndClose() -{ - _paramName = _paramNameEdit->text(); - _paramValue = _paramValueEdit->text(); - - accept(); -} diff --git a/src/runtime/src/ui/c_local_param_dialog.h b/src/runtime/src/ui/c_local_param_dialog.h deleted file mode 100644 index 747ae4fb..00000000 --- a/src/runtime/src/ui/c_local_param_dialog.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CLOCAL_PARAM_DIALOG_H -#define CLOCAL_PARAM_DIALOG_H - -#include "c_param_dialog.h" - -class cLocalParamDialog : public cParamDialog -{ - Q_OBJECT - - public: - cLocalParamDialog(const QString &initalParamName, const QString &initalParamValue, const bool paramNameEditable, - QWidget *parent); - - protected: - QLineEdit *_paramNameEdit{nullptr}; - - void InitUIElements(const QString &initalParamName, const QString &initalParamValue, const bool paramNameEditable); - void AddWidgetsToLayout(); - - public slots: - void SaveAndClose(); -}; - -#endif diff --git a/src/runtime/src/ui/c_param_dialog.cpp b/src/runtime/src/ui/c_param_dialog.cpp deleted file mode 100644 index f69230cb..00000000 --- a/src/runtime/src/ui/c_param_dialog.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_param_dialog.h" - -cParamDialog::cParamDialog(const QString & /*initalParamName*/, const QString & /*initalParamValue*/, - const bool /*paramNameEditable*/, QWidget *parent) - : QDialog(parent) -{ -} - -void cParamDialog::InitBasicUIElements(const QString &initalParamName, const QString &initalParamValue) -{ - _paramName = initalParamName; - _paramValue = initalParamValue; - - QVBoxLayout *layout = new QVBoxLayout; - - QWidget *paramWidget = new QWidget(this); - _paramWidgetLayout = new QHBoxLayout; - _paramWidgetLayout->setContentsMargins(3, 3, 3, 3); - paramWidget->setLayout(_paramWidgetLayout); - - _paramLabel = new QLabel(this); - _paramLabel->setText(" = "); - _paramLabel->setStyleSheet("font-weight: bold;"); - - _paramValueEdit = new QLineEdit(this); - _paramValueEdit->setText(initalParamValue); - - QPushButton *okayButton = new QPushButton(this); - okayButton->setText("Set"); - connect(okayButton, SIGNAL(clicked()), this, SLOT(SaveAndClose())); - - layout->addWidget(paramWidget, 2); - layout->addWidget(okayButton, 0); - layout->setContentsMargins(2, 3, 2, 3); - - setLayout(layout); - setWindowFlags(Qt::Tool); - setMinimumWidth(350); - - _paramValueEdit->setFocus(); - - setWindowTitle("Parameters"); -} - -void cParamDialog::AddBasicWidgetsToLayout() -{ - _paramWidgetLayout->addWidget(_paramLabel, 0); - _paramWidgetLayout->addWidget(_paramValueEdit, 3); -} - -// Returns the name of the param -QString cParamDialog::GetParamName() const -{ - return _paramName; -} - -// Returns the value of the param -QString cParamDialog::GetParamValue() const -{ - return _paramValue; -} diff --git a/src/runtime/src/ui/c_param_dialog.h b/src/runtime/src/ui/c_param_dialog.h deleted file mode 100644 index 4165cae5..00000000 --- a/src/runtime/src/ui/c_param_dialog.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CPARAM_DIALOG_H -#define CPARAM_DIALOG_H - -#include -#include -#include -#include -#include -#include -#include -#include - -class cParamDialog : public QDialog -{ - Q_OBJECT - - public: - cParamDialog(const QString &initalParamName, const QString &initalParamValue, const bool paramNameEditable, - QWidget *parent); - - // Returns the name of the param - QString GetParamName() const; - - // Returns the value of the param - QString GetParamValue() const; - - protected: - QString _paramName; - QString _paramValue; - - QHBoxLayout *_paramWidgetLayout{nullptr}; - QLabel *_paramLabel{nullptr}; - QLineEdit *_paramValueEdit{nullptr}; - - void InitBasicUIElements(const QString &initalParamName, const QString &initalParamValue); - void AddBasicWidgetsToLayout(); - - public slots: - virtual void SaveAndClose() = 0; -}; - -#endif diff --git a/src/runtime/src/ui/c_process_view.cpp b/src/runtime/src/ui/c_process_view.cpp deleted file mode 100644 index 7129b532..00000000 --- a/src/runtime/src/ui/c_process_view.cpp +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_process_view.h" - -#include "common/result_format/c_checker_bundle.h" -#include "common/result_format/c_parameter_container.h" -#include "common/result_format/c_result_container.h" - -#include "common/config_format/c_configuration.h" -#include "common/config_format/c_configuration_checker.h" -#include "common/config_format/c_configuration_checker_bundle.h" -#include "common/config_format/c_configuration_report_module.h" - -#include "c_checker_dialog.h" -#include "c_global_param_dialog.h" -#include "c_local_param_dialog.h" -#include "c_param_dialog.h" - -cProcessView::cProcessView(QWidget *parent) : QTreeWidget(parent) -{ - setContextMenuPolicy(Qt::CustomContextMenu); - - setSortingEnabled(false); - - connect(this, &QTreeWidget::customContextMenuRequested, this, &cProcessView::showContextMenu); -} - -const QString cProcessView::GetApplicationDir() -{ - return QCoreApplication::applicationDirPath() + "/"; -} - -void cProcessView::LoadConfiguration(cConfiguration *const configuration) -{ - _currentConfiguration = configuration; - clear(); - - cParameterContainer configParams = configuration->GetParams(); - std::vector paramNames = configParams.GetParams(); - - // Add Configuration Params - for (std::vector::const_iterator paramIt = paramNames.cbegin(); paramIt != paramNames.cend(); - paramIt++) - { - AddParamItemToRoot(*paramIt, configParams.GetParam(*paramIt)); - } - - // Add CheckerBundles - std::vector checkerBundle = configuration->GetCheckerBundles(); - for (std::vector::const_iterator bundleIt = checkerBundle.cbegin(); - bundleIt != checkerBundle.cend(); bundleIt++) - { - AddCheckerBundleItemToRoot(*bundleIt); - } - - // Add ReportModules - std::vector reportModules = configuration->GetReportModules(); - for (std::vector::const_iterator reportModuleIt = reportModules.cbegin(); - reportModuleIt != reportModules.cend(); reportModuleIt++) - { - AddReportModuleItemToRoot(*reportModuleIt); - } - - expandAll(); -} - -void cProcessView::AddConfiguration(cConfiguration *const configurationToAdd) -{ - if (nullptr == configurationToAdd) - return; - - // Add CheckerBundles - std::vector checkerBundle = configurationToAdd->GetCheckerBundles(); - for (std::vector::const_iterator bundleIt = checkerBundle.cbegin(); - bundleIt != checkerBundle.cend(); bundleIt++) - { - AddCheckerBundleItemToRoot(*bundleIt, true); - } - - // Add ReportModules - std::vector reportModules = configurationToAdd->GetReportModules(); - for (std::vector::const_iterator reportModuleIt = reportModules.cbegin(); - reportModuleIt != reportModules.cend(); reportModuleIt++) - { - AddReportModuleItemToRoot(*reportModuleIt, true); - } -} - -void cProcessView::showContextMenu(const QPoint &pos) -{ - QTreeWidgetItem *currentItem = itemAt(pos); - QMenu menu(this); - - if (nullptr != currentItem) - { - QVariant itemType = currentItem->data(0, ITEM_TYPE_ID); - - switch (itemType.toInt()) - { - case ITEM_PARAM: { - QAction *editAction = new QAction("Edit"); - menu.addAction(editAction); - QAction *deleteAction = new QAction("Delete"); - menu.addAction(deleteAction); - - connect(editAction, SIGNAL(triggered(void)), this, SLOT(EditParam())); - connect(deleteAction, SIGNAL(triggered(void)), this, SLOT(DeleteParam())); - break; - } - case ITEM_CHECKER_BUNDLE: { - QAction *addParameterAction = new QAction("Add Parameter"); - menu.addAction(addParameterAction); - menu.addSeparator(); - QAction *deleteAction = new QAction("Delete"); - menu.addAction(deleteAction); - - menu.addSeparator(); - QAction *moveUpAction = new QAction("Move Up"); - menu.addAction(moveUpAction); - QAction *moveDownAction = new QAction("Move Down"); - menu.addAction(moveDownAction); - - connect(addParameterAction, SIGNAL(triggered(void)), this, SLOT(AddLocalParam())); - connect(deleteAction, SIGNAL(triggered(void)), this, SLOT(DeleteCheckerBundle())); - connect(moveUpAction, SIGNAL(triggered(void)), this, SLOT(MoveUp())); - connect(moveDownAction, SIGNAL(triggered(void)), this, SLOT(MoveDown())); - - break; - } - case ITEM_CHECKER: { - QAction *addParameterAction = new QAction("Add Parameter"); - menu.addAction(addParameterAction); - - QAction *editAction = new QAction("Edit"); - menu.addAction(editAction); - QAction *deleteAction = new QAction("Delete"); - menu.addAction(deleteAction); - - connect(addParameterAction, SIGNAL(triggered(void)), this, SLOT(AddLocalParam())); - connect(editAction, SIGNAL(triggered(void)), this, SLOT(EditChecker())); - connect(deleteAction, SIGNAL(triggered(void)), this, SLOT(DeleteChecker())); - - break; - } - case ITEM_REPORT_MODULE: { - QAction *addParameterAction = new QAction("Add Parameter"); - menu.addAction(addParameterAction); - - menu.addSeparator(); - QAction *editAction = new QAction("Edit"); - menu.addAction(editAction); - QAction *deleteAction = new QAction("Delete"); - menu.addAction(deleteAction); - - menu.addSeparator(); - QAction *moveUpAction = new QAction("Move Up"); - menu.addAction(moveUpAction); - QAction *moveDownAction = new QAction("Move Down"); - menu.addAction(moveDownAction); - - connect(addParameterAction, SIGNAL(triggered(void)), this, SLOT(AddLocalParam())); - connect(deleteAction, SIGNAL(triggered(void)), this, SLOT(DeleteReportModule())); - connect(moveUpAction, SIGNAL(triggered(void)), this, SLOT(MoveUp())); - connect(moveDownAction, SIGNAL(triggered(void)), this, SLOT(MoveDown())); - - break; - } - } - } - else - { - QAction *addModuleAction = new QAction("Add Module"); - menu.addAction(addModuleAction); - - connect(addModuleAction, SIGNAL(triggered(void)), this, SLOT(AddModule())); - } - - menu.exec(mapToGlobal(pos)); -}; - -void cProcessView::mouseDoubleClickEvent(QMouseEvent *event) -{ - QTreeWidgetItem *clickedItem = itemAt(event->pos()); - - if (nullptr != clickedItem) - { - QVariant itemType = clickedItem->data(0, ITEM_TYPE_ID); - - if (itemType == ITEM_PARAM) - EditParam(clickedItem); - else if (itemType == ITEM_CHECKER) - EditChecker(clickedItem); - } -} - -cParamData cProcessView::GetParamDataFromItem(const QTreeWidgetItem *item) -{ - QVariant data = item->data(0, ITEM_DATA_ID); - cParamData paramData = data.value(); - return paramData; -} - -cCheckerBundleData cProcessView::GetCheckerBundleDataFromItem(const QTreeWidgetItem *item) -{ - QVariant data = item->data(0, ITEM_DATA_ID); - cCheckerBundleData checkerBundleData = data.value(); - return checkerBundleData; -} - -cCheckerData cProcessView::GetCheckerDataFromItem(const QTreeWidgetItem *item) -{ - QVariant data = item->data(0, ITEM_DATA_ID); - cCheckerData checkerData = data.value(); - return checkerData; -} - -cReportModuleData cProcessView::GetReportModuleDataFromItem(const QTreeWidgetItem *item) -{ - QVariant data = item->data(0, ITEM_DATA_ID); - cReportModuleData reportModuleData = data.value(); - return reportModuleData; -} - -void cProcessView::AddCheckerBundleItemToRoot(cConfigurationCheckerBundle *const checkerBundle, const bool expand) -{ - QTreeWidgetItem *newCheckerBundleItem = new QTreeWidgetItem(invisibleRootItem()); - - newCheckerBundleItem->setBackgroundColor(0, QColor(0, 120, 250, 128)); - newCheckerBundleItem->setText( - 0, QString("CheckerBundle %1").arg(checkerBundle->GetCheckerBundleApplication().c_str())); - newCheckerBundleItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); - newCheckerBundleItem->setData(0, ITEM_TYPE_ID, ITEM_CHECKER_BUNDLE); - - cCheckerBundleData newData; - newData.applicationName = checkerBundle->GetCheckerBundleApplication(); - newData.enabled = true; - - QVariant data; - data.setValue(newData); - - newCheckerBundleItem->setData(0, ITEM_DATA_ID, data); - - // Add Bundle Params - cParameterContainer configParams = checkerBundle->GetParams(); - std::vector paramNames = configParams.GetParams(); - - for (std::vector::const_iterator paramIt = paramNames.cbegin(); paramIt != paramNames.cend(); - paramIt++) - { - AddParamItem(newCheckerBundleItem, *paramIt, configParams.GetParam(*paramIt)); - } - - // Add Checkers - std::vector checkers = checkerBundle->GetCheckers(); - for (std::vector::const_iterator checkerIt = checkers.cbegin(); - checkerIt != checkers.cend(); checkerIt++) - { - AddCheckerItem(newCheckerBundleItem, *checkerIt); - } - - if (expand) - newCheckerBundleItem->setExpanded(true); - - addTopLevelItem(newCheckerBundleItem); -} - -void cProcessView::AddCheckerItem(QTreeWidgetItem *parentCheckerBundleItem, cConfigurationChecker *const checker) -{ - QTreeWidgetItem *newCheckerItem = new QTreeWidgetItem(parentCheckerBundleItem); - - newCheckerItem->setBackgroundColor(0, QColor(0, 120, 250, 32)); - - std::stringstream ssCheckerName; - ssCheckerName << checker->GetCheckerId().c_str() << " [" << PrintIssueLevel(checker->GetMinLevel()) << ", " - << PrintIssueLevel(checker->GetMaxLevel()) << "]"; - - newCheckerItem->setText(0, ssCheckerName.str().c_str()); - newCheckerItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); - newCheckerItem->setData(0, ITEM_TYPE_ID, ITEM_CHECKER); - - cCheckerData newData; - newData.checkerId = checker->GetCheckerId(); - newData.minLevel = checker->GetMinLevel(); - newData.maxLevel = checker->GetMaxLevel(); - - QVariant data; - data.setValue(newData); - - newCheckerItem->setData(0, ITEM_DATA_ID, data); - - // Add Checker Params - cParameterContainer configParams = checker->GetParams(); - std::vector paramNames = configParams.GetParams(); - - for (std::vector::const_iterator paramIt = paramNames.cbegin(); paramIt != paramNames.cend(); - paramIt++) - { - AddParamItem(newCheckerItem, *paramIt, configParams.GetParam(*paramIt)); - } - - parentCheckerBundleItem->addChild(newCheckerItem); -} - -void cProcessView::AddReportModuleItemToRoot(cConfigurationReportModule *const reportModule, const bool expand) -{ - QTreeWidgetItem *newReportModuleItem = new QTreeWidgetItem(invisibleRootItem()); - - newReportModuleItem->setBackgroundColor(0, QColor(0, 113, 77, 91)); - newReportModuleItem->setText(0, QString("ReportModule %1").arg(reportModule->GetReportModuleApplication().c_str())); - newReportModuleItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); - newReportModuleItem->setData(0, ITEM_TYPE_ID, ITEM_REPORT_MODULE); - - cReportModuleData newData; - newData.applicationName = reportModule->GetReportModuleApplication(); - newData.enabled = true; - - QVariant data; - data.setValue(newData); - - newReportModuleItem->setData(0, ITEM_DATA_ID, data); - - // Add Report Params - cParameterContainer configParams = reportModule->GetParams(); - std::vector paramNames = configParams.GetParams(); - for (std::vector::const_iterator paramIt = paramNames.cbegin(); paramIt != paramNames.cend(); - paramIt++) - { - AddParamItem(newReportModuleItem, *paramIt, configParams.GetParam(*paramIt)); - } - - if (expand) - newReportModuleItem->setExpanded(true); - - addTopLevelItem(newReportModuleItem); -} - -void cProcessView::AddParamItem(QTreeWidgetItem *parentItem, const std::string &name, const std::string &value) -{ - QTreeWidgetItem *newParamItem = new QTreeWidgetItem(parentItem); - - AddParamToItem(newParamItem, name, value); - - parentItem->addChild(newParamItem); -} - -void cProcessView::AddParamItemToRoot(const std::string &name, const std::string &value, const bool setAsFirstItem) -{ - QTreeWidgetItem *newParamItem = new QTreeWidgetItem(invisibleRootItem()); - - AddParamToItem(newParamItem, name, value); - - addTopLevelItem(newParamItem); - - if (setAsFirstItem) - { - int index = invisibleRootItem()->indexOfChild(newParamItem); - QTreeWidgetItem *child = invisibleRootItem()->takeChild(index); - invisibleRootItem()->insertChild(0, child); - } -} - -void cProcessView::AddParamToItem(QTreeWidgetItem *newItem, const std::string &name, const std::string &value) -{ - newItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); - newItem->setData(0, ITEM_TYPE_ID, ITEM_PARAM); - - SetParamDataOnItem(newItem, name, value); -} - -void cProcessView::SetParamDataOnItem(QTreeWidgetItem *item, const std::string &name, const std::string &value) -{ - cParamData newData; - newData.paramName = name; - newData.paramValue = value; - - QVariant data; - data.setValue(newData); - - item->setData(0, ITEM_DATA_ID, data); - - item->setText(0, QString("%1=\"%2\"").arg(name.c_str(), value.c_str())); -} - -QTreeWidgetItem *cProcessView::HasParamItem(const QTreeWidgetItem *parentItem, const std::string &name) -{ - QTreeWidgetItem *foundItem = nullptr; - for (int i = 0; i < parentItem->childCount(); i++) - { - QTreeWidgetItem *child = parentItem->child(i); - - cParamData paramData = GetParamDataFromItem(child); - if (paramData.paramName == name) - { - foundItem = child; - break; - } - } - - return foundItem; -} - -void cProcessView::AddLocalParam() -{ - if (selectedItems().count() > 0) - { - QTreeWidgetItem *selected = selectedItems().first(); - - cLocalParamDialog newDlg("newParameter", "", true, this); - - if (newDlg.exec() == QDialog::Accepted) - { - std::string paramName = newDlg.GetParamName().toLocal8Bit().data(); - std::string paramValue = newDlg.GetParamValue().toLocal8Bit().data(); - - QTreeWidgetItem *existingItem = HasParamItem(selected, paramName); - if (existingItem) - { - QMessageBox::StandardButton reply = QMessageBox::question( - this, "Overwrite Parameter", - "Do you want to overwrite\nthe parameter '" + QString::fromStdString(paramName) + "'\nwith '" + - QString::fromStdString(paramValue) + "'?", - QMessageBox::Yes | QMessageBox::No); - - if (reply == QMessageBox::Yes) - { - SetParamDataOnItem(existingItem, paramName, paramValue); - emit ChangeConfiguration(); - } - } - else - { - AddParamItem(selected, paramName, paramValue); - emit ChangeConfiguration(); - } - } - } -} - -void cProcessView::AddGlobalParam() -{ - cConfiguration currentConfig; - GetConfigurationFromView(¤tConfig); - - cGlobalParamDialog newDlg("InputFile", "", true, this, ¤tConfig); - - if (newDlg.exec() == QDialog::Accepted) - { - std::string paramName = newDlg.GetParamName().toLocal8Bit().data(); - std::string paramValue = newDlg.GetParamValue().toLocal8Bit().data(); - - QTreeWidgetItem *existingItem = HasParamItem(invisibleRootItem(), paramName); - if (existingItem) - { - QMessageBox::StandardButton reply = - QMessageBox::question(this, "Overwrite Parameter", - "Do you want to overwrite\nthe parameter '" + QString::fromStdString(paramName) + - "'\nwith '" + QString::fromStdString(paramValue) + "'?", - QMessageBox::Yes | QMessageBox::No); - - if (reply == QMessageBox::Yes) - { - SetParamDataOnItem(existingItem, paramName, paramValue); - emit ChangeConfiguration(); - } - } - else - { - AddParamItemToRoot(paramName, paramValue, true); - emit ChangeConfiguration(); - } - } -} - -void cProcessView::EditParam() -{ - if (selectedItems().count() > 0) - EditParam(selectedItems().first()); -} - -void cProcessView::EditParam(QTreeWidgetItem *item) -{ - cParamData paramData = GetParamDataFromItem(item); - - cParamDialog *newDlg; - if (item->parent() == nullptr) - { - cConfiguration currentConfig; - GetConfigurationFromView(¤tConfig); - newDlg = new cGlobalParamDialog(paramData.paramName.c_str(), paramData.paramValue.c_str(), false, this, - ¤tConfig); - } - else - newDlg = new cLocalParamDialog(paramData.paramName.c_str(), paramData.paramValue.c_str(), false, this); - - if (newDlg->exec() == QDialog::Accepted) - { - SetParamDataOnItem(item, newDlg->GetParamName().toLocal8Bit().data(), - newDlg->GetParamValue().toLocal8Bit().data()); - emit ChangeConfiguration(); - } -} - -void cProcessView::GetConfigurationFromView(cConfiguration *newConfiguration) -{ - QTreeWidgetItem *rootItem = invisibleRootItem(); - newConfiguration->Clear(); - - for (int i = 0; i < rootItem->childCount(); ++i) - { - QTreeWidgetItem *configurationItem = rootItem->child(i); - - // Params... - if (configurationItem->data(0, ITEM_TYPE_ID) == ITEM_PARAM) - { - cParamData paramData = GetParamDataFromItem(configurationItem); - newConfiguration->SetParam(paramData.paramName, paramData.paramValue); - } - // CheckerBundles... - else if (configurationItem->data(0, ITEM_TYPE_ID) == ITEM_CHECKER_BUNDLE) - { - cCheckerBundleData bundleData = GetCheckerBundleDataFromItem(configurationItem); - cConfigurationCheckerBundle *newBundle = newConfiguration->AddCheckerBundle(bundleData.applicationName); - - for (int j = 0; j < configurationItem->childCount(); ++j) - { - QTreeWidgetItem *checkerBundleItem = configurationItem->child(j); - - if (checkerBundleItem->data(0, ITEM_TYPE_ID) == ITEM_PARAM) - { - cParamData paramData = GetParamDataFromItem(checkerBundleItem); - newBundle->SetParam(paramData.paramName, paramData.paramValue); - } - // Checker... - else if (checkerBundleItem->data(0, ITEM_TYPE_ID) == ITEM_CHECKER) - { - cCheckerData checkerData = GetCheckerDataFromItem(checkerBundleItem); - cConfigurationChecker *newChecker = - newBundle->AddChecker(checkerData.checkerId, checkerData.minLevel, checkerData.maxLevel); - - for (int p = 0; p < checkerBundleItem->childCount(); ++p) - { - QTreeWidgetItem *checkerItem = checkerBundleItem->child(p); - - if (checkerItem->data(0, ITEM_TYPE_ID) == ITEM_PARAM) - { - cParamData paramData = GetParamDataFromItem(checkerItem); - newChecker->SetParam(paramData.paramName, paramData.paramValue); - } - } - } - } - } - else if (configurationItem->data(0, ITEM_TYPE_ID) == ITEM_REPORT_MODULE) - { - cReportModuleData moduleData = GetReportModuleDataFromItem(configurationItem); - cConfigurationReportModule *newModule = newConfiguration->AddReportModule(moduleData.applicationName); - - // Params for reportModules... - for (int j = 0; j < configurationItem->childCount(); ++j) - { - QTreeWidgetItem *reportModuleItem = configurationItem->child(j); - - if (reportModuleItem->data(0, ITEM_TYPE_ID) == ITEM_PARAM) - { - cParamData paramData = GetParamDataFromItem(reportModuleItem); - newModule->SetParam(paramData.paramName, paramData.paramValue); - } - } - } - } -} - -void cProcessView::DeleteParam() -{ - if (selectedItems().count() > 0) - DeleteItem(selectedItems().first(), false); -} - -void cProcessView::EditChecker() -{ - if (selectedItems().count() > 0) - EditChecker(selectedItems().first()); -} -void cProcessView::EditChecker(QTreeWidgetItem *item) -{ - QVariant data = item->data(0, ITEM_DATA_ID); - - cCheckerData moduleData = data.value(); - cCheckerDialog newDlg(moduleData.minLevel, moduleData.maxLevel, this); - - if (newDlg.exec() == QDialog::Accepted) - { - moduleData.minLevel = newDlg.GetMinLevel(); - moduleData.maxLevel = newDlg.GetMaxLevel(); - data.setValue(moduleData); - - std::stringstream ssIconName; - ssIconName << moduleData.checkerId << " [" << PrintIssueLevel(moduleData.minLevel) << ", " - << PrintIssueLevel(moduleData.maxLevel) << "]"; - - item->setData(0, ITEM_DATA_ID, data); - item->setText(0, ssIconName.str().c_str()); - - emit ChangeConfiguration(); - } -} - -void cProcessView::DeleteChecker() -{ - if (selectedItems().count() > 0) - DeleteItem(selectedItems().first(), true); -} - -void cProcessView::DeleteReportModule() -{ - if (selectedItems().count() > 0) - DeleteItem(selectedItems().first(), true); -} - -void cProcessView::SelectModuleFromFileSystem() -{ - QString pathToNewModule = QFileDialog::getOpenFileName(this, tr("Add module to configuration"), GetApplicationDir(), - tr("CheckerBundles, ReportModules (*.exe);;All files (*)")); - - if (pathToNewModule.length() > 0) - { - emit ExecuteProcessAndAddConfiguration(pathToNewModule); - } -} - -void cProcessView::MoveUp() -{ - QTreeWidgetItem *item = currentItem(); - int row = currentIndex().row(); - - if (item && row > 0) - { - QTreeWidgetItem *parent = item->parent(); - - if (nullptr == parent) - parent = invisibleRootItem(); - - int index = parent->indexOfChild(item); - QTreeWidgetItem *child = parent->takeChild(index); - - parent->insertChild((index > 1) ? index - 1 : 1, child); - } - - emit ChangeConfiguration(); -} - -void cProcessView::MoveDown() -{ - QTreeWidgetItem *item = currentItem(); - int row = currentIndex().row(); - - if (item && row > 0) - { - QTreeWidgetItem *parent = item->parent(); - - if (nullptr == parent) - parent = invisibleRootItem(); - - int index = parent->indexOfChild(item); - QTreeWidgetItem *child = parent->takeChild(index); - - parent->insertChild((index < topLevelItemCount()) ? index + 1 : index, child); - } - - emit ChangeConfiguration(); -} - -void cProcessView::DeleteCheckerBundle() -{ - if (selectedItems().count() > 0) - DeleteItem(selectedItems().first(), true); -} - -void cProcessView::DeleteItem(QTreeWidgetItem *item, bool deleteChilds) -{ - if (deleteChilds) - { - // Delete childs - for (int i = 0; i < item->childCount(); ++i) - { - QTreeWidgetItem *childItem = item->child(i); - - DeleteItem(childItem, deleteChilds); - } - } - - delete item; - - emit ChangeConfiguration(); -} diff --git a/src/runtime/src/ui/c_process_view.h b/src/runtime/src/ui/c_process_view.h deleted file mode 100644 index 14f2b9bf..00000000 --- a/src/runtime/src/ui/c_process_view.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CPROCESS_VIEW_H -#define CPROCESS_VIEW_H - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "common/result_format/c_issue.h" -#include "common/result_format/c_result_container.h" - -class cParamDialog; -class cCheckerDialog; -class cLocalParamDialog; -class cGlobalParamDialog; - -class cConfiguration; -class cConfigurationChecker; -class cConfigurationCheckerBundle; -class cConfigurationReportModule; - -// Container for parameters -struct cParamData -{ - std::string paramName; - std::string paramValue; -}; - -// Container for parameters -struct cCheckerBundleData -{ - std::string applicationName; - bool enabled; -}; - -// Container for parameters -struct cCheckerData -{ - std::string checkerId; - eIssueLevel minLevel; - eIssueLevel maxLevel; -}; - -// Container for parameters -struct cReportModuleData -{ - std::string applicationName; - bool enabled; -}; - -class cProcessView : public QTreeWidget -{ - Q_OBJECT - - protected: - static const unsigned int ITEM_TYPE_ID = Qt::UserRole + 1; - static const unsigned int ITEM_DATA_ID = Qt::UserRole + 10; - static const unsigned int ITEM_PARAM = 0; - static const unsigned int ITEM_CHECKER_BUNDLE = 1; - static const unsigned int ITEM_CHECKER = 2; - static const unsigned int ITEM_REPORT_MODULE = 3; - - // Pointer to current configuration which is hold in cRuntimeWindow - const cConfiguration *_currentConfiguration{nullptr}; - - public: - cProcessView(QWidget *parent = nullptr); - - /** - * Loads a configuration in the processView - **/ - void LoadConfiguration(cConfiguration *const configuration); - - /*! - * Adds a configuration - * - * \param configurationToAdd - */ - void AddConfiguration(cConfiguration *const configurationToAdd); - - void GetConfigurationFromView(cConfiguration *newConfiguration); - - private: - // Add checker bundle item to invisible root item - void AddCheckerBundleItemToRoot(cConfigurationCheckerBundle *const checkerBundle, const bool expand = false); - - // Add checker item to checker bundle item - void AddCheckerItem(QTreeWidgetItem *parentCheckerBundleItem, cConfigurationChecker *const checker); - - // Add report module item to invisible root item - void AddReportModuleItemToRoot(cConfigurationReportModule *const reportModule, const bool expand = false); - - // Add param item to parent item - void AddParamItem(QTreeWidgetItem *parentItem, const std::string &name, const std::string &value); - - // Add param item to invisible root item - void AddParamItemToRoot(const std::string &name, const std::string &value, const bool setAsFirstItem = false); - - void AddParamToItem(QTreeWidgetItem *newItem, const std::string &name, const std::string &value); - - // Set param data and text - void SetParamDataOnItem(QTreeWidgetItem *item, const std::string &name, const std::string &value); - - // Evaluates, if the parent item has a param item with the given name as child - // Returns the found param item or nullptr - QTreeWidgetItem *HasParamItem(const QTreeWidgetItem *parentItem, const std::string &name); - - cParamData GetParamDataFromItem(const QTreeWidgetItem *item); - cCheckerBundleData GetCheckerBundleDataFromItem(const QTreeWidgetItem *item); - cCheckerData GetCheckerDataFromItem(const QTreeWidgetItem *item); - cReportModuleData GetReportModuleDataFromItem(const QTreeWidgetItem *item); - - public slots: - // Add a local parameter - void AddLocalParam(); - - // Add a global parameter - void AddGlobalParam(); - - // Calls the dialog for the current selected item - void EditParam(); - - // Calls the dialog for a special item - void EditParam(QTreeWidgetItem *item); - - // Deletes the current selected item - void DeleteParam(); - - // Opens the dialog to add a new Module to the configuration - void SelectModuleFromFileSystem(); - - // Deletes the current selected item - void DeleteCheckerBundle(); - - // Calls the dialog for a special item - void EditChecker(); - - // Calls the dialog for a special item - void EditChecker(QTreeWidgetItem *item); - - // Deletes the current selected item - void DeleteChecker(); - - // Deletes the current selected item - void DeleteReportModule(); - - void DeleteItem(QTreeWidgetItem *item, bool deleteChilds = true); - - // Moves an item up - void MoveUp(); - - // Moves an item down - void MoveDown(); - - signals: - void ChangeConfiguration(); - - int ExecuteProcessAndAddConfiguration(QString processPath); - - private: - virtual void mouseDoubleClickEvent(QMouseEvent *event); - - protected: - void showContextMenu(const QPoint &pos); - - /** - * Get application directory - * - * @return directory, where the application is installed - */ - const QString GetApplicationDir(); -}; - -Q_DECLARE_METATYPE(cParamData) -Q_DECLARE_METATYPE(cCheckerBundleData) -Q_DECLARE_METATYPE(cCheckerData) -Q_DECLARE_METATYPE(cReportModuleData) - -#endif diff --git a/src/runtime/src/ui/c_runtime_window.cpp b/src/runtime/src/ui/c_runtime_window.cpp deleted file mode 100644 index dd918bee..00000000 --- a/src/runtime/src/ui/c_runtime_window.cpp +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#include "c_runtime_window.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/result_format/c_result_container.h" - -#include "../c_configuration_validator.h" -#include "c_process_view.h" - -const QString cRuntimeWindow::DEFAULT_CONFIG = "DefaultConfiguration.xml"; - -cRuntimeWindow::cRuntimeWindow(const std::string &strConfigurationFilepath, const std::string &inputFile, - QWidget *parent) - : QMainWindow(parent) -{ - - QAction *newAction = new QAction(tr("&New"), this); - newAction->setShortcuts(QKeySequence::New); - newAction->setStatusTip(tr("New configuration")); - connect(newAction, &QAction::triggered, this, &cRuntimeWindow::NewConfiguration); - - QAction *openAction = new QAction(tr("&Open..."), this); - openAction->setShortcuts(QKeySequence::Open); - openAction->setStatusTip(tr("Open configuration")); - connect(openAction, &QAction::triggered, this, &cRuntimeWindow::OpenConfigurationFile); - - QAction *saveAction = new QAction(tr("&Save..."), this); - saveAction->setShortcuts(QKeySequence::Save); - saveAction->setStatusTip(tr("Save configuration")); - connect(saveAction, &QAction::triggered, this, &cRuntimeWindow::SaveConfigurationFile); - - QAction *saveAsAction = new QAction(tr("&Save As..."), this); - saveAsAction->setStatusTip(tr("Save configuration")); - connect(saveAsAction, &QAction::triggered, this, &cRuntimeWindow::SaveAsConfigurationFile); - - auto fileMenu = menuBar()->addMenu(tr("&File")); - fileMenu->addAction(newAction); - fileMenu->addAction(openAction); - fileMenu->addAction(saveAction); - fileMenu->addAction(saveAsAction); - - QSplitter *splitter = new QSplitter(Qt::Horizontal); - - QWidget *processWidget = new QWidget(this); - QVBoxLayout *processLayout = new QVBoxLayout; - - QWidget *processButtonBar = new QWidget(this); - QHBoxLayout *processButtonBarLayout = new QHBoxLayout; - - QPushButton *addModule = new QPushButton(this); - addModule->setText("Add Module"); - QPushButton *addParameter = new QPushButton(this); - addParameter->setText("Add global Parameter"); - - processButtonBarLayout->addWidget(addParameter); - processButtonBarLayout->addWidget(addModule); - processButtonBarLayout->setContentsMargins(2, 2, 2, 2); - processButtonBar->setLayout(processButtonBarLayout); - - QLabel *processLabel = new QLabel(processWidget); - processLabel->setText("Runtime Configuration"); - processLabel->setStyleSheet("font-weight: bold;"); - _processView = new cProcessView(this); - _processView->setSortingEnabled(false); - _processView->setContentsMargins(QMargins(0, 0, 0, 0)); - _processView->setAlternatingRowColors(true); - _processView->setSelectionBehavior(QAbstractItemView::SelectRows); - _processView->setRootIsDecorated(true); - _processView->setHeaderHidden(true); - - processLayout->addWidget(processLabel); - processLayout->addWidget(_processView, 2); - processLayout->addWidget(processButtonBar, 0); - processLayout->setContentsMargins(3, 6, 3, 3); - processWidget->setLayout(processLayout); - splitter->addWidget(processWidget); - - QWidget *outputWidget = new QWidget(this); - QVBoxLayout *outputLayout = new QVBoxLayout; - outputLayout->setContentsMargins(3, 6, 3, 3); - outputWidget->setLayout(outputLayout); - splitter->addWidget(outputWidget); - - _currentConfiguration = cConfiguration(); - - setCentralWidget(splitter); - - connect(addModule, SIGNAL(pressed()), _processView, SLOT(SelectModuleFromFileSystem())); - connect(addParameter, SIGNAL(pressed()), _processView, SLOT(AddGlobalParam())); - - connect(_processView, SIGNAL(ChangeConfiguration()), this, SLOT(OnChangeConfiguration())); - connect(_processView, SIGNAL(ExecuteProcessAndAddConfiguration(QString)), this, - SLOT(ExecuteProcessAndAddConfiguration(QString))); - - // Set the size of the application of the half size of desktop - QSize quarterDesktopSize = QDesktopWidget().availableGeometry(this).size() * 0.5f; - resize(quarterDesktopSize); - - if (!strConfigurationFilepath.empty()) - { - LoadConfiguration(&_currentConfiguration, strConfigurationFilepath.c_str()); - } - else - { - // Standardkonfiguration laden - QString defaultConfigPath = GetApplicationDir() + "/" + DEFAULT_CONFIG; - if (CheckIfFileExists(defaultConfigPath.toStdString())) - { - LoadConfiguration(&_currentConfiguration, defaultConfigPath); - } - // Neue Konfiguration erstellen - else - { - CreateNewConfiguration(&_currentConfiguration); - } - } - - // Apply input file settings from command line - if (!inputFile.empty()) - { - _currentConfiguration.SetParam(PARAM_INPUT_FILE, inputFile); - OnChangeConfiguration(); - } - - ShowConfiguration(&_currentConfiguration); -} - -const QString cRuntimeWindow::GetApplicationDir() -{ - return QCoreApplication::applicationDirPath(); -} - -const QString cRuntimeWindow::GetWorkingDir() -{ - return QString::fromStdString(fs::current_path().string()); -} - -bool cRuntimeWindow::ValidateAndWrite(cConfiguration &configuration, const std::string &filePath) -{ - std::string message = ""; - if (!cConfigurationValidator::ValidateConfiguration(&configuration, message)) - { - std::stringstream ssDetails; - - ssDetails << "Configuration is invalid." << std::endl << std::endl; - ssDetails << "Message: " << std::endl; - ssDetails << message; - ssDetails << std::endl << std::endl; - ssDetails << "Please fix it before saving configuration file"; - - QMessageBox::warning(this, tr("Error"), tr(ssDetails.str().c_str()), QMessageBox::Ok); - return false; - } - configuration.WriteConfigurationToFile(filePath); - return true; -} - -void cRuntimeWindow::OpenConfigurationFile() -{ - QString filePath = - QFileDialog::getOpenFileName(this, tr("Open Configuration"), GetWorkingDir(), tr("configurations (*.xml)")); - - LoadConfiguration(&_currentConfiguration, filePath); - ShowConfiguration(&_currentConfiguration); -} - -bool cRuntimeWindow::SaveConfigurationFile() -{ - if (!_currentConfigurationPath.isEmpty()) - { - if (_configurationChanged) - { - QFileInfo fileInfo(_currentConfigurationPath); - - // If we opened a default configuration the user cannot overwrite this - if (fileInfo.fileName().toLower() == DEFAULT_CONFIG.toLower()) - { - // Open Dialog - return SaveAsConfigurationFile(); - } - else - { - QMessageBox::StandardButton reply = QMessageBox::NoButton; - reply = QMessageBox::question(this, "Overwrite Configuration", - "Do you want to overwrite:\n" + fileInfo.fileName(), - QMessageBox::Yes | QMessageBox::No); - - if (reply == QMessageBox::Yes) - { - UpdateConfiguration(); - bool validation_result = false; - validation_result = - ValidateAndWrite(_currentConfiguration, _currentConfigurationPath.toLocal8Bit().data()); - if (!validation_result) - { - return false; - } - _configurationChanged = false; - SetupWindowTitle(); - return true; - } - else - { - // Open Dialog - return SaveAsConfigurationFile(); - } - } - } - else - return true; - } - // Open Dialog - return SaveAsConfigurationFile(); -} - -bool cRuntimeWindow::SaveAsConfigurationFile() -{ - QString filePath = - QFileDialog::getSaveFileName(this, tr("Save Configuration"), GetWorkingDir(), tr("configurations (*.xml)")); - - if (!filePath.isEmpty()) - { - QFileInfo fileInfo(filePath); - - if (fileInfo.fileName().toLower() == DEFAULT_CONFIG.toLower()) - { - QMessageBox::information( - this, "Save Configuration", - "It is not possible to overwrite a default configuration.\nPlease enter a different file name.", - QMessageBox::Ok); - - // Open Dialog - bool saved = SaveAsConfigurationFile(); - return saved; - } - - UpdateConfiguration(); - - _currentConfigurationPath = filePath; - bool validation_result = false; - validation_result = ValidateAndWrite(_currentConfiguration, filePath.toLocal8Bit().data()); - if (!validation_result) - { - return false; - } - - _configurationChanged = false; - SetupWindowTitle(); - - return true; - } - - return false; -} - -void cRuntimeWindow::SetupWindowTitle() -{ - std::string build_version = BUILD_VERSION; - std::string build_date = BUILD_DATE; - std::string config_ui_name = "ConfigGUI (v" + build_version + ", " + build_date + ")"; - if (!_currentConfigurationPath.isEmpty()) - { - QFileInfo fileInfo(_currentConfigurationPath); - QString applicationTitle = - QString(" - %1.xml%2").arg(fileInfo.baseName()).arg(_configurationChanged ? "*" : ""); - applicationTitle = QString::fromStdString(config_ui_name) + applicationTitle; - setWindowTitle(applicationTitle); - } - else - { - config_ui_name += " - New Configuration*"; - setWindowTitle(QString::fromStdString(config_ui_name)); - } -} - -void cRuntimeWindow::UpdateConfiguration() -{ - _processView->GetConfigurationFromView(&_currentConfiguration); -} - -void cRuntimeWindow::LoadConfiguration(cConfiguration *const configuration, const QString &strConfigurationFilepath) -{ - if (!strConfigurationFilepath.isNull()) - { - _currentConfiguration.Clear(); - - if (!cConfiguration::ParseFromXML(configuration, strConfigurationFilepath.toLocal8Bit().data())) - { - std::stringstream ssError; - ssError << "Couldn't load the configuration '" << strConfigurationFilepath.toLocal8Bit().data() - << "'. It occured a problem while parsing. Abort."; - - QMessageBox::warning(this, tr("Load Configuration"), ssError.str().c_str(), QMessageBox::Ok); - } - else - { - _currentConfigurationPath = strConfigurationFilepath; - } - } -} - -void cRuntimeWindow::CreateNewConfiguration(cConfiguration *const configuration) -{ - _currentConfigurationPath = ""; - - configuration->Clear(); - configuration->SetParam(PARAM_INPUT_FILE, ""); - - OnChangeConfiguration(); -} - -void cRuntimeWindow::ShowConfiguration(cConfiguration *const configurationToBeShown) -{ - - _processView->LoadConfiguration(configurationToBeShown); - - SetupWindowTitle(); -} - -void cRuntimeWindow::NewConfiguration() -{ - CreateNewConfiguration(&_currentConfiguration); - ShowConfiguration(&_currentConfiguration); -} - -void cRuntimeWindow::OnChangeConfiguration() -{ - _configurationChanged = true; - - SetupWindowTitle(); -} - -int cRuntimeWindow::ExecuteProcessAndAddConfiguration(const QString &processPath) -{ - QProcess process; - QFileInfo fileInfo(processPath); - - QString processName = fileInfo.fileName(); - QString processDir = fileInfo.absoluteDir().absolutePath(); - - std::string processExec = processName.toLocal8Bit().data(); - - std::cout << "-----" << std::endl; - std::cout << "Start to read default configuration from: " << std::endl; - std::cout << processPath.toLocal8Bit().data() << std::endl; - - emit Log(QString("-------------------------------------------------------------")); - emit Log(QString("---- Start to read default configuration")); - emit Log(QString("-------------------------------------------------------------")); - - if (!StringEndsWith(processExec, ".exe")) - processExec = processExec.append(".exe"); - - if (!CheckIfFileExists(processPath.toStdString())) - { - emit Log(QString("> Application '%1' does not exist. Abort.").arg(processPath.toStdString().c_str())); - return -1; - } - - process.start(processName, {"--defaultConfig"}); - process.setWorkingDirectory(processDir); - process.waitForStarted(); - - emit Log(QString("> Start application '%1' to generate report...").arg(processExec.c_str())); - - double dTimePassedToGenerateDefaultReport = 0; - - // Check if process is still running or we waited too long.... - while (process.state() == QProcess::Running && dTimePassedToGenerateDefaultReport < 100.0) - { - process.waitForFinished(300); - dTimePassedToGenerateDefaultReport += 3.0f; - - QString stdOut = QString(process.readAllStandardOutput()); - QString stdErr = QString(process.readAllStandardError()); - - std::cout << stdOut.toLocal8Bit().data() << std::endl; - std::cout << stdErr.toLocal8Bit().data() << std::endl; - } - - QString reportFile = processExec.c_str(); - reportFile = reportFile.replace(".exe", ".xqar"); - QString confgurationFile = processExec.c_str(); - confgurationFile = confgurationFile.replace(".exe", ".xml"); - - cConfiguration newConfigurationToAdd; - - if (CheckIfFileExists(confgurationFile.toLocal8Bit().data(), false)) - { - emit Log(QString("> Configuration '%1' found.").arg(confgurationFile.toLocal8Bit().data())); - emit Log(QString("> Extract configuration.")); - - cConfiguration::ParseFromXML(&newConfigurationToAdd, confgurationFile.toLocal8Bit().data()); - } - else if (CheckIfFileExists(reportFile.toLocal8Bit().data(), false)) - { - emit Log(QString("> Report '%1' found.").arg(reportFile.toLocal8Bit().data())); - emit Log(QString("> Extract configuration...")); - - cResultContainer report; - report.AddResultsFromXML(reportFile.toLocal8Bit().data()); - - report.ConvertReportToConfiguration(&newConfigurationToAdd); - } - else - { - emit Log(QString("> Report '%1' or configuration '%2' does not exist. Abort.") - .arg(reportFile.toLocal8Bit().data(), confgurationFile.toLocal8Bit().data())); - return -1; - } - - UpdateConfiguration(); - - _currentConfiguration.AddConfiguration(&newConfigurationToAdd); - - if (nullptr != _processView) - { - _processView->LoadConfiguration(&_currentConfiguration); - emit Log(QString("> Configuration added.")); - OnChangeConfiguration(); - } - - return 0; -} diff --git a/src/runtime/src/ui/c_runtime_window.h b/src/runtime/src/ui/c_runtime_window.h deleted file mode 100644 index 53287dc5..00000000 --- a/src/runtime/src/ui/c_runtime_window.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -#ifndef CRUNTIME_WINDOW_H -#define CRUNTIME_WINDOW_H - -#include - -#include "common/config_format/c_configuration.h" -#include "common/qc4openx_filesystem.h" - -class cProcessView; -class cProcessLog; -class cRuntimeControl; - -// Implementation of the main window -class cRuntimeWindow : public QMainWindow -{ - Q_OBJECT - - protected: - cProcessView *_processView{nullptr}; - - cConfiguration _currentConfiguration; - QString _currentConfigurationPath; - - bool _configurationChanged{false}; - - public: - cRuntimeWindow(const std::string &configurationFilePath, const std::string &inputFile, QWidget *parent = 0); - - /* - * Loads a configuration from file to datastructure - * \param strConfigurationFilepath: File path of the configuration which should be loaded - */ - void LoadConfiguration(cConfiguration *const configuration, const QString &strConfigurationFilepath); - - /* - * Shows a configuration to the user - * \param currentConfiguration: Configuration which should be shown - */ - void ShowConfiguration(cConfiguration *const currentConfiguration); - - // Updates the internal configuration - void UpdateConfiguration(); - - private slots: - // Open result file - void OpenConfigurationFile(); - - // SaveAs configuration file - bool SaveAsConfigurationFile(); - - bool ValidateAndWrite(cConfiguration &, const std::string &); - - // Save configuration file - bool SaveConfigurationFile(); - - // Creates a new Configuration - void NewConfiguration(); - - // Called if a confuration was changed. - void OnChangeConfiguration(); - - /*! - * Execute process and read default configuration - * - * \param processPath - * \return Positive number if everything is okay - */ - int ExecuteProcessAndAddConfiguration(const QString &processPath); - - signals: - void Finished(); - - void Log(QString log); - - private: - // Filename of the default configuration - static const QString DEFAULT_CONFIG; - - /** - * Get application directory - * - * @return directory, where the application is installed - */ - const QString GetApplicationDir(); - - /** - * Get working directory - * - * @return directory, from where the application is started - */ - const QString GetWorkingDir(); - - // Changes the window title of the application - void SetupWindowTitle(); - - // Creates an empty configuration - void CreateNewConfiguration(cConfiguration *const configuration); -}; - -#endif diff --git a/test/function/CMakeLists.txt b/test/function/CMakeLists.txt index d63cec33..d5291155 100644 --- a/test/function/CMakeLists.txt +++ b/test/function/CMakeLists.txt @@ -9,11 +9,6 @@ add_subdirectory(report_modules) add_subdirectory(result_pooling/src) add_subdirectory(result_format/src) -if (WIN32) - # FIXME: We need some adaptions that this works in Linux - add_subdirectory(runtime/src) -endif(WIN32) - add_custom_target(INSTALL_TEST_REFERENCES ALL) add_custom_command( @@ -30,10 +25,6 @@ add_custom_command( ${CMAKE_CURRENT_SOURCE_DIR}/result_pooling/files ${REFERENCE_FILES_INSTALL_DIR}/function/result_pooling - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_SOURCE_DIR}/runtime/files - ${REFERENCE_FILES_INSTALL_DIR}/function/runtime - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/../../doc/schema ${REFERENCE_FILES_INSTALL_DIR}/doc/schema diff --git a/test/function/runtime/files/DemoCheckerBundle_config.xml b/test/function/runtime/files/DemoCheckerBundle_config.xml deleted file mode 100644 index fc533a59..00000000 --- a/test/function/runtime/files/DemoCheckerBundle_config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/test/function/runtime/files/XodrSchemaChecker_config.xml b/test/function/runtime/files/XodrSchemaChecker_config.xml deleted file mode 100644 index 98824132..00000000 --- a/test/function/runtime/files/XodrSchemaChecker_config.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - diff --git a/test/function/runtime/files/XoscSchemaChecker_config.xml b/test/function/runtime/files/XoscSchemaChecker_config.xml deleted file mode 100644 index e7f96f90..00000000 --- a/test/function/runtime/files/XoscSchemaChecker_config.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - diff --git a/test/function/runtime/src/CMakeLists.txt b/test/function/runtime/src/CMakeLists.txt deleted file mode 100644 index e2056d58..00000000 --- a/test/function/runtime/src/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2023 CARIAD SE. -# -# This Source Code Form is subject to the terms of the Mozilla -# Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - - -set(TEST_NAME runtime_tester) - -set_property(GLOBAL PROPERTY USE_FOLDERS true) - -find_package(XercesC REQUIRED) - -include_directories(${TEST_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../../_common - ${XercesC_INCLUDE_DIRS}) - -add_executable(${TEST_NAME} - ${CMAKE_CURRENT_SOURCE_DIR}/../../_common/helper.cpp - ${TEST_NAME}.cpp) - -add_test(NAME ${TEST_NAME} - COMMAND ${TEST_NAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../../ -) - -target_link_libraries(${TEST_NAME} - PRIVATE - GTest::gtest_main - $<$:pthread> - $<$:stdc++fs> - ${XercesC_LIBRARIES} -) - -target_compile_definitions(${TEST_NAME} - PRIVATE QC4OPENX_DBQA_BIN_DIR="${QC4OPENX_DBQA_DIR}/bin" - PRIVATE QC4OPENX_DBQA_RUNTIME_TEST_WORK_DIR="${CMAKE_CURRENT_BINARY_DIR}/../.." - PRIVATE QC4OPENX_DBQA_RUNTIME_TEST_REF_DIR="${REFERENCE_FILES_INSTALL_DIR}/function/runtime") - -set_target_properties(${TEST_NAME} PROPERTIES FOLDER test/function) diff --git a/test/function/runtime/src/runtime_tester.cpp b/test/function/runtime/src/runtime_tester.cpp deleted file mode 100644 index 7c2fd7f8..00000000 --- a/test/function/runtime/src/runtime_tester.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 CARIAD SE. - * - * This Source Code Form is subject to the terms of the Mozilla - * Public License, v. 2.0. If a copy of the MPL was not distributed - * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -#include "gtest/gtest.h" - -#include "helper.h" - -#define MODULE_NAME "ConfigGUI" - -class cTesterRuntime : public ::testing::Test -{ - public: - std::string strTestFilesDir = std::string(QC4OPENX_DBQA_RUNTIME_TEST_REF_DIR); - std::string strWorkingDir = std::string(QC4OPENX_DBQA_RUNTIME_TEST_WORK_DIR); -}; - -TEST_F(cTesterRuntime, CmdHelp) -{ - std::string strResultMessage; - - TestResult nRes = ExecuteCommand(strResultMessage, MODULE_NAME, "-h"); - ASSERT_TRUE_EXT(nRes == TestResult::ERR_NOERROR, strResultMessage.c_str()); - - nRes |= ExecuteCommand(strResultMessage, MODULE_NAME, "--help"); - ASSERT_TRUE_EXT(nRes == TestResult::ERR_NOERROR, strResultMessage.c_str()); -} - From d9be06cc121b586ea35fc50836c14d931bfe2f09 Mon Sep 17 00:00:00 2001 From: romanodanilo <62891297+romanodanilo@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:20:38 +0200 Subject: [PATCH 02/17] Fix viewer interface (#135) Signed-off-by: romanodanilo Signed-off-by: romanodanilo <62891297+romanodanilo@users.noreply.github.com> --- doc/manual/images/add_checker_bundle.png | Bin 67739 -> 0 bytes doc/manual/images/config_gui.png | Bin 50232 -> 0 bytes doc/manual/viewer_interface.md | 4 ++-- examples/viewer_example/viewer_example.cpp | 9 ++++----- include/common/config_format/c_configuration.h | 2 +- include/viewer/i_connector.h | 2 +- src/common/src/result_format/c_issue.cpp | 4 ++-- .../src/ui/c_report_module_window.cpp | 2 +- .../src/ui/c_report_module_window.h | 2 +- 9 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 doc/manual/images/add_checker_bundle.png delete mode 100644 doc/manual/images/config_gui.png diff --git a/doc/manual/images/add_checker_bundle.png b/doc/manual/images/add_checker_bundle.png deleted file mode 100644 index 4b1cead44c17b59fff5e36a29f2057b92636d5f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67739 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%0A6IPGKIPffYG=}I99({@TRw!oZ#|Z4 zNB`{Pj4qWcqw(w7qU3CCG#pjgNE4oh>!uA-DCXcU80T>D^t4w_(q}Dr9+z6CAytsR z_#R%_h0)|IOvmW23e~kDUKy_(H)-ih_Wk{UcIMVQR9Vqj53{J zK4*LlzTfnb-*SNnC|R&}J8@EutCwL4yWgh}*Qr~#PZjE-T8*gu>O;);u!25CCa0n* zXbwIJTbzd-Ua+`hok???gH%?N5m(dj+uotYnlxF2agK-u(N@78te`2KZ{JOOh|RT8 zYVMe>&uDY~VGK3pmO-CTcXahHq3P(1mHc5?8Cy8Z8H8+LU%T_`)9|*Lsl^C^nmHWuIAZGT&z2vtx1<}ESY14=MrOnu@ zH-9I1SGFdrE1~-{o9sN!9l$j`Xeq;PI z{CqYvzLSW52KSNcFG*H;P%zI>D&L~FVdnI3MB^JS|Lie4_E|YTh5Ktz^9<~!BWF6T z;vOZMZ!1=z3k1{+UR-@?Qio7QdS4uzx{w7~RBC53$zo?5$oL)l#+9s{&#!DNNf1mLi6^c~s_C`NGa_Bl`>T4DiS#T?XSg@tHY)&VhDuPdgWN zdu&~6B|dd&L{Peod6912ZW!sm@oUshf#*h_<43Pb*$g>u_U`b!yYX0;{xip0pdKBR%glE8QWL#(Gb{&dXi>;+=Mifj!IWE^HV;X=Baf86 zr=Hk=C`j(N^Ij4%b6A^|%udg*+&6A3?{(pE)Cm4rSFEvgfsg5qUE#01A@@9Psl@iRWMrD<+4wJrbea3% zbGrw$$~X7BOnFBl#~pHE>)*C+6a!}AT*r04`Bt%wqO-7zs?Sc$Lka=f@{ymg-!WSpsRXpoEPqheZbQu^6IpSO#EqPjv>riK<47A3#_+>K^S z^TX7T0}O@9*8cap&Ii2ZqF~B z1<&waZ7LruP*HAI28#x`IUa%!nxEdGbON`|Lz%@4{67Z0zqv?%Jr-O#=A>d~_a<0n zq3Jgn@EI1-1eypR=`Lcx?}WpMD-NnS#w+T;CK@TZ!zlL07630H1lzUiy#=eoy5O=^JeDX^}^<`tg71bSe3k9S)T~($fE0^d=2(Xvxh8ys ze8Viceh^+>Gx?ax-WNwR=-u12>g~r?<-*93l9ZspCr$h<2%PXPdk_SQp1T5^(1y64 z3>E+>JAVm@uK59HDMzbQx%f_BIWsX3xhF{r6q0NMXmBrh)+8={-%Gn1_BR8ES#YUq z#kl;sGcXrWOSEvh{yk9F0u&0IcmFznMb7TL_aH4SxD}{GdVVPBMD-AGc7_|xs_zSr zd)HLApN_NdjWdq(o2dX!)1D+8e{c{8>;!};ht)5N&Xv*c7jWajAQe5HzR=)}8(X{L z?r}u`!u{}$k-%YVw%Gjq58zSlB%xT~2Ht-FfoLNtpiW4*<3uJuz6UM+)5A82m?>6Y zLUy9ZBlDb}BJ`Shr=p8uEPhgtToJVvs+tO}VbZEM(pMM+*(XPl^%HucK>hH;0#2TS z!T@-B@}`a-z3{5%vPg{324hm|a4j9MF8Id!@uw}U0I7kjq%7M@39n+QEMVU_fKyR$ zoFf$Wjjhqt98Nx+guAZpdd!d?*Gg_L&JKAJ{i})cHSp7VsNWxiqz7NOfN9d45{F=m* zS@Y0V?n+~-_>|6n^u(;J-0t2c{rw=jIjPG78Y2aybgUi7jOlnP6-$uiTIrQhs0ZNTSmA?8tmq`VGGc zpX7g5@2aIt1xwdLPkvjMWOie#5APjjd-f!295!}c{4xu;!%!?p5l`&?1m>=9Mdo=5 zagG6Po?C?XzCxwo+D0$<%&HD%wT#;mQugAq8_v9Cfw@&8<9r)E&t#vIG7bP2)4Vh+xElC1u260@{Y zunYNxo;BDX{A$h_G{WHEU*KMf`V~OGR{Sptu~wjq({-O~<{-IE@s<_NDKE0Q>~3NO z@?ezd?dn2aB;<1Yo@gBC(#!fFcLNp0`Ou@4ZZ2S*u-IKqtnVajL57=>gWh$dly^Sn z{f3R8zd>SPi2GM91r@{+t~ZN zJ0J@c^dc@!!j@50!~Oa<=d|;xn=lf3)pU3xWAB0oh_-IXo@U~+WpS$ z9UDwez0S!L#GnP^$kO=ZVsG;aik>sxW#6cPxXoEg7Uoj3MBHH=08M51f3A%cE{145 z$}hCHuVA94b2FFtQolN!tR%c-YE4Da>298+CLhA!YuLZpB$=vN?)F7;%NH~=HYLZK0a&G~ zk0(-1kYz8ercp5+;UjEryAN){?wV@l<@w z@Vi@DXxW=3B6QAnh&@#6Te0rEzrTv1XmrY}nrxyZ`P-Rq>v;OE8sk3wNM0=Qe(aoJ zjNF-Zb;)9;cOdFQ85qP#Z9Q%!g`3o==(@EQk~PwPPBME?5fx{4xV5V~0?D_bgIafv zYB<6tH7F1t{p0ufP3(x&u@#SqHz&-WVB~)w7V}bFG5ySK?G6QVC1yF@Y{6argVnJ_ z6(z|(&MrZdUeFx3r|>9qT~!w6A-qT!sR=k41v4$DMOon$ep{9gyy;Xl^oZfh)g1kR zyPwW4=U6K=sHa=vx%5-$cBjqxj*R)-`E<_)O!fJ$fUs_za|Qyh4ju3L`Q*-bm`vVr z*z#-QDb{jWVHO%3^dHqkz$Hg42QI{LIimAw z2MgHRKQD&Pf#m?*e1R7g7$oI~v_6q1%!c#=8&MEPLfbAMdY5hxK!1dR^GM zt5m90++C!cKAE;PdX5med0o04V&&s}F73`IH|?TLsU`b@{z}=q2x9H@I*4%|MotT_ zLQB3f|8W7^Qb+^lXM3VqX-C~(eVv+x8g1#$kLz4+xR{3x$gY-P>VLp)NMo6OMs8zu z!%kB_xZ`4HJN;JZuEQ+t#mvd8j^>HWqb@iRaY@X6x1o`5+vJ%FK~*7)Ogbd!$>6l7?)=@`s+issY$MyJJ%7pqc*wi`0hy4qA~<#wCr<<^k`ehSLjM z?zYr-&nO!igkB{O#lbzeDBvWeB-(u8x1Ko~sHT z5t6b-tQKpwAIc<|ItO7Vt(+TBvGVl$=oTKKg{$9gi-soLM;jbw>($`(hXl$he|e!o z;LC$s`rJ4Be_qJ#xjKGa^ zT}{Oe+a^L=FT@WpWYU-Utmc(809RxRrxn+1Zarv1ZU(N}&svW#{6Y5V^bZ&pq9_@S ziZP}s=01M!qMoMw>0(dZ`LIlHg%$~PVC=4?fR8mxaup)ObZ`1FA)5EP(R%X?87WtG zrYdE5Cu9umI`v|oGNwHJ_pXf4Th=~5u7N&^1dVFtd`=T0HcVj$-zWJ^C;m=8Q}*CN z&)GO?7kJZI43`Jom)HTvnMZX>&<3g5Ja?X)&z2I`mG)Yfd7e2aAwF994e3=`)`9;j z#_d{mV{(t1DMnO$fM{cdxII#PlJR+hxGmz-NL&K#*O)WnK1O z5;Us1F+^paF>PMLc>KPj?glf5*MpIbx0dXyFLXpV=9qrgkhn;JFgg3?_WjeO)x<1< zUFWm}e@Hv?jC`)>O<{cHH>}VGF|tEpNOdyc>Z5sD5*te#uWg1vGTVOR8Gjk%W(hXw zx!x%>Wn2l$^pIenbn%l>0*o>To*0>yw6TQ*V37FLNO_^i@DP><%UzH+govz{-D-5Y zmANqRW}RuL{t279Xkmy$iam0x>wZ%g%=k^EuFDGBJU~gJfy2eI|)(=yw zH}93)Dl)RKZD4%e8jlQYnTmY*-JcklQ(bMEr#Cs=nkLd7URB=QwP8EhXY>QEzFW-r@sR{%UYp>%@MDjCx``M8+(U z-q4S@_C5%k9tR|wvPJWX9&L{%w`OLWRDpOe-*FKta~7*B@ujv=ac8X$k5+=i#X zNIwPG>lpe@9h-!3hU^U|L_@x0oSE*C%(pgFk5Gm7s;CGZu6k6%xZVSPz?r-|<@7ox z(I?5ocr}Ws!L;OY^UxM&H&Defo)O$WLpBTW-dlkj28c zP!uw?1V6VpU!-6xb2pI<`Ku+?*C;nqaNnsX9kPcJbVT$SFx0U+7JORc_QMEbPQy_v z-*Gnp5cKqUFaUqR56^Y5g~|?Yni~0+a$XkLRH58|4z#{0LdnMRMWz2w%|hUu|3BE! z;50|wNB?(C7_&Cr80M45P7$}-bb@NO0v94GH#Npf+HK+Qs_xfU(#80!2u>Y#!8{#W zqPlJa$-L_#B1s6N{I}f;;h;yT{NU5$EJLSdzIB_!b|ns@!+yvrOQB5oRi zxS-G!1*ju#wKYF8wr-Hnala|*nhcjHX`4YnFoVBgZ{mjM<}U+ra!+RHCJ>?HKzLVI zuF#Hk-VanHEPKIL_bLqKOVo(fGs5CmzzIVK03VYBVI6RIq2F&=k+{2NNF1hL$HvXA z=PQv$-jS!`@Bw@RO{78qQrG6^_G)p`t7xIb7TiH*;CiddKl1?hWN;v8g7*=N+BAnA zFWo|(8L`1@l!s8HT%dUss{>j@6dXPPY&4Ohqvrj|mH>oG5$pCT8$FgV`wbWQYviBA z7y=Go4g)ZSV@8nme3#92_qxLb&M-cs&L1ZJq#9hp2Qtk2-Bt<_yM(kd0G&Z2d7a%V1a}9L)g-3~p>}>#Lb)C4h8Fq<0aQg}{>BriQRlSs&hgtGl z8#cc^PJ$!K-m_Ut$YtZYrShtYN?zi$}jkvvAsMQ&ie-@Dx*L~c>&ZsG<{AF_BX zjX$2ek&9q_K<)QpJ}E(xL`X;wI(V}j91?vDf*@eQp3sJatrKRYuCL@V_*;9wvKvy4 z_6fT6%I_wF7p!2UiU!VZcySBapzhTfs2SWued8EmbOgAkQC~1oj19`rXP;HTNb{K_C}scdrX5 zJi?r^Hh8_uz2Esthh}VMrIrthgDr{Me_9-bKzMx21uiZKi5Nik2BmjY!5K@cM?3WfCmOC=}-d4TR5lBLHz;0(0{InUXwVYUPKhIreV*Jn|s?GuZ8w-8@D)swT~o13=$wF4)wu-Yq2%}xfGcn zR&RVfA~QU|#i5sXw&m8^HW-#VE`o>SU;xR0`57Px)^W6oH}8O7hC)E}I6dAdEJ+0d zX8?sFf#gA`#2+FdAQyloHJOJD!OTR?8+{r!2{3eh{1o^v3UWJbpIVJC*>D0@83g2y zG90@)WE==oG>}O*2MkYX(jV3nSi!J7AY>&0a*a7%@Au0*kVg za(#XLC>QvsL9sR^dEN;nrMkqvf~$O+lIqU1nlGqW*X`Z8go6_mBQVl3>2){c=P7zHw{K-f6V$ z!R@`Zl8}ib60!qf_=E?@N5omeV5p7zVeXrI?%Txh@gS4umcb)8W&V7l?<9v8m5-PVi{G4D&;BzrJI_**T#^=yIg%?+1VBhp z+5j5Wv;RG=owaH~d!b10ALa!~#n$6-s+8x*XHL(ZgunJ4thwLT1x-aD@x%xa&9@&w zp2v&L;r_EVD^CN`+Q$78;22q)Jl=e7t&k3PF|p3L!Jp|JceErJvXuC>WTVs$D^Qv2 zlx{O=*k?Fafncxk>uCUFtyp%NfMY;`nW31cdJ0<*rr;H0G1yPm zzHFf1_(;Xi0BDQ3wFxXo%Y6NnpMKI>8p&-z5j2Vc-}n?tnG(?BFMHfGleu>$70g7K zHkI_q**OYjBhWe+!Z`ZH-=klP0qP?)Ml<}8$Mg1+O^2>0BuTXWW@lW;(p8xydbl13?Z2$K6pH;K+ApUw8v(BkCk6oBg;HvY1iE1DqQJv66L6ssHA)*k{H! zDPyfc1UMguNogkFvjY)D@Z^l4Vu6{;aeDZSo9Am83L3WnuAXpytlu- zm&Ek88xY7sAeH**!a3@?R-BHkp_qLDeLNFBI7@-_vd8XHNOtF&A4#-n1XK_y;BXco zeDPzOk`@qmajP%s^!z%ym zbJykxsgWC-fQ!K`f{=m0p9=YiJ>~ z5D*3FH3=j@1f(V*v;;^<@`6Y2@7_1w9pnGTdwYxw*4`O=Wv#j9nrnXFoRilEdYZh) zgpYA?aq(*XbKi)I>oAFni`(Lt z=HI>69!uiid{R5$nB~=QH0_;RKmD&-jnKz!HKLU`0Y^lLlCY=d?+4$s=k2@z#mahV z^vOX!322<1pBI(SdHwWy-HlEeSjg##V*Jg?w{?d;{g@ItAWNYgUk{Z5yFIdNwz|F)1ij!8SJvf;H>Pgm$*LO zKSE-M(A1YN3fg?fWDr+YHdu@)6iekWocRsKBs9@W0hmoFI~?+>Z!TAfn|ACM(qM~j z0I0w!hH`B)VQEoXcZtBlLx}aih9-5tJ)d&r(^Ix4%Xh+I$qcnx}^vk*en9oTz$`*PGkGq5_XGDGe^~# zwGs+348XSu_Uz?VQxz@UZ zOP()xwADRa=#wcZSD4;=^FJ4;twxrJvZVK9thD6JVN8+E|di(F7#B z^&5l*9ivJ|ly9oDyygq%FMTrDB_Xh#+nlbE+6LWt!*^66?1ZzgEbELp!<*DTmX-WSYNWC?E!WR-|j(zCDQkl|}OUlA{8cbyPk8S_y znxJbTP*<&RPkmCc%y+631_OBpo4xoT#9L$}JY)?vKvwGVy1lvUR5JM<@4CA<@Pza@ zBf@0B)j8t+Gji0XgW~crTEHsRabPAY+-cS!T z2n@5q{S_QO$*Au+r#M`;;ckHx$P8c~{8t!)RX#%zWl>bKCv9pWr`i&lT49TEAYfYFDHs3M(?t8!zGr<1k%T#GX*H8Wg=PffjYUi@c_`e)kd?15&m z=fu|iD{JQpsA&N%U1&vV-MANB-Syp2AxbvvcEO9M26ieYW<#-sNXLVlmP>HQbVV2W z_egiuldV&sIJCQoSyfg@&LvSdppa`7$*qo&Q$D*p7V|7Bq`s4lfQ}8 zt@Lk9pL-y3T5NfA$!F<9zHR1R*}3LgVe#}->~*V@ETbDFg>UM!sDQhUZvE1w54>^E zuG)B_$V6+9Fm8G|#9JFO(le;Vy^xjRaiXfL#rt1#>C?V;IqRYH`0W|FTQgg?G9`oB zrui|9r*w!tn<2dyiG<8Ni$RsL*Y(-?;>;!SDbNXp2fbsT&h$ADUY+cJ;l0{vfiCPX za1ZSh>a#6PQ=^(VcAQ+SBJpr5yd;v1F(xBG!YhY|+{cZDdlTHWKky8BrF4#|hjp99 zD*h|ENYx4%7qFuqqyLm>73Dn(%3jn2&7)c$r?La?N;4qaOYF^--mMWRJ=D7y6~m^! zL7Of^*}G7xKyAP$o#C6xq>)#Dk7b_dH>lqwPxS(ar}EVNJNubjfBus>8^!P)G_)NH ze{0)$d?(B*jv8IQY51Zv(aH^RcTEb+SktF@4hLnZDlPsi$jGUWXl`f3RabGL zDn#{muT*IDO;~95P1GceJ%@HfGSTej2vzD48&LP)3V>O*xD43^*yb*>2M4kK>@WVv zNM>yg+ASi#uCwsFQQg?VR$KS?7Ak_O@pVTutuxY_{_!mu=}84T{vjpb10o(TXj#rM z@D7#Vio~WZ{==yKCQu(9VhBbH5q!IfBObj;fBulsX{vjGQsAAAi468a=^od}wvA_g z!*eS*>5#)agxw*!BcZ9iQ|Ng(KzY^_vwE9|t^|ut>T@PJg@g~Y#-j)LTnm#+U*bx6p1twOI?0O!bq9mViO$>}Gucp8L?Url&@L$|Cb^35Z0Bdiz z$u9gHP2e3j58qNz#7fcR8N0AkvNpCkEWd8m^3y-xphbR-RIOr6)ZIK9XXZbE=b_(h z7W?R^Sw`@In-8Gbr1Ll8CnPeDt<)Yq$#u-Cuf5la3P-gjR zdCg4VudivjyoX5m{Fm#_pLpCnZaNR@8YtzZ@1EqXKR$rXM(Cukytc9OPj^pG#dH}% z9g0AYdYgnj9{_%NT!L9?2N_@~yNM+leH6=IJ)AoYEr zp_A6}87_8P-GX3mrO&ekL3GU}O3MeKohggBgub#M_t9t>^E5HJFe%sWPK*FjTaW*_ z^Q6eTKqadQ^IvOw*eS+uJi&F~@HVml_*}hk^@6aro<~rv|3C=;ppB_UhfJ&LP~VxY zUz+99KCu@G4o;xUArF_>HzkXTb6lQ#2FUlu1C`C2X54gV#^-?h1B|z;o?7!I`DaLV zjD|p<#eYPSA(>_mHEX?nG44{S5~W!gT0d>AOxfse29a-G7hONN%nB`Yvh~_+nGpW) zIP7=B3pa|B9;nU(GyD94ReOeZ}^wilTS~+pLJLqj2_YAUAbYM*PZa6iP- z4{T@D0tM=rAaJM0wo6}`F-YJk2ZdD~#ua^58mq9~9vNMuw)^L?NC&;;uMjD){t=40 zgvuggvY0v7SpyjxzquK$+fZre4A~i9Q?pG6q+V`8R`*$`DJ^GjzTuyQIs1})+(nvA zT#68+=-mrsr{E>S=D`xhbTU(U_qBm|%e^VqhXOCpj5sSeW#Qa(L?J=9b@}%?X}{}n z1B`w2$YtfSNq`>y0(bn>1uusg8bGS*z}}%{dkKPkL=3;RJu6H*v3~mHw&Topm@WY& z&u|X&nbsq2U=DD#MU1G~iT7PWv0gDc{*LzuWcR#L^GAm6E{Q7Jv!ZU@c5*rYxIOlH z$pTdsS0-c3YGBMQ>bk^-)JlB{Oh=`Ru?vkC5(>JQLPGUJNmFBMJmaH;t-Hh(veyPS zclW}QlLEY-3N68~mqevExtC1in77I$C?Y&Bw|Sq}D$~75ZyZY4-hEhWGA=(dp~K44zcQ&9=jhc`SotD#4xV3-k*SC_eFZHc2V)(w3lg zG}E=b)^WAt&9{U~TRen6 zH|hrL#rrO)Qw!m;Cxf1@T|egSwqr0etJ8wj~hB7ZoueHW2!ib^Xjn?a}G1CF>g*ei9Sf}^eakQpEJwgvzLeS|lfja!nvOv$soKZmX0&^rwk z&DnTHYJV=fdszRMUJ|ul!5ueHoFqUYau4V zE#-jA(yi#;A}^NFy3xWR32Yu(tfzJ+dTw0(Ym0hkRMVal`QCFrkDf3P3K#}d_wIaA z($Oohq?o*jE zg0u30{3-BdYqH`OvW72_xPx@6f`WI-`Y%xYz9PUV>;ls&giTbRLwF+t4{$|-^E{lK zoY2V5LJLfHL}r-Umgc#AxpL9$5$!xDToN|IWw^KuikK(D0={KEL|X6#RkzcxZ9VNe ziuq+`XYF79H_Afxf1xJ+k4*8ugTcH`ESkLbV;Gjr)FtYwY292`Li4`OcMTwo%WRiw zwXvoUE>pOLkUj6oAMmoR)n~!6F`kFlHTn#U<>J@$0raSOZtiAa z2)Ja4AJ$#`z20o1HU9Ke&qLtDFa%y1=h@X~E2~l|(rBBhOj9YbL0wqo==qN;Y=Cxz z1nVJwF36+rba*k-v()HgNz>bqfM(vzizWEhrc!mb2V^&`g8i{vjfTC*?24I`vMT+k zo@F@%M7;|lX5(BVBl5Z^hej6=kBD*5n^;nNxoyiT5k6e~(|3!^t|m+P8lhwgdXm(8 zTelc_D`NdCUm}KC>wQAm%>m(=YUjrF-#0D24QsTG{kevbs_qQ`aX=E9!P*u=E@mWo zp|c{0R}oViBe`Qn6MJ4a1PI5Hr`FzGL=11ZB(hf@_awn}ct@Nup;MuAg@Rf{1nH4a zAW>Z{&AbN)Z)FzAu0()ubl|hMXM7kQ5|N(Txw)taVZmm0q*_tv)TU0mf=gqlDAn>D z!ON34e(Kr>y2$8RV7Gj!cP+$A(M!}8=Iki0M!~JuPd47gex(ud;?-TdMEJUGGX>{S zE$TQCtD(HMm_Qrra8K9$+CP-a}(a`CruOe#`emYx;- z$j&x7CraY@!BnZoQ`gToy{rg(82#*&GW5NgJ9h=I20A3r;sJPX3QSL4<2hNcYCOWQ zMs2OmxSv;+Umjp!{Xuh=FZI#tB6?f-M(B@@IG!qF{F-ehYQQw>w`?p^mfM;P+;=xpw%mgBzsJ9v#~K2gj}p0Tn@5E9Qho2uBJkT%v# ztQXi_8><_?tnfnYPIKPFfWOYA!3NwZN$j>!ng;p}YeiyvWMCFi3qx+E1mdqv2HkfJ zLOR4UQ(}XcKtG+|wj-YY92{OsuT5;GitCRDoR{L+eW2>xTi1H(&>FORIS=Y1`wlpP z^=M~$L!SKQpa9~T4E1m`&7$UicH3-{iIsrf7|`r#BppzOC&x$*d3k2~ zxmmM7ddDgg;KtZ1<@3}x>s1rFB9t@O(ReF)3QJ1t-p_5^JW_vzq~myWN`S8vw0x&9 zuxxv6kfRa3i{z*|+nkN_$***!XBST+oG8Nv_e$T!tqqn1Uv_^QiWeKL*E60SQ(u$2 zUb0$XkCo2Mo1_x2vaOCh3u08ntW*x{=n>7Q7e-p2iz|y*&8?F=>)(OxRw8vI6ET4l zz)%I6r*L&l{+LGu(lP=3Pw)DwNes~fj_=whmKzLCCoiuUVKXi|YF{bJ^6QZ(%G$`5 z(0q*T>FFsAs}Q)Xzx*Eq8UuzE6OhhYSW|H+;i}ro%K^jYI%F4!Be!K7)9l+1k-Mm+ zj8rhQ3`hWbyC>*pR6K`j?gRQ-NGyjn%tc8x?o*; zS^I2%n{BYyRxY2ITyLh{^012S0%}!#S)1c_(s&NitOkx0Ye8!uKB}Atk=Z^W?hmeA zxC}5Kx4s(Q+_YZ-&H_{I?XmS~+B`UOj)d&0~v73B4v#H{3*8|Dcdju#T;A7>mGaXG`f2ziu%7o;rEKd4mnoMlj?Kn3WMXRsic80Z>%h|R;t z1wc<(Mi5ktvl(Dd{1O!brMbLQXLdPT6ZZI+b$~jh*6pNL|A_)ntJM?|>&8#`HPvsN z$ODu~av?G)5R~n1kGa{iAB09}JE$4#;P44AJ+l1nh@AwyD3Vbw-bJw@^K{7s3# zz`#(QtBHnJll`5>Z67$10v~Vhz}PzuOJYka+7iYAheb^L##Sq=zW^u0K6VzRO%R!Q zJej9TrrK!s_IBeSHEKg+D8kjXSn4r5Yw>m$S?FF!XOMBi(qvJAho^SpHnXW!mifW* z+a?)O-WzpFo|33-JA^)B%Gml|CRkcxuyPPRklmnOo2j>sa4s2r*rV!OC{R5cQ#q9!-D_kv<@mHCP#WhNrr z_00h}D~5-NYz1pD>K&(NsGD+P0`B)3_{!+NPqx$fh5t)Xnu9s$v6*|Uo)7n$=$7pH zh`QXGs9=Gq<#O$Ua+FcG1R7m1yz9nm;vyJ~9+Z+=NYZ-YEVj6E?^)17rDSbwa7<&Y zIl%n)JF&(PmFjr!og*4ov4%bkKt_eO%owB=aLHU$NB{HAm^dmvoov{pSvN=oIGAiY zpAhhZHVM&zI6(LAoN$)xZL@06EqYjA;L*VdMc`n*QK|d~DwQ8c0hi^2!ofZ&pzx2a z8ovuetPqXSQQ)~)z;`BgmGy?YDS~Fj#fe8E|K#xF$D(M8lRnQ+onk4)TsU3k)ZT((}A^S9+|bOFE9JMh0CQ z-%`0?z9Iq$Tn1pAMCZhujR#D`qndL&QX9!(yFYlap~m^drsXYo6Sc~UpxIP=I8Iv6 z_114+CnCMpFnKCvBP*qa?=NJO4M^EmKy9+rK33laF06l>y<>A{XkgMNTV!}|sp`@h z9@8tC)K9s8(9Zd;cwg>ZZyc+VGb>F=KBk(XD}M-$cg06sP*n0Lg=II(Ve+(OUz!5D zii-Qz%8!)IALbp3TFjI}Qnl`_hYh>t%8;t1K2=>9DJ&a3rtwvfCu zAq>fmEb>>`0^NQ3v@p36GQ`?F>^5m@Zmfnyj76V4@Bwa$wKp#V7D`8_On%{UZ5fJ@sTLFa(n@CaJ zPK3A7)%zla;?uW}e{_g`zXQt}{_aDb6gch`_UyZfMnSmclmhV~fDly9^Bt-GWXoOQ42CK_ScYB<(JFtYV0r0Rg2iSH5&5^ zh28Nm=^4XbD72zQO+__=t6sP30E4%AuLHexHNst>cG5AT>rlx9%O*=V|Exl0_{#LG zDrvOPGaY^WE?z{O540{R#m@jL03VfsGL9D9THi>7466pzpE*zKX)S$Oce5hRzxf=Q z*1$s*w>&2})mcO#k2aSc}rVe^o7oddH| z|GEU~!>&A;v}%}SUuxG1BdO{yA8tPp=Al3J|x{&SDeJd-jLpC;=pt-N!Cs8*VaPL(t3>8wQAFo3MB*VDenn7;`#trTh@PCMZv6?DN*9a6mI{VzSZO#>sO0JV z?xB0K97!xi#SMSNLkAdxT)*`+8A%lM@!4))=(=1HZf{VFPP(KZ&S-eJph*<>#QhM4 z%XbpD4l=ql!!ZgR#rLqDK=ONH4VGn%|9zeZ&vYd40EKgJpuH}PXt{BgL z7T%XqEYT4zMz}>LjPD#67fG}K5n5XMN*k;KK#__aVr@ZJ7mPAo*LAtM#;-X4qVj^&OF)k2hCm z{%U_O(ezQ*aqyi{<*XU8Toux@`nX%dRJCww-8Ws?NXfL1?N<_OQJZ;lWuqQ_N_KTm z^tCHgRUZynosvlHQnoxY{*?^f}#^W^H~Ki z50(<-@g2)}`}c=0>p1vh$$wm4nf&~P@dNpvwfNodAGgF&YoUsoAW!Y%0<7yBvz&e3 zS&JJR{H-S@L!Wka#*EB!a-uE_S!R+XqQHb&nW{~;+Td+Dy7=ubqTK(oCK|Gq%a>6A z*~*{Thvz8LdL$UEocL&Xz*&5#Zb3fd2EN5wk#%DG(Ixb4RUKcm(e6J*kAB2VmlJab z?AV=~Irbo8r?JUN%`*T5R8)@7`AmOj2#8))iR&+9>~a)U&X(H=kJY!44Fs<@}33lqff zLB?i4O(LrT>B4L(BfCEGH3^RM3*0sym3i!+b7uPlXMR=3uR~k35|uyJUb|)~(>s2# zC~#EkE=ND?AQSiljM@B$jD^zWM;ps^8)g2&M~wzd@`gES4w7l$sPp;9bMfEiNx5do z^980Rmt^ujxHp}1`}FWOHmeJ-^b$iZ$st_#c-CYui8B#ginB_Esi;)8hPt_5pF9~R z2I*`J&e=#hb0jL#8*HH=n?Z{|`n(HEYb^hee*a#WXO+^2^ba)v$keTj^|ZIgY}xxk zKDPl^_)>EbD{c9y6mMqAF5?BUDm`*?oo4w+e@yMvQHVfpi5?zZnUEs6y>YlP>;ss+ zLb2#QGO{?f2=6%OQC8X8~N7}bICPg|$<(9rRRf&X;;2J8`wH;tsidL`q zy-^rZ87Uf`StEIddlBO8I#%=5-r8b4_N6 zE0kLo^@jBg8=a3973KfStoZdH5a$BTIM=gzuy~r~R9TO4H~PEDL`#O1sG%P^YcCZXClsOw z$Y>dd0lVGB5-~zyU%bX$U&)bEqU@VeJXRpE5_G zppxlD7~0t3F@rVCa`*R6?pp-o3?^{I(``@1En%^WeW9OuaJoZM&VLA# zMa<$ zH)KCG!;OMJ%xfa9j9(Cji~Ly7gj*8?*sy(9OCLSYWv<#GD|>>v7X=w;bR1zkLD2f;}YNhlTX2a;eU3;8T$SeX%iWV z99Th0H))GQIxAj=@7fHGTB*_Nto&PjVnBpIOhtKz?Dn#x0`m-m_WfsDs>~OQyT3@8jP*UP$-S1lSTxL1TwJyA^JU?vM!~6aVo(dv zYF=9uSHq>WzYa)y3l1+5*zKa0du=jh;8e0sK~i^xx`TN)uSYc^7mYUcmN-FcdY@r^ z)p4LMu)c}KtF?&qmC(O9^>Sfk*EG3i$_wcKMF?7P8&X#X$~>W8Z;~Qf|9$8rP zKK^zE*9dkeybJ;%9~7kT_&|1OK5f(bJjS{fj-%nvM+-t^hph5XA5BKi=Q-^MevDAt zHs-s_ccnnc%GSLDs^`uzUhv3oVm!9o8BLX}^YDK<)_Em7_7vp$FkTaS2!iIbt&1O8 z)iy&_w;spx^R3W@=!JJi>#h26BF^|HJyA(_syokDnla&c7&=yLm=|v&Oa)c8 zeH_jtw?e3fR$^)igM}K$>KV*(bqElD1ZLlrv0dhs<(P1kBiJg%Y!Rdu;haiYo?tVt zsLnO3t8V2kTInVa8)xZ-?NY_%-@dK}m=zdd043j(R#rxq_UevIo*>A}f?|~XS93kd z(LAgZHV|5{OzQdy@z}w$zAFhM3wx{+lqA3QP_jc;cc4P)?Ub)VJ5T!AsB1 zZYo>3@3cAEkdJ>}LJ3Stw?|A%ouqa(ssZ6;1ZMa+c^YiUwD=9KUc>&=W0mk*bp+(W zB;nm%GGflTbExd{l3gb23w?KbiHcvM!1OBh1>TW8j3F%qHW@}pVKeFzNHdYlFHNiZ zde_$R#t~~9K%>?SsW0WI9_Z86g44S_zR83$haMAqS4S4*=0pWT?$3X&lV~=FDJ<^H zdsLc)PVm=YMrOv9)r!iV)m?|5&C^+wqoY zZjuMX)QS@D=*RwZ;-uYNk}oXj@ai}+e*Q^olKJ3BzWCdv^RvU*lCI&tcRQrg41&Xh z%?eCaI)d)|d%c9&tVF$7z94+at(usyu)^-iP?B6sWo<6jD`o7TWC9~X2>o6 zM$eXehWEgE+q&DeiMv>JdHTfRZP*(h#{wP^D`4Q(TTK2WbWuK1kld(kF>y`E6f#DA zJyIXR*tG8&-y2L^qS_|(C7+sfHY6XfYlYxnt#3}SSn(-3y2ZgQByqsm(UHEo_r~)p zgNDtlFT1*u_M|JiC>AA?)@=J=8SgsKn-9BLB1^&b$gQ z^o)>s>6{A)sPzI7FId*M>sSTt`c8f8iC865+`rC)3o2Vn6>R1fD?xc%-xjInAj`qq z^CLAO)aa;E@38YpM74mHRHH>4rnZy=$@~kvP~C@L0m96Sos=@#kZQ$Fa1srs?%GjvjG^^^Ow4O%ifIiF~*{s&sQl*c8NXBDqpuIWtwz`yV;z zpLpvZ^w#65_UAw7t#$58rTg?2koN6!6n^@=lBt0Nv|iwOzr++o@!t2{L}B6Hmjl>} z3->-mx;@!_cuKzcMt->Rn8oP!p1Wl3+2B1)!TZri(CiOnjHExSy;Ugt=#=<{xzNM9 zkN9c3(#lV`&4I=ne4H43{5FVj14H1=C`W5?$0wCv zcE)M?`qySEvk`wEkyAxJCc5~2Ei1$8O^XR_t!O>3-SGTG1wa_HRKmkGmq#Kzo_A_Y zRLsj+Dn$Bjf-y?4PJCPK_Z4kUin(>^;rlAC94fe3jJ78e!LqFB>YY??kIdBQjgS80 z?iUiE7fRvlESjg{zcTUdyLoazKsg=1$3k>Woi|)VI^c zfXW3!1iwCSRH<`gv+n}j_Z|3FN%j}@*~3qXb z!lY`#*cyHFxff~@xbHhv&GG41xH^52On}@mw`L1*K+)Ia%7pFYJ*^T0uM-Vlu-Rf` zU&ALOwG7igKLQ>p=6%H{4_!ve2hsP|>*D*#aWwDP?2D-4_4nE2=G>=z&Vt@AoVT}| z@S{_Ct=~1|$UMD9g|&X?ZN z%=KsCL4+sJ_`mlAa6ylncpQQXA21CavLUvzi8FFnPG_etkJ`bX8P1E?yEw+X-`Wa{ ze$!h*Du{cf8bLeQA8T4=AqH*XSUD{j`yoeYF%T0{R~dE!kv{6!pMnpn=^ok)5I89}zn>_s&e)4q@) zMo7od-mTIygoc3EB))_9X9RnSVW9?1M z$s4Oo@4fHQ9lQ7(XUA3u*?S3r0IAg1#*j<`JCnwcUD*yUW2jdRZ%o-Is0Yw2lOZl9 zFf)|bPWA(*QvPf*HVk=gTFNFS)kF0kNr%y;opvv)prUElh+KTFn{Pz5RKY6%DuPiU z1%6`@#Z&##bhPZY{xV?|_};6~M&&{a%{H}YQq&{Wa5?n?cU=We?6yE|J#t;-0IZd9 z)P|WWvE8Y(aMtrRZ>*IHOcYI4F08BY-;%gkDobJnqOEM`Pd=;!vZ)s?jR0Wl4lL}G@kS}^p`5FRs{v#sPp>yHh zK(C7NK7DieqV>dDx!kR!v8_9A-*+8(q&vY$Gu9M9`^y~>vBHR7+}JZSy7x>KUdLOa zMQz}!f!Q>Apn%w=@M3-qyT41VwSRDhao!H&gIZ)>-*Os41iG~8qI1~o=GM^XnTxzl zdvmr`qvsSk?BQ#$Yf-aVpvG)qPr)C;*-%b#54`AoqM&z{m_cguQO=QfWOCz$M!%V{ z*LXN_PB>j$9@||Ae;vGU)bKXYTXTs645f`SY9_QRX`;~qpK=!60`wRaV`h5amn2KO; ze_&E$f+3o028Pbs2G)fKFZ~Z9eQgvK4E-0=g$-IDw;%AYTons1l|~UmK|3qX{C`bL zS?}kzl@nalStqAJTOh<4^3M94au6xa3a|DfD!Ts*4xYgdNbAl2aw4?CzCC<|}v4{Ul`9Ry@b5+tS1B)naajT#*2q;ks#HyY2 zOKknFposM$)t4neRi32iIYLSPjs548fN=Hz!#<&nwy(Xz3=(nr&7l(ERN+A4=!TB zjY-7dDpfhL^pB_#+5W*%+Xsyt|DGjO;2_2cjdoSQDl!WkUP?}~vfdQh)a&AhTOB%c z6bmL<&2B1HIgCo%9bw%1WO|tU9Fwa`MW$oA=IQvK%Z?ECAgEI+!KTnfLFI9iMN6)4 zLJw*g=blPPZEdi^Tgf|gZeMGfd21C{P^=PNNL>&b?HY8U{_!v1^}SLy7&IBJ_ct}C zi-UP9v5ozi^MTu$puwenul?BCxPK)(mP0&E-Yn?w@(+J7ZzSR5M~tm3*u1KLF*)n1 zM_?fOx>KBoNk{W_db*C&BYnlcDyH+pgx*UgJ2k=n#f%C6H_SNDVigN}FM@IKZjWP^ z9(#tY{M$0P{<{q-+obP=hg&&@H8OruvcE6Pi6e!82z2vH!TN>mV9))^MY>54)+Z2N zH^6}8DYJJER4M*mg-C&pK>@xNlIO#(l!@%iLE|JKBz_uzNV ze7K~pyE*AxjH6zcNbI>{KIxi^wBi&Vx>b`faoWRUqJ1qD(5I9^1AU3q-Ycj32H@mk zkB1#6HcTp|c2)=9D&YGyzU1bdzXRwFCFX683j1{7ou+Cb4A5lE9fS~Szxc9L_S0q1 zao+YG8W|=Ih07-5X@eF9jmm}p2HUM_S6r&_6J6;h9kCl1Az6DHFZmTMZBcd#{+-F0 zb8mEU=5>q5IT3cBuSY=g9B78X+ofSAj5>J4i0**%582sv47G@Dyyz5 z42z`m#`!S%n2tCR&%smAeED0^!_MJx@i@c+D}DhD<(R2mA)OFl6*bzW0ny49L1~=y zDm$GNMiBPxLZDb4kO(dkf7mTMO3{m5G-Jv;Fmp98L!m&;>5)AAm7>ZNfmXIH-5A$N zoX+JGcIq*m4a~F81X=`AU&QkQVFlv8wQzQwnKO}&VZ!_vXzY4H@R6>}`RrB2wGE-{ z(180)!}i^HoD+OGudBeLq&_c@y&7Ib*1s%aKG`q{(9!yinDA2nsPAD*X}&#{(BvPD z$Av_gQ<}P(S$lBc2chkgKnr{k)pZ)yAeAbd zQj!}K{z0==;|97)HlM?ta#-{7*IzVHIh>$p>QSBtnjw#6+f# zhy8vLtL53Hk+sDA5Dq%f-$`_F zsT$H#0uHlx!@zNB*f3Nmvg?iOM$YuDRvwo_cxsIZ$15z zm|40edyi{eoI}vGS}BIJVnM5Ws_L)YjB$tDj9FXTTdlnVa`|Uu7}l$UdI)@kK6JQ< z?5uAY%w)cg@1w~bx(z(vtm$$jRd}Ulf!az)j2QHS3Jr5gSF1>&YIzql+!KYCJJ5DO zrVLaJloDZFG#N3YoDbN2t1?}9Lt@WM@}G08OLvU3>&tA!C9NXv*UZuWWSHABSduml z41ZL_7vSrzy4_4~AL?OXJ@#{<_xkAPaM~pXESM=DzEI8Y=62=O9e_V8E;g9C%WRE) zfN%mrKc+d7qPcCqsh+a~;9)pWk>h@JnPeCpzv3w`8gH?Rx}SJiA?1r&c7Jb7>Sg8L zWmWZzHJ64HZgdYJENm93)0(njP5yFs8r*YG4K8qT!VLQPY8`i zye?9y*auB+t^mK%-1kb=<_Qy*Z@Y!bj@vmn-e}dkS+cu4VStJ)rU|;hZ^#t}z$YW_ z*?*1wYFWW>iSJHr;lJcLRCA-D__NRN22$=p_Ft_N47mHMchQ!2#)Z>+&GB!=NSlDi zzLkJ~k<`F1B*iHp_G<{*+edv*sR(IXHkt8RXteWy$dD9z;53CrtzfV=qiA!}JZd(t zg?N+iEk7ac&L_ZN-OQ8z;dwa0PzX$Oj43|)H6(^eXdmQ{L;@3vK5G#xg_d6~;v@a8 zoDW^PiEQ!7w-4HVpHjK=_M!C+`b9;i**z(5#(_J(uLWp?DdsUnZL1m#jVQa|?E_D< zKfn&FIzG#%-JWS94%M;tGTX5iL=v=C{y~+gSVxp5zCRe*|l?cX`#* z0KBuBTQD3nK23oz%X;B;F8T&#@`Lx;b3H4f9h+Cpw}H3Wmtupj$>9Dd(+}*EY3KPf z@Bj_YEUgT_{lhM(`4(FtV?^(_XRH}HKhl2T=Xj?Uc|IB(TB(0rAQ5io%&95+BU!pKuDtZdsbfXB zudF^HL=B$|Ua*J_24B#Y>bsiM=Z0S;Xmb$Q0@8itM8890V%ce#R<0_2(%?B-G^s;_ ze4S22p7#WHhM!m%Cp2Fx(TBS2?KKF{4wPwpLtpoBcG0Yo&@1I{w;2hm^uhCs4!^rr z7+9ak8e6Wjp@sQ(8{AQ=YG;uZDmpp;Z-P_9iu#B<2g>8k;@9M_uH#NThH*3B3w?ydq$@AeT+eKXLv)!kbf*0W48xn zb3WcwAcXEraQ^^otJ7~p+4N{GbL#<0NMq}qHA=zL>OENb{hD+Na`4g=x0C9B;is6) znA@dbUk22K^4PY(*X^T}CjAHlV7k6JfWOW5(#d_VBO&u=f3nWiP~LyWIys~WAM?rfBNW5XU?EqWGGkZ)SH&^7n{lBs_2=x6d4Gz0ZB+Qb0;N`soG}wYk^&9{*2bl`0F|6jN}=3Cn%mQn@uyR+~jtYtw3|7SBmO68*lgJjJiO za8tsyhGt+`YYfvVtE;B%;;4iger5ThHNap-(-ilcIkY&1K2F-{Kg3K_S}dO`xK-xX zEW82@W}o2MKdD;&$Ww6-pR4y~w(w~3Y(FhSfz;2EDUi_@7v@$n*YPQ7H?~)k`Vo|E zbvj*>xpycdK|K=rv(o5ie62&@oB(d6@@x2eG-81f{)`*L*uGXRKwT&a2ul3$%~)II z|6=Yv!A>w84EIwqe$1=Q3M1;ItUp=MY{A}6zPP3G$DkjsHilRCcW1Hp$7;M z5CxSg0RjYwh}0xRN)kv&@~q&@eeZog``Pca-{bx8t`8m_BFS3UO0IR5|NnQ2Ocx6t z1jBcnB+Q4hbIrciBA1`)Uf&To74mfGu}3^Etv$SF@jmc`!-@H#R4Va8_%w_K+&}gr zQo9jU;y**dGyT47zc?nJZSa*I&5G+`Ah%xKL9e#%@-#<>3 zTt$|IA4IA))cOO~!!k{j@fj?s*#zg1h=QFW^Dh$cB``-*htvVBZD?8x@q`Dx+_ZOd z15Ijo@^?x@`M>jzaYsEtavM>@EvDa0m&Xfn=teKV1BR=0>z$sWsRw*{0BwX$2pl2l zDywS3RBAu~pFvhiBBDlyhje0TC^rl_7i0kP$u^)#HKhW}O-^XO7cZ;Kk8~pFH5m(D&Gv0=2@6o{6h3WnxRq9Honms?RV)EW(|Gz7ec|xuK2jc!6 z`h9HlT;`mC7`x1OUQd#WqC7uj3n}aaRk}bwz7apzGZ5#y;*iwp@%;|!_VEc{s`v`- z5pnCYA(f8a&=7*I{!qo%E+lEJ51h@=h_H>(h#iS3N|8-b8m})hk}h-%Jgb-$Tau6! z{}o~RiIFL-UOZhH0J(8r!6N(2nsG_!!d~LUw4#TQ`hC#@gB@~oZZh;qm=Rsg3_kx8+nW`B8ST3h-qPfP8d&w1M=dM_~)zI`H)MN zy$xeW03gbvd7~MRBhj$vO+FTXVu-8myo6`uSv;n8otK9Md$o_5yZcQtVzs<ae z6d7V=@$uMzm1iPyd_Wwfhy?PIe)XrywQ6NoCo2UHr2I1VhATJ@@Y9cu<>lYUxjk_7 zWJL(_WQ0+))9tuTJ#PohD?@%mH-HWDF?pz@|5Roju!Nh3N1DlLQn^Hj;cFKH$W%hY z?RyoW314`R$z;=;CL=OF7M~y0T$N!?4sig)#eTrDa9}mx3kN4yB^a$|O z(69gh$7&&vsE?@isbg*UZpE4=taO!lhfc+XJ4s~H+XJUDY}+be2NtNOX;fBqm#_{t z;i{MZzDISAcB6%*Yr|g~6VbtHsEUo@o_iCe^+0L2Xztqmr}vcrVNlkpZhHdIj=;VB zGp!YXD@Y@amwwaR3-N34hquri`xM0t(a?Itz~$O6P0$w^PNyy}9WJ(4=v}m#;X5F5 z(hPpCG`(Y;5tP2M*HnoIN}IK&RlUrm+J+uVMmlzia#==oCxXcTe9DGXrOU-ofjE&{d4lm zKG7Ahh>fwsW0n^}9{M69Z$;!vp%;AWXDh^s$5`Z8kWUlbHtLQLHWh{DsWj%F7n6ul zU&G^?86wOtLqDV3am$HReh9QM6kJKiMx%)PO$}l=y3Jtf(mHmhS>ShZ=F7qMyW<4^ zlkiIW__;BIA=%|RdAjn?mH7DZQru7lXiS$-_uCf=swHh8iYfsy_Yc8FPJ3ee8BayQsW-c}4GdoyiY7o0Hki~KY z1alUdI{v9|ho{n~9JB}#2qK~VYdqk46*bY3B!_-l1{h4#;3HSF#L zt&qLP#PVoQoj=0Q48)cct5n_ zoLd@|y_8si(M04wdCq{yyt$_-!})NaSM4M;YG8EGrTl_w?fJl0Cmol_+r7nQM^~&v2K;dc6Sd(^g3cOu^bg&J zV}-<8IAxPl&kOFAC!bG?XcMK4jY=tmcWr+TPwVbTJ^9|RLI$_n&~~Kky`3@MsvfT9 z1;5*7s(6qy0R+r?IcbE~1ris;-c)Pqsp&=B_R={RM*6!cAzN&!0mQ^#1E>@4YI3n! zS*fMeox5XRkkS?6)zTuYZK71){>hk*2e

Vl)}=C$+G?DhUc!sEj!2Ga<_Kd1&BnmE^A6Y5?-UC9CvtJ z#Z)M$SPo5&Whg)KNJdpizEGWK7_f+fQz`x|V!-#=MO2tPzHTY1?xZazW>P=}1Q>~O z$8jK5n*VsS5Drf|azE|tjF=0*Yv#J_31UziXX}e#NbolU7CEf{SXIw=g&?O2iCb5y zKBY-~9_pCbO&<>Jcd(^2$2^gEM065>AZlZN+@f_cjmq+R8Mzo%^3kv>XzeP$@}kI4 zG5rO8=J3$z69%l;s>--D=srLC12gnQOwNJUfly|EIkM8SPtPBpCeRd<8c?GZd6Y?h z(0~#*S;1EMX^)y<2|WVm?Q=m1Wej4U;W=zi-XA^NcMYp&znhCaSe!o|hZ!o7zK1s^ zko@QxDoasi)7rhA9kd~g&^*NS^CYzj0zABi1hXU^Arm+H_VGBB&p%*2bz;x?M4SLM zKDS#_;Cz4l=;MG>RfD<-HcC4LvMx^ZwrJ{K-Jeg6ukVrXc)xHPGf)!mZDp#M|GT7f z)P{U(%;5M>@0=%X#;!CD2ro{{>7ljObXYMbd#|I`SD?(`wR2s%0sQjh0g2_rHrDPu z!j058vtWuWN#2pl&SHnBZ4|#h<(xB2*w2!vS;`y>A=4m;q(I2rJx=D&lH^+Ym14o4 z6-iZpZ4^9BFH{W)1GjQ*Ma}c&-tYs0+;PAISGVd+eD~2Jf)2b-M!oaDTT_YuWx@dD z6E*FFxgd&jBW^ov1klOA$ko&3&}(T**7_cKa6v>MW^mLVJ7NS8py@DsT|&TFd_XxzjO-nb&sw zK+w;=&dP5Rg3bJ*shv+W6U!a&!bW|&!^Cr=U&_PwAm!2HvmS}mDItG11sepR zZK$02y&LL+FE+!{_93TTR%e6sngFAhV7U|E;`7?|3wK)(OTjmdWB3xUi1P$=;bZne zHxqMr^pGnUrWKbvY4oJ!n6ZGLdqABX^y(5y@iM9M5l|aPoZF1a#kYQ~(U-8r8T5~eX8K}?u-?{Z8S>gt6P`^879+ z^aU4fIpkvjI(rG;IVcIWVjUmf?Mel2uP(0LGV`C+Na_WrO?@H||IF+tEjM08V? zSNxod6};7I^$E|a0F#=h=NEJc;zz0Z;27v~j?EM~iCY->CZez>HE1vU`+nRUX@rSI`U2zm~aHI5JjGH$r^h*Y}@D`bZ=8U;^tW5$%0Q{ zW~nYAuEy{Vx6;l2qpNIwAI~M){mu^UZ2b~1&-zUG5oP4($CHaNqqU)cq5Cf7zL%3` zccay_MMqwJkuZXchure4fzOANR4}ts5+U(K>v~$l2IMKg}EaT@w8& z&u$x(qJEZCh8GR=Cwg6yRwJ09D^*}y6HS?QKAQG)2w97Wa{}wRy(qlpRPI>47UiRH zz;_V)HHW)zg{HX6iw&NCM`R_-Cz zsY4+?VG}&qpjlm=)(rI>mnw%KamdmsdsJyeRDC7?>eEIq3*7)MXQz%eQZr+*xV$z% zj}*Quv@{$33Fs#U>?4%0P+jwnJ)Zi&m$&iY+&~RCUs|X?oUeb!;bRV=O|UEnb7wZ%sM5H( z?n}oV|K2mo!#Y<}KfOM=R}a>7OU(6Sbk0nnCS;t&ca6XWW||IKQD$UQfeJ`Z8=->x z+ee4qgc+MywuIZbWB6bCP;bqDnCCqKFR+>US&pak$sGtEX%8_s%BdZ-G}jH`w4!-h z2M()gIWYN#+4Ep_dF27@fP~y;?84E(<2JCPOh%r6v^DrL{KLfb1D*fMa>9&R=2}iN zXuwld0;0kH9=`lqV$W60%v`_F+!>^H-3-wI6;3?pG#?;lP0hbS!yi4f8GFdbx4JvD zkzA;xNaEW>;4T!vsdT^7Rl8YAu8e|~TQ6tE0gCWfBIP(AULsbej&toICl>fIS)N|Y*6<9G305i5U6;-elxNyiquepc$`c&dXStPRQ1 zwBqSxH=%fGa$ZiuHS|O$YT~nI5@_5aUZAWTu9l#BJfd)EvgSP4UVf8rp!02V&xTuu z`SQL7awUp0yPW}w@=(3(DndfD&R+#We|M`~-%;2#-H+Z?81L++zQOdH|K_0)&(LBRn^DoS`nLBw}FXTueA?4kb0GJpqv{YNs-iug;XX%&nL6 zn`UvN^nzlSYe>z1TiZ$%5C&2wM#$nv}_Djsr`w{yR@%avf2 z`{t3~8#_$yuiU;7!!pH!JJgL;fBx>fI+Sz^GBWTd%1%)CjDLNRD}tPsHG)XiM?$?L`686{Uq>`A)|U2y`NQ-?e7-n4wA-c;+N zuSBQiz2hYwr3IG?`uxqys74dg%u2ZFt7;SREl+2;)$2i^VEm#h6ZS3 zKqN_d?70F}XUv2Ge`khNRaREE;xqy`l4oJiZC_g++IdYG_|^6W2C{_PCC;2C^3JPx+NoN^AczNQ!Gt# z>(fx3LUO&lhMZ;fJzpB(!zPIf2|wS z-({^+XMlY90qfxdn21XhZgVR&QJXxTB|lv-8T<->agXGy!c3r-m1NpXWafY{*gk3EQR$5Af8 zTI@heP5!x01!Pr-!cD=&{u~2xYX`m2f72tJ++H$^6~2c6-mo*L#_?6p;3{eIM7`0< zxM4?mj7E;Di)9JI(8)J7PAdOzZ(Uf4=pK+0vDRpkb)`6rkQ3sI#wHP%`OrpwzgxRj zrYj|sS%eK##P17D$H~fxAQkG-E>Q^)?{6fxImC3-3F#~hRLfk63hWJ1#Mg-9!`WmF zn;%A{aR#XuRN#>4Oc^f_14TLZCWr<365S-!99pQA6-lc@@fDbRh{2|GclejieO&XE zD>NGfpKbR+u2YJ%u5|yKM<%oQM7il%Y$)?O5;v9x%8QAHb$rkBn2m^+_)>`g0g}+@ zJe!qvO6w=EZ>StRch!%aN)Beily18Gwn|jY#px!_lpzW{oMTq24awLBvNfXi;Dkzg z-RX0;Dixj~tl(stuX3T@R+r6_-aJZs) zDG!!aZ*q;4rJwyPq^QjfSgw=tL<;tO1k1m2!!M6f9I>1sDcs~z>C&-ikdnqa?JFym zP{j#q)wt=fad3t0bDDN6CFYy$DJpu!q7xccAhvPb z>zucJ{7GcU0(KgaZ6(otqx!bt;+rqgFI#z-Q&CnpO>r(5%M}T@QY}1zvcgwc&(fo4 zu56px`-DC`8B5~Th3BD_zV7$HmB74Rv0s;i81?dz&H^j(j0GR(K<4++(`7!XXc13m z5#TJV|F4Afz{@`X*YxtzJXw}%1TEQi*<_7x1&@C_w7(&@#gq=eti+tyZb5-6zfzim zRpoe`4Of2iz<#|c5s6n=$-pt|8=P1JbQi>$<86hh$?5m4a>UZma^|m{qMY@z(^0@; zh(*hUP)EBz$!vii9_@9tsDe)a;2MaC(4-*=(-T5dat@?dmT7LHY6G z-`9yYUYylv+wP1UnMz~L!v9TqJsWkT(uTl#Tpcx>44oGZI@0v+Ku=XRE@3rP%wlR> zoss<8m=KNCx>_a28p_bFu9uA$(62rYFihz>jSfm&sSEdLGX8hw+S=APdepM>ZHhXA z-m$FX^gh~@x)7#iD&R6h(s_q};>)F(QJMe6#)Mz`FI=U9&PSo7n*Uba3!Dc2=>x4M zGt4YeYnXc5$p&_x^5jE8_t8N=)Jk$2vGZTu-ZTHfJX*7KZvsw~s2JdOwRuPnfVMX2 z=22!)Au9q~uGfy^Ix1#BA+|AXjf*soqs$lg(r?F`3_*QAzB=ZeZ&N(77KQ8py4)+m z!@>6q5JX`PRl=peNa@Skz92!byVXUJQ&s-g6bh>!dQ;r*RaBZc;i2ugYw@NE*y882 z?b85iIEqL6FHRc`8B`d7{n{9@yM6+Q2$o4zBiJR!Np180-8NZR0Bt{^V$ZQgL6}7; zF4@>;4#a7VMC4wlwhql_ZKEn>3n|`o&IZ8L_?q!*Oq4>8WVXvpW7nG6JUSCE@J|9d zDWWgsx)UhWGUkyKbrIuv-n2D^<*wsyHBDR#Zc;&aytb%77;-zNmpJ5mhUZ0DG60)t zzdInqrRM8s2H{eR@13ak?Li=AemUJ!j>)#|#Y28As&Fs2tSZ`J3OLs?YEto>ulwW0v=>1&vLvp6BhZMnl_-_>@gUtGY5)8M8J5+xAUIH)W)E+ z9-to6Q&U0S{yjw~XUw&+Ew5|x;LgFjU!VPxq%@_Z0r07UHU1lrlJo~hw+1v@hq)wJ z6bN-P-f<*6I)C*cae3XF!~e+9=W6@BJE!^Wn^cbyMES$7aW11Q9PK{>tAN-m8Pu*X zsKv4KZ_#(TmzgQ^LyEr1elUIT1dK%0aTwTOWOXcn3H_nqP^YFU9ayNAPh9qwR!e4c zbZQV|OcuS?b!seClQVh(oVuzd9>guca*>1vi7rrkdFZ5$*p=1b-qETUBOs zX|uuqphv!cNVw>X!IGh~E8uN76dh}DB<80m!{WspNAi4qUMx}69?pgnNWXeROf=`@_QYvfa$;s?Z(SN-Vmu z-Ri7IB&I-QN=wQozFV?5K-NCK2QW;i`g#(vL#pI@ns50ub&H3}6+IN{LFODd35 z+zez@t56kf1?)4fC~mTthMDcV4RSwgp0-@$@_Q1$__#;Ycxs!}WWvT^NGYH4WeL?h zhacTWztp$4NCBI(IBqSdpSb#49Or1F>0M?4DV_+O8(iw>gtK}4gny!uFM`TJn6GvA ze{SEhR`m<53AV7@swLbnjqm`h+UTbC%roWhQulx07NJNt8s!`1h||kOazdJ`Ns?lI-z2TBW6zlB7rf*3o+W?r+(KuhfwjRscL=CRbtgQ3afcH$> zoDBpWO|Vp{(o@p{+DmPA!mk(&3SbmwjXz?ukTME6uvCVv5I-Bd=EJOPRoTCyH|<9T zRi#X}c7$*c(^1rr$d&A_b&v2;re4%e+~5_B0(RM#NIi1h)Yj+ukw#1W=T>-F2bNZv zlq}=#xP~nLV*~GplK5OYkbC8SAP)~%EArRy!p@0W!Z`<5#_KhvCbt+`Zh@iA8Rmt) z_s!<{_1`7N(Y?>lrAd1Db*#3v-FNzTOVamYQ|7}5QX+50cfATC5&{yS2zQo3qo%fl z^v$IF7>}d!-ZjbbcJN>(Hk>2Gqhn_yU7S4GMU48k4U{^M_%vmVyjygWzDVnrJ} zywL6dGvl&guI2PwRzXHJro&wyXqiq4+(w{8=DWu&VI_VY)W^^V{T`5q63@b_;;&i$3Vzo3Bn~R2KN4JMwy3jiG7K&ZhJ!?=? z1Q4YZP{DP0Su{=v9BWI~XmV;Ig{9PhYZSYUCy~}sSWy@Md;Ja0QE3QtbwbT8*ku-z ze}!&e5@TnIHVW_4o?@BmW9CY~toIhdIH`}=qqV(C&M4QJ4i&Jt_Ph%M*dUoI?Xxkv zbyGV|?IJ=Dk{aY+QgUBK7I?#f2uOBsGT+_Ap3O*X3|TPY%vEDKiD^c}2jO zIE1llf(pg={t;3{7!#}RuG4Xb^aQIt^S#2j`>xwY(+^Oa8hA!^3oJ6bBF+}T$JoK| z8g@?7yA0;l9yg+Ab9y!*Z||diE0nEeSoVrq4BVDDT81RsgtX5qaJLp7SI(x#3R-CO zc&x7Uua$f?eUA6lNdl96kJF(RcypCtIT6RMvWX+M%l&5oF89pdapQO2%>YhG)OGLp>Bc+}z&~kBluLWtW9a#H zVJ)=$T7{HJPBCtJa>=}7qCwSjG|1DmvHx%+rdn^|^!vU|DA(%HoJEkOLHZy`S9>ib z#)WajC=1GGO{y@I1i4b4^F5A&o7rQ;a8!$M*A93&G|IFlVm9*6iKQzq%r--=a>lC$ zsI$*f=d-Oj0o(cYw~2nS?eg6M#?{9Sl<%hA7pRyi_;J^O#dN1_>w4Cr*7LBPX|p>Q zv$cM^(itWh!2JmSDj|>!gvnl%pMWVqv<~l?@4Gfn%mtjDz0g1>t@yM-vfo9P$#E|n z__)jJc8Y6W*b`nxT~`&omnnBMR5Uce18rRT&56$g_g9fh^Z8$4TCc2pF17{ePEkN* z?4$wABQ)ugfH(=#0vOynotuDuFNii)|4&ioV6Ik6h&t)7;)OVPTiAa-v|hVH%2RJ_eL^3wTrqzM3tU9M3!#*rH$rh<_^1W#)ha1|7v};wO#_7 zjiFsdjhD{^10qx4seD57RX2NjStnb)&b-eK6KT$pYz=8L{te2_5*}pN6|0j>vo(q} z*P3-f?DfQ%levI;@bHsI_}}!9vsX4>ioa3q)8b{+lK+A4J>BY5qdH&xtw z^PL@VN3|{h;}Mf`cdwahQIk7pjY#jv$Tr~y6sM6G%1koVULoUZT?|&!sIn~T(F0(Y z;G2wy6{!Ef&T1Q*#Lrs-7JTCruy=`$Z%9WN(R>3NB^D7|328e>rfIAB{IJXX+0_e0t2?>pQ!uvIrv1ZuQrgWKdr1W)ppB?8=JBdFi;>s@<;Y+?H zVCgR?X4Se(j zOKkT+`wno=lDpy|2Z*wQMcFO-Gw{nUnfmN($})QH%Q`ryB9e%%9v0p77re=y^bHvh zIZ17YWa4HOcq8n-zZ?xnFSSA5yuNgw^8__rM9HbEA1>x>lmll^jLvHWPOFI|=s!Yq znjG4lf|IVKnBY4A-cGZP*S2_g{X~eVHRcOt=Iz?wL%etb&b$z-K|RW2@vBaM0VuFn zY$Z18ajUp`g3lX$?Yyw5^vSM~;6*BZ3)i@G1e}lyD|XK3R7!S4*bB(v9#QZzCWK5o zN+5qImdgEx0mp;GxAeG|x`ITnX~-K7-k6+RmOnI7zDpWfy`JIw>X1h*&DA!uZV}UL zH@)`8ULzA5@!j;QVw)Q@|ELS+HyERdS&ZwbiwbQY<6aLi;B5$9huJ9v9Rcq3E9dRQ z`9g~-dTmgAejTZgDelnh8Cp)sxK!j=zBrw|s-Fv7rq)a0wX*4Eq7?;7t-|0 z5r{qq^Yl3L@+_I?7wJ#TIm$(w0vbm0r#k+wBe}Q=Sl4ZzL1QCOg zQRg(1eSH;ADEiIOmMoRXb>OK4+9b0AAyX{Y)`-&CWD0gJPV(+!+IZd1*^)u??LCP) z7d3;IYmQ1}k4)AsmWLL6nwOGJGmuJhfWNEWvkB`U5QtgxQ>2h{$}N^XS>8my5SGa6 zLFl}BjzKH`K=CF*+8vPgEWS|Gb_i#Cc~ez{a{S6KYWjW5Kg&L#m>P6eKfWKBx<*q4 zH~zn1RjH}Oz-rnkycUaep}h}q)g(P(j{e%LFFR~@G8l?oP{bjf*B|y~rcIu!xYE#I zo|9=Wrk_x0GLSNpy1fVM8=dySp6#f)Hm*oE=6Zk2s))aJT%O z@i3pML=)rM`{#ZxnT&ZB8v=8NqTtg)GHN z3v53m3_llckVd&_{>DE2{=mz(``(|L7xav@&j$U{WF5Y*(th~U;nhjT%mIqC7k0?n z5A7hn^pnlNqoKel!K2M-S(dmSx!d!L?VQDHzc}#{OOeEH7emy^(#8_Trkqsj0*in6dV7%k`Q03>M@5i zsiX;kDTH6hQQC!Tlnb-#T!zhNluJketT3uWHHxJ|W6G>iy%EODg4IMe{Y(!vwiz=I zAO{WqMkPKM-Q&e)647Uo@#zra8xh+cpHRiayox< zns(r0`6Gj!hF0)+sz04D@M}1`=xI+~{$>dA!|)>yg!-X^PpSob;A(9CU`EH1jbVic z^0|5x(fp%#38XDlt}hE-1G(XOmEOx0tslom!`QJ$ua|t&Kd%hZa>y0_#|>s{Jz&fd zy7e}G==)Hp3Y{C-5wJtA@CH>;L zF!`8>Vbk6dF-l0lvik77&IyCYp!cho`s!Sc8a2$*N;=W^c4z7U7Xwu6u$l*Rcy0bE z2h=mKy2P&75Ojn=n@p;pfPnSM4vxMAQSb z3A{zfgr~ip(HKnVM^-dBaNhyafWS>$sCH*(j{Me2vR#|xXPbKlR=J}7^vPw5cgA9~ zpl>O|yu#x-(9PVZ9YSD2nFow3;e4AcYw2H<^=$+cVi~Ocf`*bO#t?CuVTYxoLMGVW7qXQDE{-7Gv5sd%JVZW$9zU0?6%9eO3NS z?}(~hBRpHT<4~Um!5fjFRRSM0l1LO>+BIXL9z__X+&_w-g0Ctc@zmRh(0 zr9laQsDnYeQN>4` z*lwMpoJCb3&FZv`bwjzYeXCw2_7Oc)zpNC)XUM%mc!0*cArCpt} z#)28_LOq`=P+&< z@uvd~hF@-jntIOGE5G0paZV<98(%zH{%3Hw#djtg`Y*L1-x!oiBPMD`To(*ge2YvZ@-Q%nYvy-oUGM@ZXRm zCAG4&(^r*lo7xLHCOV{@(4beYlnPEX*=KnUyou249j>6H@)r%24~aMg&F=HAt`;j? z&DrbymsEj^;W8_;Ma|<`QZ@#%v;t|acZL9-8)3mvBXE*IDq3^;R<(`rK zNliKd%wJywyo3Ssw`oO-yTxDl&Nf`*8=YzCv+$_&u!^UkG)zefWJ~)Ub?>~s8$?~5 zP)fdt#UVMJu+VA8=wCHO2+V-DgR=s@Va9)DRjw3(#|;&H$K^P$-WXCLJbRIPa=K4e zwV)-yRhz9uAMdw4QF3zEZp+Geu)fMoxGM_L*+Xxqr6{Ka0ou-Xe&F9V1 z6#B-3WCd(<)h^5+tEQo+&2pFa5Ggx|4K~74RV%FsWL;0bhD*vGv>LGO@Sjw`Hf3c} zwRW3-Y}Eu95MF375d;iINc<=7o>p6(l9FOYx0f`@89_sqy7p;nYDNP3sMq)NwnFbr z1IzSaX6=8fk($7#rVXu3e>9`gB`g7h07{vZb;EeFD>`%q=Y@qe*)EHG?0Ws_XM*^w zDe0Ot7GpVB#k?nlqqsu7uBhzpr%y6lrZbt5b)qHks(- zbG}}^L7r>n@pRtKwZA;4l}FVR-=r; zxB+uW0K;z``Y8fY3`TQ^fNU(=_f_4gPUjn)9p{*_i47O=iGkc_e9idhdQat$?U$H) z%P*@t#^ICc4Ryt@dm>Vk_1%HqiPG?FRRiqS#GT11k(7dR%{So=z!d1*{2X(+G&AWp z*}ix)T-;NkZ{?0o-)C~qfSWR-U+8G*Jm`46Ij*-v6G;HR{ZGR6n)>>ys=nKBe@j-1LkO+x@Stiv zQ0`OJr{`)_s6ekT)}Z#aKY#ChqNh&9UpSV(f*+ChI@{e6aL8S$1coLY!8@KWJiF6Y zF*eDA|Mq$#oYUUKKQ**v{AAFZH#uolYQY^*(`9htYSJ6hI)H4DNoUQRmmgNI$@})9 zxm*(5Bxo$Vi1Fvs?A9)WwEJBOEI^Uy^{@Eb&fh40rJf>Oa#F96AOXUzRf18ngBKJ& zBdi32QC*9ZI?OW<5S`+=zkrtd4-~~HD`?4U%4@0rCf>I^Pu@9R2+NSkTvs55!kPKH z!$VEDA;8o8)W}J1@J(punHs9sNRDDNpY&qW^3lf9zT}n%ZT#Qm}p?^w80k?sz4Md#@* zARr!j8aKL+k2s$BMdc)bKearp;;^{QfZPttY)Y}bw{ml@N6x31H7^PQd6w-jwz)Gy3LPz?DH0xv0A+dH#>ZZJ-0Z!FEnOMTd)?otLeK2U;K2b!#M! z`9<=O4*xY_1p2lPc7gode=*LqkWgMd>l`1ZE)>c^}mGP^3-QyE#r>eq{1 zFsZhFxBXM6w$5MPtUg)dH!qCIvUPU025F!Y7|4T3h#6!6CIZqA6G4n8I;=!9+Xm2^ z^Nw8`Vf#&4;$x-;eLXIF)~~4Tfjsl#pM`Q8+GZd;<|`n18Zi?QZ1}cXc0asLktlEn zSGxi|Kk937?~O9^hhsHx8vYj6A; zo(4Nhv>l0F%}lohtbC_(E5_#qTp`UofV?fFZ!@QK=}D@?U~2&w)g)^(LvPsHg0-%O z+kEWUq91fdrj^fEFt-|Z2r!$OJLA($PlG7aYJN{WU|hdcjZKz_R@r|p-!q={39M|D z>xD>9_@}1Lte3frzbX*Cl;gN+2C%19vNZlUnhAjg&ze4;7gw4D5Y#@m^ICHT1(iC~ z%#0^LyAP%T_|>V0MNbbveY~~&*b#y}sJh}uX~(tHw3XY6bkaCe0=AiCUTkB-6%X$t zG;zQoVDwhra6)up;g-n;|A)U}n)`nO=IKq`pKn;f_+Deu9AF~=S!VhGq_@{+6NTNW zdwNoLq5Jr3+^Wyi?`{ggk!jgv32DEuwp@v*StVS2dKz(BqVKfVG=q^|OBYwsuqPwP zVF6h-p6W!{gQNgA&=nc}qvwq6WEWl=FIf!VCFd7-lsczbd%KZ0ugB*O%=nwkVRDp1 z(0sr5B0#gOQbst_N{4fIP~A5SkgQBqKV>Y5U3jVh7uM;c)a~Z*R8hDG7XPD<45c0?q^N)s9@Q z;6G->o?uanr_FYQ2U` z^cRk)X=Z_^k#-V$>Eshx*fQz-i-={CiS6bs10L`MhacNU>b~bV77RBeyvBP<9n9_Ojd z9+sb&0)snPm`C%1$Q8dax&BiJrVN&L!uJK-o&pBr)LUqbL+wG^T>@#8gJNQ$J+^{cipMZ0r#QlfP#7h70m8Gp4(8{XKhTt&e)8K>_ zhm0nCu9}>fNcqAW_=*BGC$#rX-Fx5eF4~vr(va5c_zH0O;d`ps0vP|p4PeeP+FP`; zhp1YaDWP38>t<0BYJoAHoNufh3jv0ybB_n@zJRSrzQfW+I{?hONGYiv6+u|^$_g&S zzBa3~b*$#6Ow$@q9lhhid>|e!U4|D`B>yxeNT>{(*?!jc56T;n5aVAJkEVqS56ygN ze1D*2U_^yBT>)Fn5MwX7 zbBp@=`GBU7LH6kr2?KpW;OQ*ZkFx^U9;9_q8p<*nE3Y+<2%GVo0?4~NSSXv82 z*wV8bU0nZ>j&>J)>rTtB$N8*pmreTMQDwe>RbFKcl~c>z;DF=7O!2=4wF9$K!stmT z_;$BMs}26+AP>eNO?$njZRlq?+qk_IS;;vr@wrM58Rq*rK>}3m)|=4MxX8^dR$>Od zHnyZzrg}qwu|77x*^aj)Nh*c+N@@9wS2weHTj8r^!t_I$d*CE>>6YZ*3OD^_|4=U; z1B9jW?YQ3+rTC5b+_FbMIO^{-I4HkJmowf<<<0|@h4wi^Ks=|X{xytPP%3cmUmoeMcE(#>QAtTAXnP6a+-7bY<)a4ho?MVF0NS5D-ELiX$p*RGLyPfYi`K zKnRL}2I(cV5TqC&^pFG+l6>a|=XvJ+o_Bp~z5je`ee3i7ldOAl@7Z^sefBwLXYb$7 zD8hebGOjg79TaPshJEV}}Q>pY1&-WEci}r#3nQ^7rzW)`9N6~r5 z;s1osqRTRl;VB(e}6EP$dhy_}yRY}*Y*O!r)7R)SSY*ngS3kdHzX1Ro zH2k_||MPSEPtr9^>#1cU?|ROyBc`&Mr#?AgYHl{y`Dg0VH_^S01s1<~TACh7`o+av z^+Su{|Ro-`yU&#NFQd562+g9lU}rJ{p5t z(pJ0&R?OoTeTVQQ6tTk>R%h`QqO8w{c7`{!EK0q3A$EB2xWHd<9w9GXBM7zg{UVFy zzD;y9OYa!FlB$2V0ty*?%#=}%z-Umc_VjOUjBTx!V$D5IcTq{6CaD_~^q2HsPGOp5 z_wnjaa@R>2n`B))^_SkPCG6oDPSxF{oOpMlbF0zT3zbe0u9)^{Hh{K^UL(HWv@AbS!ai5N4K@08uHf z&a{wQg>V^10(PLe=E~gI(=ROsMVKdMh*_daL_6dSk6sVckl!`uK4^ZhwHaPHiCJnz zRC%dm8B>9i73f#^G=7o2z!9&q&t_h3`Q3-H(K+$HOHQ;9ZYd|#V?K{}h2NR%-zGxL zO?Ml>FKBQ%3c;&R_$eyE4Dt@>ri3p=)ZdDI`<##p5P{>Ty<)IBjlNDPR<=oMr*z=Z zbGTro$vd(QaEt$W>aEv_6>9+e9b6LTsK@X|`k|r*L+{VnLYB+vLo7m)E9HWEOB~6-5Aw zaqQBQ^vRs;v`%s@OFcR^77@;35d$dR*rkthH9YNvka+t%*JUL6yHEJG$QI%tl1mDt zJX00y#e9wy43~L{4|WuD3-twq?5PI1X`=MP{M;Xn5Vs!8!PWU9U*y{CsNTyuKr!K< ze1BV?0k^-c7w}Do)-1*2@;A?7mtVro!D?gfd}0NcZRi8Yx<93v)!nJeIJXtECjP$j$dQn zTEFmjl(g&pg$Ilwnm&0eN%SQ5`Sa&h4m`g!h7{gaN@>9({5>)_UVmyr7hjtt`tFE@ z$+^;=k&+W%UUpsz;7%oHqV-2Wmd$>hu0d%hQZ2?q>mWao!#W+9R8pJw!S9LzMf zYvmxOCs1pq9L7OQukem7o8IZowq8L7JgL2!=nCc8hz*{0Vt1x@5f_m`t9)JtHd5>m zq*ds6jJi)S6ir4EJSv4>gI5U3|b@9>t5^zL5r2-AS^MU4K${xW=BB3O}qu?WlJ$1>vQdy){e zk0{;9el4$Rs=`&m<*@)A-QPd_)=YD^{(>Ek{18pBvR}IxoLTz{^$BZH8gIRTK{rfQ zmv%X^N5$z4UJRNb-+sl6eS8FCc;gY@i=rvS%7>b|9kY>@u)Px*Nb$FSmJ(f3+JTgM zb$#(pogl|qK507qTK#GlMWy_vMtaW-t@nuy?{qM4P`OhMRb#5Xc9B_1F9@I&JDUz= zs}|0n^mz|1Uyzcl+|go<528_;wY|8*L#;6s1^;o4sVC$LQn}z~-ryXuTMj!Y=)L1i zBwP+1%e!?L%3_3$vUZ=kP(MN)PP`A3>AmcmPwbg6(!9w0Tx5U~H(+c4NjHH!4e=Fb z>?*4OEF)vlmwP{a$qjUKIwh7`aDk6y_9pVt+iM9{5=j9x6=z_EwIifCGVZQfeV^Dy zcV`suvGuEHH!|BIAYkT=nwH$Fk0T+8)It}8%@BBj@*)opp?q_mVCtru@|y21E@ot@n<0QSH7DKqX!)##;N z`eza30Z!xrzquh7EH8vR6U`EjFCI%L5twIwpZHxPSQlVePX8BHhJ2ersdNrQnE+CH z)b(zx0%zC-FWelyQ``v(={hhJ)45y!H!_CE8()KzfSmFtPr&YmdfZspGo?z=*75K2 zLd`){Tjm+BK`lz8(HP0K%iG4)^B~48KLeo06Q7&??yUt_Ud<=ShzV-|M^GOrAGhS| z+J6OLOa>bdYIS;eU4<&0w$7q!s7pJ8>eOs%ot|v)Hfri5 zC0vl8TbaqmLkKq8<}aA+mcVVID*^zUEuwS<(x0v)*}hw3n-IQy)>YY;aSHRtXTLX1 z!>hJc?|S(xJ>B9(mFe}(0V-GWs;9R3OTu=0x|4pE?P=;vZIwro@?70I#a=f?4S3Jg^%M`%VT;LX6QGE2VLlHjiKQg5IGg% zD>;=jV9)Deh`#Y_iLO*f7I}rvTF5efh55P4(gWwwP^nWQYc=fj7wsku}C+~5; ze!Nsi3Sv&#pQ?KwLRM5Zt7Z*~>D+Tww^URXg2)%DRSlJ4eS6gm4r|WB=^d{KO(7jk zA^;F+BMa9Z9s8)$8M7b49foo1|AR70pNuKHse$WEQcF3P^CSs%F+RnGGqWuO@9n=H zHGR%G+sL?3jp>B)I$9jsa6dVRlV)cGi=40!+x6U$f_HQy=EY4A$YEj2K`7>_SsxIE ztX_vPwKoxcXr${T%*o`p0X?wycn+mNVwpj@oQJhj+SCyX|78#cHG1>L0P*7-Xe+zKSxYcn>JX<>n~Ip*@_xkF8u5x-sh-K%uZ_ufgK1lO#5VPIyT1Q; zjatG>RtZ%+T?R(sZ}Eei!HJg;woy^~UtdYH_x~H_q-k5P4aF*&-O^37XZt>`wjIw3 zxqj_MXhrRjbi4hXk1A<>-wQ$VQ1JUY#TuVU?;aDwr6alxMSrjYA;xp8sdMkPK$kXkVKoR}3-$UtfZoWB9&DW5Zq`Pk4tbKJy)JEWNQm1Tz7b&5o&Ou7PSy&q`kv=iA%jjK znkp70D@J>$oLfB(65OA2;7eAoO zABxOjr>|_0{s(?Y4Zq0)m^7~711o#I9Ok&IWoh`BL^1gtdI5EnP-Cz~0-17S!bOE` zLsIKS*)1>DmT;8;f%I<|q#%nnJX@jmYpd+T3lIE$g6LcdN!eD<*HQtLqDvZMSzBHA_JCFUO$H3~J`C z=WvX?8*k|hRDC0)ps@t&$+TiI5tfd$p?3gZ!GG-md{We4Au$3~Ygx78qPG?v;f(Yy z0N~5i*P?|KQW(qo`(Y>mQTL~!jdjm-F7ejih!P-${8T44+PnJ&>AUW~mq%n!8-Y&V z_-o2sL+e7Vh1ldyX>?BimkI2mD~9UZZ0?fY*gxTYW(9-4N?+*gx!c@2(5=h?|r8F&M{zUb26h_AP<7M?%gM9Q*rSRGAf7D~c9uKb?uPTEvF_TAfbrR05g@63?(GtSPO zuSsE-YdiK#_|QT}D-Yy}LdgZXrb8gPfHB$ta!~41<7VlJbl(uM&y2XoAniT_InnZ{&7PAR>mqvHa z$_sJD`cQlOR%*$$ayQp5K!_f~gY_J<2rBDQp_ZmnM6<|bzFLnkfTx|# zf1}U-5M9+6nTpK6h}+gufhT;XG;gogdTxKk2ID}n1b^{M=%8yf&(XW4bxn9lg?M6U zVUS>t5{T*5_<0Gl3=TD^O=#$1sFd=m^w0~jwm~_9?V_l)nu4#*7b(wza9?>uL+!F9 zZrHzc`z#BJ_T?sSHx(nA)tIiTwxrW0ti;R+w)x3a|CchAy_;I$Wqv&(l6qL;9CK-c zST>-EO9QUy2Riw6;zO$bLU}{bvlzE({=M?)Y7p8kx7O8nhw=@&%Rv?Wz||AwRcDtQ ztto)LrO+ujN00X5%Cz&}d{1GR{QnXFEcNyZ*%a`+i~94FFE%1$woo5 zUeO>0%qCULNv5~rz~Wew|4g3>z5=sDN^8U(3_efXzj&(3l+8xJzA~(cyz^gBhYsQg z9jIo+Io<|0s|xto*C8?Tt_;+t3qyGEG)Hv+Ny@FyL0G! zz=n;&+)e2F%tq)RK7oKWr~9U`#AIYexxM83T^c8MW+~p;G!$mx?B|}d=g~XEy?05+ zG7oPnNi-?qtLXAv<%{FYfAqO_R{7j zooa3$mzrB>^?vCzz4;S*gwN@kY;Fo#_-a3Vzb?gJVP9+Uzny@31ollIpxg@_-^@X% zhHvKZ%^bdshHuW`n=|+}HGW$u2}i*=)WjVftDG$9cZO0kq;+OrJ#g+O zG&VL~zkdC&O-*)fZEX|`7ECU2ZYEuCvEb?C*g>1BusK<Xty3+_D!+nn3l`yob6L*tB*kx~D& z9+SxglD1bTDB4(@@u--lp7+?sI&?PXs15Acu_3=UJ-aO|=(cBt`H!S`&TI&~?P3>R zW3#>~G~9S+eb@HO!S!9=KToJGsI@#?9CJN=r9Z^zEF-V0zu3dC@L@TCjproKSz^f+ zfjBbIk)Cl;cB5KD{-TlNImM8b!r$)xq)2R*#N`xc%uK7k+YHO=X`!R&5?bXv65hdG zlfMn9>+W}G_lsS#D$25DYnsa5xIax9hgu)}DP|xghOjR^B>Qj=?2qo<^!fV<48>Y4 zm+~~6n}zM0TfapeP6kbupDW7FQlscAI}SZi30QfNM6TFhR%4CnF0;IvrIUinn^lP? z=XSiI@GB5+l^--KAS5klO~jp2&s?IF{bp%8>cO7ImSw*rRBS__V@TZVL8wxB1U!M= zvFLNf$vp^lMLaI==e)55-rm{J>|S=C@6dRH;A~`8!|R0A#rTgs()efW4TfIyM@-Ft znBuX~Xog~Ci1ju&`FKYzMWWlK#tM{h3@kV5*!Wk!pse;fVnEi_aULE);I{B zs|3&7wN#sDEu9QGY3#LHrymkPFvEO4F){rn=F8ypOcIMLo>ruw1Ba* zOO`0J`JTx$28SSz>#uhjn6SGzaH&)&#^daaRK0bhbedB~ndR|{;|6+XaEp~o!!D-x zJ>*Pcu1h0rgJR>UZs$`5IP?2rB7=I^`f9Q*o~yiObAg|%;_OHzX?2h&26MME%_rg{ zHKw}v>(4G(wqGSw@l&*X1SK!)nRq(Q8~3sttdBDSe_(aTOp_Oz8u&&wjXilC&E%!z zgvQaGitr_bVv}?WzNcR$Zw6i^sNC-}tEdR)+C&|m&php4Yb#Bi+fw_0eCDYAfX|PI z5v7P2X?2wC(G=Aqx!E(5QhBxH9kB}YUVW`9FJ>!Ca7#K3CfBVz>`vTz1laXoM+`#&Li|7L=mJwrWVZI zA~v7s6{TNX5_;)=v0hD`Wfw49Fsj{u15b{0JVi&0DB$`hJXVb&8Wwu<`{8z2=)=W^H!4213n>HMS zEw^(9d)v>exc1x{?%-5s`QZtU{7VMHm-*$VzAKWs$R^~3!UF0&t3##6SHSumhi$yAHw#slC|M19p>uc*{>i33@Ysh#AtQ_H;jCQeudve~aY$Fwy0sS2) z940p%mzOoz6HJ&kbS5L>Kf*Wpi_c48$A0vm5Pk$r9)%f_^XW#eI@_Q?PrEemy$%^x zWQX|#VXD=)K*xU*_cGMR1}}ZA^qG6371+4y!%7s?aB`!FY#w28Ww&YEOGx$Zkq%;p z63vOqvGc726_-zM4u>vv>5PFqkeIy+7FUt+i>ZMWX&Q2+ymES)KHAo%Z^PdU?lbiI zac0B0&+!QR2hF&oy@3=S!;;|Z{1j74s^y0AlCP)uoczQ~PabU$M;;Z+B+qy{g zLp^L{DBY(H2?mdEg=|0ojAofojb$;8TL-r3GH6Md9(A9O&eZ#7WiVx*pxF8{9L#^1 zx#tnjNRtINRb@w%s>bO)%yM#*Uz>U&0HJ|}tpQ8o*fL6-oxAnq#jhskstiSp9iBokpdU^bi8|EXnx4vb>YYI~Wg>|`%b;f#g>Ug!xPw?-7$YJgp>dNq zQi5!UHNsO3pkxXMyB}DvZRg@eyM{|Ahm%6*f)?wOQPGLW&`cp!&7>q~P-NiYUWL($ z)vb=fV^Nj#x%8$y9P5+F4QPd;eW*{wFp|h;q82G9G#i7&ZTE=ry?ultP3nQ0%NEC< zm^Ns-Viama8}e_B#$x45!F@12(<0HZ9?Oz(whH4;5z#$)v?||@7M_$W5`1C551LvRlRLiCQ2Dp zO;jWe-?Bf-nW!oDogkG!o+!fu^!?FO9*(?uUlhel|Vh%#%*E>Z=IStGI zQ)ph>)WBQ(Qsmh1aOI=vd=ySCO?)eMt!r8X2y?6msP(T8Ta|0~`_Xs@C0uQPWb-(D zXc+fHe-U(RgXiwzFiP7R%@Ih+MBCsXHNCZS!ZzKX0tGxUcf|ZUfaN};=kA0o_O%_< z7=obKikstJVrra-ADnoz@^uJZz6ia7oV?hJ0^Pp?I0%=k@XOrkJX`{_Ks8hZ-XtSE zi2tYfXd{6!nn$zS&!!3tg>7+kp!dtkP7jhTd;`dq^yc80Gd-skaYk&5xov5f>vOyr= zW{}MYJw|tisLYz(r&M-$2{e9}MWH9X)|q>wBZD1%AUyX6Warzf8HMTbJHb!>N(K5* zpAo}aTAzX5Cjh#mi1sYG87dw4VDgV0!s~0vD0}2Tew7*+YHUQF4wdA0l5jZ zHxV`wJ%FOtI91OMn^%BSVKYF9uTRbodXcrpN5V;vp|-<8H(oZ{yj^0CsPrvEV_lYC z50vHCj)l&P0EIb@r=i~`wx#V#-><6OYtyz1a%sG1QwFTPZFV_LdMv8TB<5wT_RrIF zO$Eq=4Y82xFesnZ7<|9jBrPeIy0Y5Grwo48-7cuF`fqk!8eOR0@!-f4(@07HB~$2d zd5|7>??>K-I|*WAALeX#Z3yMg%<4nPK9`+BIz_|Ar>B2r4LPy3(4?n63)wrOqBRCy z3muyK1IVVfOKPZ@=uEs?8R|IOxX;GZ-UX;8%=#A~(5d;pV0zN68QN&v6O;SczH1S} zj_y7V*=heDPw)-bB4YCNFSA7EvB>qIu@SnoU+wzkC{AB(k0>eBlZeS31j5wS{S2N6 z&&|t=6vHftbR3(A(pvs2+-WRYQwf@c!fu}dPEh~&%?}3eKgy;4*d*h3)3bacVSPBh zhHTl{#;e?UjiGY3=?GF+<=%E_Goh%rL1%~Amn@!dtEHf=JKcXYg29 z8Kk_30wSN&Bl^*V*BQ66@;Wvs19GiJ){rr0h`qt$r}r`=q_veaf(8dk(51R$$Tx@t z^j+gli#W^4pp89im=^1P$_dYcGGtbk(D2EB33~R|tdt<@ zD1({yTimSV1fi?)2li2C9n20GY&b6`1#f}}dU{sRY6t}yDS&2(NMK8mFAdPYr37(4 z7I&G<9})USiV;wbiD%K<&5IvJ#O_1(@nI+WH;{xTbUE!9NMOT>+a+kscs=MZnPVmlU!D+y>6g37tOSP6%Bbvs$Odounrx4a}lmV-a4FyYv{l%g)@GFwCO=k_3K2KwW zC}x~H@*C>Ht|AcPaG4W7*LEK}u|s!cch3!I1hppuVK=9+=iXcaFL}hL`%!ufr$s|J zf?$}7!a1K-@pS}+UNd`xt`1!H^itqP_u;H$Z;5mnfvHdL(`-=2G$%A4m@m-Yq@Nx$ zgM2c4b3;-GJT}KQeJ^*0`MX#gX?u3ZX#cE14Pgie&0pzY-IqRy#>K{UR1LZ$2fp_a z_!_6I;gI*GE2y>qSUq|7wf_VfsZJ~?Fa0Q+{2q+4$Rn<4`{uD!?{y0l0FlpqzHbWO zSAQ>n*Q*sTN{W$T#`bW#v0%74@|a}hf3NHIX!JRRYc1YAUpzmIHit_LJ|flX1qd#u z=uLduAGjd*uMr3nE*RA|i%IMm6Ejfbdo4}%3!R%2;ya!dZ~`AI2(1cs za09R$=-34&1?cntp%b^)_iG^H0;3cb7Z*o?fol!|M+blbK*wK(?*$?&dl%Hrw_buA P3GDo7livzYIo$bQ;^Y?$ diff --git a/doc/manual/viewer_interface.md b/doc/manual/viewer_interface.md index ddc24b44..fb0887e5 100644 --- a/doc/manual/viewer_interface.md +++ b/doc/manual/viewer_interface.md @@ -1,6 +1,6 @@ - - - - - - - - - - diff --git a/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json b/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json new file mode 100644 index 00000000..657a5a36 --- /dev/null +++ b/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "DemoCheckerBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/DemoCheckerBundle $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] +} diff --git a/runtime/tests/test_data/linux/framework_manifest.json b/runtime/tests/test_data/linux/framework_manifest.json new file mode 100644 index 00000000..93c26ab5 --- /dev/null +++ b/runtime/tests/test_data/linux/framework_manifest.json @@ -0,0 +1,7 @@ +{ + "manifest_file_path": [ + "tests/test_data/linux/demo_checker_bundle_manifest.json", + "tests/test_data/linux/result_pooling_manifest.json", + "tests/test_data/linux/text_report_manifest.json" + ] +} diff --git a/runtime/tests/test_data/linux/result_pooling_manifest.json b/runtime/tests/test_data/linux/result_pooling_manifest.json new file mode 100644 index 00000000..21ca66d6 --- /dev/null +++ b/runtime/tests/test_data/linux/result_pooling_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "ResultPooling", + "exec_type": "executable", + "module_type": "result_pooling", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/ResultPooling $ASAM_QC_FRAMEWORK_WORKING_DIR $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/runtime/tests/test_data/linux/text_report_manifest.json b/runtime/tests/test_data/linux/text_report_manifest.json new file mode 100644 index 00000000..f74f1177 --- /dev/null +++ b/runtime/tests/test_data/linux/text_report_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "TextReport", + "exec_type": "executable", + "module_type": "report_module", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/TextReport $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json b/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json new file mode 100644 index 00000000..2f476462 --- /dev/null +++ b/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "DemoCheckerBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\DemoCheckerBundle.exe %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] +} diff --git a/runtime/tests/test_data/windows/framework_manifest.json b/runtime/tests/test_data/windows/framework_manifest.json new file mode 100644 index 00000000..e5253474 --- /dev/null +++ b/runtime/tests/test_data/windows/framework_manifest.json @@ -0,0 +1,7 @@ +{ + "manifest_file_path": [ + "tests\\test_data\\windows\\demo_checker_bundle_manifest.json", + "tests\\test_data\\windows\\result_pooling_manifest.json", + "tests\\test_data\\windows\\text_report_manifest.json" + ] +} diff --git a/runtime/tests/test_data/windows/result_pooling_manifest.json b/runtime/tests/test_data/windows/result_pooling_manifest.json new file mode 100644 index 00000000..6ef2fc7e --- /dev/null +++ b/runtime/tests/test_data/windows/result_pooling_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "ResultPooling", + "exec_type": "executable", + "module_type": "result_pooling", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\ResultPooling.exe %ASAM_QC_FRAMEWORK_WORKING_DIR% %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] + } diff --git a/runtime/tests/test_data/windows/text_report_manifest.json b/runtime/tests/test_data/windows/text_report_manifest.json new file mode 100644 index 00000000..ca2be181 --- /dev/null +++ b/runtime/tests/test_data/windows/text_report_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "TextReport", + "exec_type": "executable", + "module_type": "report_module", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\TextReport.exe %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] + } diff --git a/runtime/tests/test_runtime.py b/runtime/tests/test_runtime.py index 7d2a4f48..5b867a65 100644 --- a/runtime/tests/test_runtime.py +++ b/runtime/tests/test_runtime.py @@ -4,128 +4,64 @@ # with this file, You can obtain one at https://mozilla.org/MPL/2.0/. import pytest -import runtime.runtime as runtime import os -import subprocess -from lxml import etree -from typing import List - - -def is_valid_xml(xml_file: str, schema_file: str) -> bool: - """Check if input xml file (.xml) is valid against the input schema file (.xsd) +import sys - Args: - xml_file (str): XML file path to test - schema_file (str): XSD file path containing the schema for the validation - - Returns: - bool: True if file pointed by xml_file is valid w.r.t. input schema file. False otherwise - """ - with open(schema_file, "rb") as schema_f: - schema_doc = etree.parse(schema_f) - schema = etree.XMLSchema(schema_doc) - - with open(xml_file, "rb") as xml_f: - xml_doc = etree.parse(xml_f) - - if schema.validate(xml_doc): - print("XML is valid.") - return True - else: - print("XML is invalid!") - for error in schema.error_log: - print(error.message) - return False +import runtime.runtime as runtime -def check_node_exists(xml_file: str, node_name: str) -> bool: - """Check if input node node_name is present in xml document specified in xml_file - Args: - xml_file (str): XML file path to inspect - node_name (str): Name of the node to search in the xml doc +def on_windows() -> bool: + """Check if script is executed in Windows OS Returns: - bool: True if node at least 1 node called node_name is found in the xml file + bool: True if executing the script on Windows """ - # Parse the XML document - tree = etree.parse(xml_file) - # Use XPath to search for the node - nodes = tree.xpath(f'//*[local-name()="{node_name}"]') - return len(nodes) > 0 + return os.name == "nt" -def test_runtime_execution(): - - start_wd = os.getcwd() - install_dir = os.path.join("..", "bin") - os.chdir(install_dir) - - config_xml = os.path.join( - "..", "runtime", "tests", "test_data", "DemoCheckerBundle_config.xml" +def launch_main(monkeypatch, config_file_path: str, manifest_file_path: str): + monkeypatch.setattr( + sys, + "argv", + ["main.py", f"--config={config_file_path}", f"--manifest={manifest_file_path}"], ) + runtime.main() - runtime_script = os.path.join("..", "runtime", "runtime", "runtime.py") - - process = subprocess.Popen( - f"python3 {runtime_script} --config={config_xml} --install_dir={os.getcwd()}", - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.getcwd(), - ) - stdout, stderr = process.communicate() - exit_code = process.returncode - if exit_code == 0: - print("Command executed successfully.") - print("Output:") - print(stdout.decode()) - else: - print("Error occurred while executing the command.") - print("Error message:") - print(stderr.decode()) - # Check that result file is correctly generated - result_file = os.path.join("Result.xqar") - assert os.path.isfile(result_file) - # Check that at least one node called "Issue" is present in the result - node_name = "Issue" - assert check_node_exists(result_file, node_name) - os.chdir(start_wd) +# The test expects the binaries configured in the `3step_config.xml` are present +# in the `../bin` relative path as configured in the +# `test_data/framework_manifest.json` +def test_3steps_manifest(monkeypatch): + cwd = os.getcwd() + config_xml = os.path.join(cwd, "tests", "test_data", "3step_config.xml") + os_path = "linux" + if on_windows(): + os_path = "windows" -def test_3steps_config(): + manifest_json = os.path.join( + cwd, "tests", "test_data", os_path, "framework_manifest.json" + ) - start_wd = os.getcwd() - install_dir = os.path.join("..", "bin") - os.chdir(install_dir) + launch_main(monkeypatch, config_xml, manifest_json) - config_xml = os.path.join("..", "runtime", "tests", "test_data", "3steps_config.xml") + checker_bundle_output_generated = False + result_xqar_generated = False + report_txt_generated = False - runtime_script = os.path.join("..", "runtime", "runtime", "runtime.py") + for _, _, files in os.walk("./"): + for file in files: + if "DemoCheckerBundle.xqar" in file: + checker_bundle_output_generated = True + if "Result.xqar" in file: + result_xqar_generated = True + if "Report.txt" in file: + report_txt_generated = True - process = subprocess.Popen( - f"python3 {runtime_script} --config={config_xml} --install_dir={os.getcwd()}", - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.getcwd(), - ) - stdout, stderr = process.communicate() - exit_code = process.returncode - if exit_code == 0: - print("Command executed successfully.") - print("Output:") - print(stdout.decode()) - else: - print("Error occurred while executing the command.") - print("Error message:") - print(stderr.decode()) + # Check that checker bundle output file is correctly generated + assert checker_bundle_output_generated == True # Check that result file is correctly generated - result_file = os.path.join("Result.xqar") - assert os.path.isfile(result_file) + assert result_xqar_generated == True # Check that report txt file is correctly generated - result_file = os.path.join("Report.txt") - assert os.path.isfile(result_file) - - os.chdir(start_wd) + assert report_txt_generated == True From 069eef010b390e22db98a139b92425f838eadafc Mon Sep 17 00:00:00 2001 From: romanodanilo <62891297+romanodanilo@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:54:50 +0200 Subject: [PATCH 04/17] Add esmini viewer plugin example (#136) Signed-off-by: romanodanilo Signed-off-by: romanodanilo <62891297+romanodanilo@users.noreply.github.com> --- examples/CMakeLists.txt | 1 + examples/esmini_viewer/CMakeLists.txt | 37 +++++++ examples/esmini_viewer/README.md | 41 +++++++ examples/esmini_viewer/esmini_viewer.cpp | 111 +++++++++++++++++++ examples/esmini_viewer/helper.cpp | 55 +++++++++ examples/esmini_viewer/helper.h | 16 +++ examples/esmini_viewer/qc4openx_filesystem.h | 24 ++++ 7 files changed, 285 insertions(+) create mode 100644 examples/esmini_viewer/CMakeLists.txt create mode 100644 examples/esmini_viewer/README.md create mode 100644 examples/esmini_viewer/esmini_viewer.cpp create mode 100644 examples/esmini_viewer/helper.cpp create mode 100644 examples/esmini_viewer/helper.h create mode 100644 examples/esmini_viewer/qc4openx_filesystem.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5cb9aec3..76cd86b8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(checker_bundle_example) add_subdirectory(viewer_example) +add_subdirectory(esmini_viewer) install( DIRECTORY diff --git a/examples/esmini_viewer/CMakeLists.txt b/examples/esmini_viewer/CMakeLists.txt new file mode 100644 index 00000000..d5b9b451 --- /dev/null +++ b/examples/esmini_viewer/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2023 CARIAD SE. +# +# This Source Code Form is subject to the terms of the Mozilla +# Public License, v. 2.0. If a copy of the MPL was not distributed +# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +cmake_minimum_required(VERSION 3.16 FATAL_ERROR) # CMake policy CMP0095 +cmake_policy(SET CMP0095 NEW) # RPATH entries are properly escaped in the intermediary CMake install script + +set(VIEWER EsminiViewer) +project(${VIEWER}) + +set(CMAKE_INSTALL_RPATH $ORIGIN/../../lib) + +set_property(GLOBAL PROPERTY USE_FOLDERS true) + +include_directories(${VIEWER} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/report_modules/report_module_gui/src +) + +add_library(${VIEWER} SHARED esmini_viewer.cpp helper.cpp) +target_link_libraries(${VIEWER} PRIVATE qc4openx-common $<$:stdc++fs>) + +install( + TARGETS ${VIEWER} + DESTINATION bin/plugin +) + +install( + FILES + esmini_viewer.cpp + DESTINATION + examples/esmini_viewer/src +) + + +set_target_properties(${VIEWER} PROPERTIES FOLDER examples/viewer) diff --git a/examples/esmini_viewer/README.md b/examples/esmini_viewer/README.md new file mode 100644 index 00000000..cf99fb94 --- /dev/null +++ b/examples/esmini_viewer/README.md @@ -0,0 +1,41 @@ +# Esmini viewer plugin + +This folder contain the plugin to add an odr viewer based on esmini-odrviewer application + +## Installation + +After building the framework, a new plugin will be shown in the `File` dropdown menu of the ReportGUI application + +The plugin executes the command + +``` +odrviewer --density 0 --window 100 100 800 800 --odr YOUR_ODR +``` + +So in order to execute this you need to + +1. Download esmini-demo from [esmini release page](https://github.com/esmini/esmini/releases) +2. Install `odrviewer` in your system path + + - Linux install + Long term installation: + + ``` + sudo mv esmini-demo/bin/odrviewer /usr/local/bin/ + ``` + + Short term installation: + + ``` + export PATH=$PATH:esmini-demo/bin/ + ``` + +## Usage + +The plugin checks the input file pointed by the xqar file you are inspecting: + +- If it is an OTX file, the plugin does not show anything +- If it is an ODR file, the plugin shows it +- If it is an OSC file, the plugin get the correspoding openDRIVE file from the input openSCENARIO and shows that road network + +Currently the plugin has only the functionality of showing the road network and it is presented for demonstration purposes. diff --git a/examples/esmini_viewer/esmini_viewer.cpp b/examples/esmini_viewer/esmini_viewer.cpp new file mode 100644 index 00000000..04fe4f35 --- /dev/null +++ b/examples/esmini_viewer/esmini_viewer.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2023 CARIAD SE. + * + * This Source Code Form is subject to the terms of the Mozilla + * Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "common/result_format/c_issue.h" +#include "common/result_format/c_locations_container.h" +#include "common/util.h" +#include "helper.h" +#include "viewer/i_connector.h" +#include +#include + +const char *lasterrormsg = ""; + +bool StartViewer() +{ + std::cout << "START Odr Viewer" << std::endl; + return true; +} + +std::string getFileExtension(const std::string &filename) +{ + // Find the last occurrence of the dot character in the filename + size_t dotPosition = filename.rfind('.'); + + // Check if a dot was found and it's not the first character + if (dotPosition != std::string::npos && dotPosition != 0) + { + return filename.substr(dotPosition + 1); // Return the extension without the dot + } + else + { + return ""; // No valid extension found + } +} + +bool Initialize(const char *inputPath) +{ + if (std::strcmp(inputPath, "") == 0) + { + lasterrormsg = "ERROR: No valid xosc or xodr file found."; + return false; + } + + bool viewerAvailable = IsExecutableAvailable("odrviewer"); + + if (!viewerAvailable) + { + lasterrormsg = "ERROR: Odrivewer executable not found in system path. Please follow " + "qc-framework/examples/esmini_viewer/README.md for install instructions"; + return false; + } + + std::cout << "INITILAIZE VIEWER WITH INPUT FILE: " << inputPath << std::endl; + std::string strResultMessage; + std::string inputFileExtension = getFileExtension(inputPath); + + std::string odrToShow; + if (inputFileExtension == "otx") + { + lasterrormsg = "ERROR: Cannot load otx file in odr viewer"; + return false; + } + else if (inputFileExtension == "xosc") + { + bool result = GetXodrFilePathFromXosc(inputPath, odrToShow); + if (not result) + { + lasterrormsg = "ERROR: Cannot retrieve odr from input xosc"; + return false; + } + } + else if (inputFileExtension == "xodr") + { + odrToShow = inputPath; + } + std::cout << "odrToShow : " << odrToShow << std::endl; + bool result = ExecuteCommand(strResultMessage, "odrviewer", + "--density 0 --window 100 100 800 800 --odr " + std::string(odrToShow)); + std::cout << strResultMessage << std::endl; + return result; +} + +bool AddIssue(void *issueToAdd) +{ + return true; +} + +bool ShowIssue(void *itemToShow, void *locationToShow) +{ + return true; +} + +const char *GetName() +{ + return "Esmini Viewer"; +} + +bool CloseViewer() +{ + return true; +} + +const char *GetLastErrorMessage() +{ + return lasterrormsg; +} diff --git a/examples/esmini_viewer/helper.cpp b/examples/esmini_viewer/helper.cpp new file mode 100644 index 00000000..9d15729c --- /dev/null +++ b/examples/esmini_viewer/helper.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2023 CARIAD SE. + * + * This Source Code Form is subject to the terms of the Mozilla + * Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "helper.h" + +bool IsExecutableAvailable(const std::string &executable) +{ +#ifdef _WIN32 + std::string command = "where " + executable + " > nul 2>&1"; +#else + std::string command = "which " + executable + " > /dev/null 2>&1"; +#endif + + return std::system(command.c_str()) == 0; +} + +bool ExecuteCommand(std::string &strResultMessage, std::string strCommand, const std::string strArgument) +{ + std::string strTotalCommand = ""; + +#ifdef WIN32 + strCommand.append(".exe"); +#endif + strTotalCommand.append(strCommand.c_str()); + + if (IsExecutableAvailable(strCommand)) + { + if (!strArgument.empty()) + { + strTotalCommand.append(" "); + strTotalCommand.append(strArgument.c_str()); + } + + int32_t i32Res = system(strTotalCommand.c_str()); + if (i32Res != 0) + { + strResultMessage = + "Command executed with result " + std::to_string(i32Res) + ". Command: '" + strTotalCommand + "'."; + return false; + } + else + { + return true; + } + } + + strResultMessage = + "Command to execute was not found in any defined install directory. Command: '" + strTotalCommand + "'."; + return false; +} diff --git a/examples/esmini_viewer/helper.h b/examples/esmini_viewer/helper.h new file mode 100644 index 00000000..9cafe494 --- /dev/null +++ b/examples/esmini_viewer/helper.h @@ -0,0 +1,16 @@ +/* + * Copyright 2023 CARIAD SE. + * + * This Source Code Form is subject to the terms of the Mozilla + * Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#ifndef _HELPER_HEADER_ +#define _HELPER_HEADER_ + +#include "qc4openx_filesystem.h" + +bool IsExecutableAvailable(const std::string &executable); +bool ExecuteCommand(std::string &strResultMessage, std::string strCommand, const std::string strArgument = ""); + +#endif diff --git a/examples/esmini_viewer/qc4openx_filesystem.h b/examples/esmini_viewer/qc4openx_filesystem.h new file mode 100644 index 00000000..f82be48d --- /dev/null +++ b/examples/esmini_viewer/qc4openx_filesystem.h @@ -0,0 +1,24 @@ +/** + * Copyright 2023 CARIAD SE. + * + * This Source Code Form is subject to the terms of the Mozilla + * Public License, v. 2.0. If a copy of the MPL was not distributed + * with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef QC4OPENX_FILESYSTEM_H_ +#define QC4OPENX_FILESYSTEM_H_ + +#ifdef __has_include +#if __has_include() +#include +namespace fs = std::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif +#else +#error __has_include is not defined, but is needed to detect correct filesystem! +#endif + +#endif From 09c960a38fc15f8e7126927d4013066496befd08 Mon Sep 17 00:00:00 2001 From: romanodanilo <62891297+romanodanilo@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:02:05 +0200 Subject: [PATCH 05/17] Update artifact name and fix EsminiViewer build (#137) Signed-off-by: romanodanilo Signed-off-by: romanodanilo <62891297+romanodanilo@users.noreply.github.com> --- .github/workflows/build-on-change-linux-bare.yaml | 2 +- examples/esmini_viewer/CMakeLists.txt | 14 +++++++------- examples/esmini_viewer/esmini_viewer.cpp | 2 +- examples/esmini_viewer/helper.cpp | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-on-change-linux-bare.yaml b/.github/workflows/build-on-change-linux-bare.yaml index 996d0394..4c6968d1 100644 --- a/.github/workflows/build-on-change-linux-bare.yaml +++ b/.github/workflows/build-on-change-linux-bare.yaml @@ -93,7 +93,7 @@ jobs: - name: Upload release artifact uses: actions/upload-artifact@v4 with: - name: qc-framework-executables-ubuntu + name: qc-framework-executables-linux_x64 path: artifacts - name: Unit test execution diff --git a/examples/esmini_viewer/CMakeLists.txt b/examples/esmini_viewer/CMakeLists.txt index d5b9b451..1c8633d3 100644 --- a/examples/esmini_viewer/CMakeLists.txt +++ b/examples/esmini_viewer/CMakeLists.txt @@ -7,22 +7,22 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) # CMake policy CMP0095 cmake_policy(SET CMP0095 NEW) # RPATH entries are properly escaped in the intermediary CMake install script -set(VIEWER EsminiViewer) -project(${VIEWER}) +set(ESMINI_VIEWER EsminiViewer) +project(${ESMINI_VIEWER}) set(CMAKE_INSTALL_RPATH $ORIGIN/../../lib) set_property(GLOBAL PROPERTY USE_FOLDERS true) -include_directories(${VIEWER} PRIVATE +include_directories(${ESMINI_VIEWER} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../src/report_modules/report_module_gui/src ) -add_library(${VIEWER} SHARED esmini_viewer.cpp helper.cpp) -target_link_libraries(${VIEWER} PRIVATE qc4openx-common $<$:stdc++fs>) +add_library(${ESMINI_VIEWER} SHARED esmini_viewer.cpp helper.cpp) +target_link_libraries(${ESMINI_VIEWER} PRIVATE qc4openx-common $<$:stdc++fs>) install( - TARGETS ${VIEWER} + TARGETS ${ESMINI_VIEWER} DESTINATION bin/plugin ) @@ -34,4 +34,4 @@ install( ) -set_target_properties(${VIEWER} PROPERTIES FOLDER examples/viewer) +set_target_properties(${ESMINI_VIEWER} PROPERTIES FOLDER examples/viewer) diff --git a/examples/esmini_viewer/esmini_viewer.cpp b/examples/esmini_viewer/esmini_viewer.cpp index 04fe4f35..ad22ebaa 100644 --- a/examples/esmini_viewer/esmini_viewer.cpp +++ b/examples/esmini_viewer/esmini_viewer.cpp @@ -68,7 +68,7 @@ bool Initialize(const char *inputPath) else if (inputFileExtension == "xosc") { bool result = GetXodrFilePathFromXosc(inputPath, odrToShow); - if (not result) + if (!result) { lasterrormsg = "ERROR: Cannot retrieve odr from input xosc"; return false; diff --git a/examples/esmini_viewer/helper.cpp b/examples/esmini_viewer/helper.cpp index 9d15729c..ee75feed 100644 --- a/examples/esmini_viewer/helper.cpp +++ b/examples/esmini_viewer/helper.cpp @@ -7,6 +7,7 @@ */ #include "helper.h" +#include bool IsExecutableAvailable(const std::string &executable) { From 9460a49144d51c2d1dbd9b05e9e133686f39325b Mon Sep 17 00:00:00 2001 From: Hoang Tung Dinh Date: Fri, 23 Aug 2024 18:35:06 +0200 Subject: [PATCH 06/17] runtime: Support optional argument to specify working dir (#138) Signed-off-by: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> --- demo_pipeline/run_pipeline.sh | 10 ++++----- demo_pipeline/templates/otx_template.xml | 4 ++-- demo_pipeline/templates/xodr_template.xml | 4 ++-- demo_pipeline/templates/xosc_template.xml | 4 ++-- doc/manual/runtime_module.md | 8 +++++++ runtime/runtime/runtime.py | 27 +++++++++++++++++------ 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/demo_pipeline/run_pipeline.sh b/demo_pipeline/run_pipeline.sh index bac35fc1..72dda2df 100755 --- a/demo_pipeline/run_pipeline.sh +++ b/demo_pipeline/run_pipeline.sh @@ -5,11 +5,11 @@ python3 /app/demo_pipeline/configuration_generator.py +OUTPUT_DIR=/out/qc-result-$INPUT_FILENAME + qc_runtime \ --config "/tmp/generated_config/config.xml" \ - --manifest "/app/demo_pipeline/manifests/framework_manifest.json" + --manifest "/app/demo_pipeline/manifests/framework_manifest.json" \ + --working_dir "$OUTPUT_DIR" -mkdir -p /out/qc-result-$INPUT_FILENAME -cp /app/framework/bin/*.xqar /out/qc-result-$INPUT_FILENAME -cp /app/framework/bin/*.txt /out/qc-result-$INPUT_FILENAME -chown -R $USER_ID:$GROUP_ID /out/qc-result-$INPUT_FILENAME +chown -R $USER_ID:$GROUP_ID $OUTPUT_DIR diff --git a/demo_pipeline/templates/otx_template.xml b/demo_pipeline/templates/otx_template.xml index a5b9ff9a..a3100f27 100644 --- a/demo_pipeline/templates/otx_template.xml +++ b/demo_pipeline/templates/otx_template.xml @@ -4,7 +4,7 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/demo_pipeline/templates/xodr_template.xml b/demo_pipeline/templates/xodr_template.xml index 54fede79..230926f2 100644 --- a/demo_pipeline/templates/xodr_template.xml +++ b/demo_pipeline/templates/xodr_template.xml @@ -4,7 +4,7 @@ - + @@ -12,7 +12,7 @@ - + diff --git a/demo_pipeline/templates/xosc_template.xml b/demo_pipeline/templates/xosc_template.xml index 6a12143d..e14b76d0 100644 --- a/demo_pipeline/templates/xosc_template.xml +++ b/demo_pipeline/templates/xosc_template.xml @@ -4,7 +4,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/doc/manual/runtime_module.md b/doc/manual/runtime_module.md index 73083e23..4724bf4b 100644 --- a/doc/manual/runtime_module.md +++ b/doc/manual/runtime_module.md @@ -29,3 +29,11 @@ where - `$PATH_TO_CONFIG_FILE` points to an xml file following the [config xsd schema](../doc/schema/config_format.xsd) - `$PATH_TO_MANIFEST_FILE` points to the [manifest file](manifest_file.md) of the framework. + +All the files generated during the runtime execution, including result files and report files, will be saved in the output folder `qc-output-YYYY-MM-DD-HH-MM-SS-*`. + +Alternatively, users can specify a specific output folder for the runtime using the argument `--working_dir`. + +```bash +qc_runtime --config=$PATH_TO_CONFIG_FILE --manifest=$PATH_TO_MANIFEST_FILE --working_dir=$PATH_TO_OUTPUT_FOLDER +``` diff --git a/runtime/runtime/runtime.py b/runtime/runtime/runtime.py index 3457ff41..3092512d 100644 --- a/runtime/runtime/runtime.py +++ b/runtime/runtime/runtime.py @@ -106,13 +106,14 @@ def execute_modules( run_module_command(report_module, config_file_path, output_path) -def execute_runtime(config_file_path: str, manifest_file_path: str) -> None: +def execute_runtime(config_file_path: str, manifest_file_path: str, working_dir: str) -> None: """Execute all runtime operations defined in the input manifest over the defined configuration. Args: config_file_path (str): input configuration xml file path manifest_file_path (str): input manifest json file path + working_dir (str): working directory """ checker_bundles = {} @@ -157,16 +158,12 @@ def execute_runtime(config_file_path: str, manifest_file_path: str) -> None: f"No result pooling module found in the provided framework manifest. There must be exactly one result pooling module defined." ) - formatted_now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f") - execution_output_path = os.path.join(os.getcwd(), formatted_now) - os.makedirs(execution_output_path, exist_ok=True) - execute_modules( config_file_path, checker_bundles, result_pooling, report_modules, - execution_output_path, + working_dir, ) @@ -185,9 +182,25 @@ def main(): required=True, ) + parser.add_argument( + "--working_dir", + type=str, + help="Working directory where all the output files are generated.", + required=False, + ) + args = parser.parse_args() - execute_runtime(args.config, args.manifest) + working_dir = None + if args.working_dir is None: + formatted_now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f") + working_dir = os.path.join(os.getcwd(), f"qc-output-{formatted_now}") + else: + working_dir = os.path.abspath(args.working_dir) + + os.makedirs(working_dir, exist_ok=True) + + execute_runtime(args.config, args.manifest, working_dir) if __name__ == "__main__": From 47ea18a6ce5466053c21e2e594e0d5dd11986d2f Mon Sep 17 00:00:00 2001 From: Hoang Tung Dinh Date: Mon, 26 Aug 2024 10:04:42 +0200 Subject: [PATCH 07/17] Update build instructions (#140) Signed-off-by: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> --- INSTALL.md | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 07736c75..c5855cf3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,6 +26,15 @@ The ASAM Quality Checker Framework runs on Linux and Windows. The framework cons Links to download the sources and the tested versions can be found in the [license information appendix](licenses/readme.md). +## Build and install C++ modules + +- Use CMakeLists.txt within the main directory as source directory +- Do not forget to set `CMAKE_INSTALL_PREFIX` +- Do not forget to set `CMAKE_BUILD_TYPE` if using CMake generator `Unix + Makefiles` + +### Build on Linux + On Linux, toolchain and 3rd party dependencies can be installed as follows (example for Ubuntu 22.04). ```bash @@ -44,14 +53,7 @@ apt update && apt install -y \ git ``` -## Build and install C++ modules - -- Use CMakeLists.txt within the main directory as source directory -- Do not forget to set `CMAKE_INSTALL_PREFIX` -- Do not forget to set `CMAKE_BUILD_TYPE` if using CMake generator `Unix - Makefiles` - -For Linux, an example CMake call to build the framework +An example CMake call to build the framework looks like this (call from the repository root): ```bash @@ -64,6 +66,22 @@ cmake --build ./build --target install --config Release -j4 cmake --install ./build ``` +### Build on Windows + +On Windows, an example build for the the dependency XercesC looks like this: + +```bash +$xercesZip = "$env:WORKING_PATH\xerces-c-3.2.5.zip" +Invoke-WebRequest -Uri "https://dlcdn.apache.org/xerces/c/3/sources/xerces-c-3.2.5.zip" -OutFile $xercesZip +Expand-Archive -Path $xercesZip -DestinationPath "$env:WORKING_PATH" +cd "$env:WORKING_PATH\xerces-c-3.2.5" +mkdir build +cd build +cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX="$env:WORKING_PATH\Xerces-Out" .. +cmake --build . --config Debug +cmake --build . --config Debug --target install +``` + For Windows Visual Studio 16 2019 an example CMake call to build the framework looks like this (call from the repository root): @@ -73,9 +91,8 @@ $ cmake -G "Visual Studio 16 2019" -A "x64" -T "v142" -B../build -S. ^ -DCMAKE_INSTALL_PREFIX="" ^ -DENABLE_FUNCTIONAL_TESTS=ON ^ -DGTest_ROOT="" ^ - -DASAM_OPENDRIVE_XSD_DIR="" ^ - -DASAM_OPENSCENARIO_XSD_DIR="" ^ - -DQt5_ROOT="" + -DQt5_ROOT="" ^ + -DXercesC_ROOT="" $ cmake --build ../build --target ALL_BUILD --config Release $ ctest --test-dir ../build -C Release $ cmake --install ../build @@ -85,12 +102,6 @@ With the following CMake values: - _\_: The prefix CMake installs the package to - _\_: The root dir of the pre-built GoogleTest package -- _\_: The directory containing the schema (*.xsd) - files for OpenDRIVE downloaded from the ASAM website (multiple versions of - the schema files in this directory are supported). -- _\_: The directory containing the schema (*.xsd) - files for OpenSCENARIO downloaded from the ASAM website (multiple versions of - the schema files in this directory are supported). - _\_: The root dir of the pre-built Xerces-C++ package - _\_: The root dir of the pre-built qt5 package From c53c7cf9eebd6ef3490939ed13b5e1b0a0b0c895 Mon Sep 17 00:00:00 2001 From: Hoang Tung Dinh Date: Mon, 26 Aug 2024 10:54:19 +0200 Subject: [PATCH 08/17] Update build config for XercesC to Release (#141) Signed-off-by: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> --- .github/workflows/build-on-change-windows.yaml | 4 ++-- INSTALL.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-on-change-windows.yaml b/.github/workflows/build-on-change-windows.yaml index 46b40681..d51e5246 100644 --- a/.github/workflows/build-on-change-windows.yaml +++ b/.github/workflows/build-on-change-windows.yaml @@ -84,8 +84,8 @@ jobs: mkdir build cd build cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX="$env:WORKING_PATH\Xerces-Out" .. - cmake --build . --config Debug - cmake --build . --config Debug --target install + cmake --build . --config Release + cmake --build . --config Release --target install Add-Content $env:GITHUB_PATH "$env:WORKING_PATH\bin\xerces-c_3_2D.dll" Add-Content $env:GITHUB_PATH "$env:WORKING_PATH\Xerces-Out\include" diff --git a/INSTALL.md b/INSTALL.md index c5855cf3..beaed115 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -78,8 +78,8 @@ cd "$env:WORKING_PATH\xerces-c-3.2.5" mkdir build cd build cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX="$env:WORKING_PATH\Xerces-Out" .. -cmake --build . --config Debug -cmake --build . --config Debug --target install +cmake --build . --config Release +cmake --build . --config Release --target install ``` For Windows Visual Studio 16 2019 an example CMake call to build the framework From deec028e03c431494e89b1057bb9fa01e0fdff25 Mon Sep 17 00:00:00 2001 From: Hoang Tung Dinh Date: Mon, 26 Aug 2024 12:16:09 +0200 Subject: [PATCH 09/17] Update documentation to explain about result file names (#143) Signed-off-by: hoangtungdinh <11166240+hoangtungdinh@users.noreply.github.com> --- doc/manual/file_formats.md | 44 ++++++++++++++++++++----------- doc/manual/using_the_framework.md | 2 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/doc/manual/file_formats.md b/doc/manual/file_formats.md index 964277aa..31ef1bf2 100644 --- a/doc/manual/file_formats.md +++ b/doc/manual/file_formats.md @@ -11,34 +11,46 @@ with this file, You can obtain one at https://mozilla.org/MPL/2.0/. ## Configuration File (`*.xml`) The runtime configuration settings are stored in a configuration file. This -file defines which CheckerBundles and what checkers are used, how they are +file defines which Checker Bundles and what checkers are used, how they are parameterized and whether the issues are warnings or errors. If a CheckerBundle outputs errors that are not configured in this file, the result pooling removes them later on. This way only the relevant results are included in the overall -result. Furthermore, it can be configured which ReportModules are started after -all CheckerBundles finish execution. +result. Furthermore, it can be configured which Report Modules are started after +all Checker Bundles finish execution. ```xml + - - - - - - - - + + + + + + + + + + + + + + + ``` -The runtime parses this file, then executes the configured CheckerBundles, and +The runtime parses this file, then executes the configured Checker Bundles, and hands them the configuration file. The parameter "application" to the XML tags `` and `` specifies the name of the executable to be used. -Notes for the paths: +**NOTE ABOUT FILE NAMES IN CONFIGURATION FILE** -- Results will be stored in the directory from which the call is made. +The name `Result.xqar` is reserved for the output of the Result Pooling module. The framework automatically created the `Result.xqar` file in each execution. Therefore: +* The name `Result.xqar` **MUST NOT** be used as the name of the result `.xqar` file for any Checker Bundle. +* The file `Result.xqar` can be used as the input file for Report Modules. +* The result file of each checker bundle must have the postfix `.xqar`. It must be a file name (e.g., `my-bundle-result.xqar`) and must not contain any path (both asolute path and relative path are not allowed). +* All the result files and the automatically generated `Result.xqar` will be stored in the in the output folder `qc-output-YYYY-MM-DD-HH-MM-SS-*`. It is also possible to configure the output folder path. For more details see the [Runtime Module documentation](runtime_module.md). ## Result File (`*.xqar`) @@ -67,7 +79,7 @@ or semantic flaws. - Optional external files (e. g. Images of generated graphs such as speed over distance). Currently not supported. -The following example shows the results obtained by running two CheckerBundles, +The following example shows the results obtained by running two Checker Bundles, one called SyntaxChecker and one SemanticChecker. ```xml @@ -119,6 +131,6 @@ where the default parameters are shown: (... more Checkers...) - (... more CheckerBundles...) + (... more Checker Bundles...) ``` diff --git a/doc/manual/using_the_framework.md b/doc/manual/using_the_framework.md index 42a2baca..42b0073e 100644 --- a/doc/manual/using_the_framework.md +++ b/doc/manual/using_the_framework.md @@ -10,7 +10,7 @@ with this file, You can obtain one at https://mozilla.org/MPL/2.0/. ## Create Configuration File -Create a [configuration file](file_formats.md) to use the framework, following [the predefined configuration file schema](https://github.com/asam-ev/qc-framework/blob/develop/doc/schema/config_format.xsd). +Create a [configuration file](file_formats.md) to use the framework, following [the predefined configuration file schema](https://github.com/asam-ev/qc-framework/blob/develop/doc/schema/config_format.xsd) and the explanation in [the configuration file documentation](file_formats.md). Example configuration files for running the official Checker Bundles for ASAM OpenDrive, OpenScenario XML and OTX can be found in [the templates folder](https://github.com/asam-ev/qc-framework/tree/develop/demo_pipeline/templates). From 5a390f2ad1463ea398a0bffa5e1b462fd5f151e3 Mon Sep 17 00:00:00 2001 From: romanodanilo <62891297+romanodanilo@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:44:54 +0200 Subject: [PATCH 10/17] Update windows docs (#144) Signed-off-by: romanodanilo Signed-off-by: romanodanilo <62891297+romanodanilo@users.noreply.github.com> --- INSTALL.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index beaed115..e240848f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,6 +1,6 @@