From ee286890d7cd0921fc16e3fb9fe33ba1551ff4ed Mon Sep 17 00:00:00 2001 From: Sebastian Wieczorek Date: Thu, 10 Sep 2020 16:29:11 +0200 Subject: [PATCH 01/11] QML executor plugin Allow to create components on the fly with QML --- CMakeLists.txt | 9 + src/components/qmlexecutor/CMakeLists.txt | 26 ++ src/components/qmlexecutor/canbusmodel.hpp | 47 ++++ src/components/qmlexecutor/gui/qmlexecutor.ui | 145 ++++++++++ .../qmlexecutor/gui/qmlexecutorguiimpl.h | 254 ++++++++++++++++++ .../qmlexecutor/gui/qmlexecutorguiint.h | 61 +++++ src/components/qmlexecutor/qmlexecutor.cpp | 95 +++++++ src/components/qmlexecutor/qmlexecutor.h | 127 +++++++++ src/components/qmlexecutor/qmlexecutor_p.cpp | 73 +++++ src/components/qmlexecutor/qmlexecutor_p.h | 111 ++++++++ .../qmlexecutor/qmlexecutormodel.cpp | 179 ++++++++++++ src/components/qmlexecutor/qmlexecutormodel.h | 122 +++++++++ .../qmlexecutor/qmlexecutorplugin.h | 16 ++ .../qmlexecutor/tests/qmlexecutor_test.cpp | 89 ++++++ .../tests/qmlexecutormodel_test.cpp | 86 ++++++ src/gui/CMakeLists.txt | 2 +- 16 files changed, 1441 insertions(+), 1 deletion(-) create mode 100644 src/components/qmlexecutor/CMakeLists.txt create mode 100644 src/components/qmlexecutor/canbusmodel.hpp create mode 100644 src/components/qmlexecutor/gui/qmlexecutor.ui create mode 100644 src/components/qmlexecutor/gui/qmlexecutorguiimpl.h create mode 100644 src/components/qmlexecutor/gui/qmlexecutorguiint.h create mode 100644 src/components/qmlexecutor/qmlexecutor.cpp create mode 100644 src/components/qmlexecutor/qmlexecutor.h create mode 100644 src/components/qmlexecutor/qmlexecutor_p.cpp create mode 100644 src/components/qmlexecutor/qmlexecutor_p.h create mode 100644 src/components/qmlexecutor/qmlexecutormodel.cpp create mode 100644 src/components/qmlexecutor/qmlexecutormodel.h create mode 100644 src/components/qmlexecutor/qmlexecutorplugin.h create mode 100644 src/components/qmlexecutor/tests/qmlexecutor_test.cpp create mode 100644 src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d9861919..0f3895033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,12 @@ set(CMAKE_CXX_STANDARD 14) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) +#windows build may need to point to boost, moidify with your boost install dir and uncomment +#set(BOOST_ROOT C:/local/boost_1_73_0/) +#set(BOOST_INCLUDE_DIR C:/local/boost_1_73_0/) +#set(BOOST_LIBRARY_DIR C:/local/boost_1_73_0/libs) + + option(WITH_COVERAGE "Build with coverage" OFF) option(WITH_TOOLS "Build tools directory" OFF) option(DEV_BUILD "Development build will include GIT hash and branch to versioning" ON) @@ -49,6 +55,9 @@ find_package(Qt5SerialBus ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Widgets ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Test ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Svg ${QT_REQUIRED_VERSION} REQUIRED) +find_package(Qt5Qml ${QT_REQUIRED_VERSION} REQUIRED) +find_package(Qt5Quick ${QT_REQUIRED_VERSION} REQUIRED) +find_package(Qt5QuickWidgets ${QT_REQUIRED_VERSION} REQUIRED) message(STATUS "Qt Version found: ${Qt5Core_VERSION}") set_property(TARGET Qt5::SerialBus PROPERTY INTERFACE_COMPILE_FEATURES "") diff --git a/src/components/qmlexecutor/CMakeLists.txt b/src/components/qmlexecutor/CMakeLists.txt new file mode 100644 index 000000000..19cd2d6cb --- /dev/null +++ b/src/components/qmlexecutor/CMakeLists.txt @@ -0,0 +1,26 @@ +set(COMPONENT_NAME qmlexecutor) + +set(SRC + gui/qmlexecutor.ui + gui/qmlexecutorguiimpl.h + gui/qmlexecutorguiint.h + qmlexecutor.cpp + qmlexecutor_p.cpp + qmlexecutormodel.cpp + canbusmodel.hpp +) + +add_library(${COMPONENT_NAME} ${SRC}) +target_link_libraries(${COMPONENT_NAME} cds-common) +target_include_directories(${COMPONENT_NAME} INTERFACE ${Qt5Widgets_INCLUDE_DIRS}) +target_include_directories(${COMPONENT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +if(WITH_TESTS) + add_executable(${COMPONENT_NAME}_test tests/${COMPONENT_NAME}_test.cpp) + target_link_libraries(${COMPONENT_NAME}_test ${COMPONENT_NAME} Qt5::Test fakeit) + add_test(NAME ${COMPONENT_NAME}_test COMMAND ${COMPONENT_NAME}_test) + + add_executable(${COMPONENT_NAME}model_test tests/${COMPONENT_NAME}model_test.cpp) + target_link_libraries(${COMPONENT_NAME}model_test ${COMPONENT_NAME} Qt5::Test fakeit) + add_test(NAME ${COMPONENT_NAME}model_test COMMAND ${COMPONENT_NAME}model_test) +endif() diff --git a/src/components/qmlexecutor/canbusmodel.hpp b/src/components/qmlexecutor/canbusmodel.hpp new file mode 100644 index 000000000..8c723c2a1 --- /dev/null +++ b/src/components/qmlexecutor/canbusmodel.hpp @@ -0,0 +1,47 @@ +#ifndef CANBUSMODEL_HPP +#define CANBUSMODEL_HPP + +#include +#include + +/** + * @brief The CANBusModel class is used to interface CAN operations with QML stack + */ +class CANBusModel : public QObject +{ + Q_OBJECT + +public: + CANBusModel() : QObject(nullptr) {} + +signals: + /** + * @brief Sends a CAN frame + * @param frameId frame identifier + * @param frameData frame payload + */ + void sendFrame(const qint32& frameId, const QByteArray& frameData); + + /** + * @brief Send a CAN named signal + * @param name signal name + * @param value signal value + */ + void sendSignal(const QString& name, const QVariant& value); + + /** + * @brief A CAN frame was received + * @param frameId frame identifier + * @param frameData frame payload + */ + void frameReceived(const qint32& frameId, const QByteArray& frameData); + + /** + * @brief A CAN signal was received + * @param signal name + * @param signal value + */ + void signalReceived(const QString& name, const QVariant& value); +}; + +#endif // CANBUSMODEL_HPP diff --git a/src/components/qmlexecutor/gui/qmlexecutor.ui b/src/components/qmlexecutor/gui/qmlexecutor.ui new file mode 100644 index 000000000..6e4e74808 --- /dev/null +++ b/src/components/qmlexecutor/gui/qmlexecutor.ui @@ -0,0 +1,145 @@ + + + QMLExecutorPrivate + + + + 0 + 0 + 1381 + 770 + + + + QMLExecutor + + + + + + + + + + Load QML + + + + + + + true + + + + + + + + MS Shell Dlg 2 + + + + Edit QML + + + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + false + + + false + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Plain + + + 1 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QQuickWidget::SizeRootObjectToView + + + + + + + + + 16777215 + 16777215 + + + + QFrame::Box + + + QFrame::Plain + + + true + + + + + + + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
+
+ + +
diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h new file mode 100644 index 000000000..cb1ce1b4e --- /dev/null +++ b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h @@ -0,0 +1,254 @@ +#ifndef QMLEXECUTORGUIIMPL_H +#define QMLEXECUTORGUIIMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui_qmlexecutor.h" +#include "qmlexecutorguiint.h" +#include "canbusmodel.hpp" + +/** + * @brief QMLExecutor plugin gui implementation + */ +class QMLExecutorGuiImpl : public QMLExecutorGuiInt +{ + Q_OBJECT + +public: + /** + * @brief explicit constructor + * @param parent qt QObject requirement, possibly to remove + */ + explicit QMLExecutorGuiImpl(QObject* parent) + : QMLExecutorGuiInt(parent) + , _ui(new Ui::QMLExecutorPrivate) + , _widget(new QWidget) + , _qmlUpdateCheckTimer(this) + { + _ui->setupUi(_widget); + + _ui->splitter->setCollapsible(1, true); + + constexpr int size = 1234; + _ui->splitter->setSizes(QList({size,size})); + + QObject::connect(_ui->quickWidget, &QQuickWidget::statusChanged, this, &QMLExecutorGuiImpl::handleStatusChange); + QObject::connect(_ui->editQMLButton, SIGNAL(clicked()), this, SLOT(editQML())); + QObject::connect(_ui->loadQMLButton, SIGNAL(clicked()), this, SLOT(browseAndOpenQML())); + QObject::connect(&_qmlUpdateCheckTimer, SIGNAL(timeout()), this, SLOT(checkQMLModification())); + } + + virtual QWidget* mainWidget() override + { + return _widget; + } + +public slots: + /** + * @brief Slot to do browsing for QML file, calls qml load + */ + void browseAndOpenQML() + { + QUrl url = QFileDialog::getOpenFileUrl(mainWidget(), "Open QML"); + + if(!url.isEmpty() && url.isValid()) + { + loadQML(url); + } + } + + /** + * @brief loads qml into a quick widget + * @param url url of a local file to load + */ + virtual void loadQML(const QUrl& url) override + { + if(url.isValid() && !url.isEmpty()) + { + _qmlUrl = url; + + _ui->editQMLButton->setEnabled(true); + + log("Loading qml file: " + url.toLocalFile()); + + _ui->quickWidget->setSource(QUrl()); + _ui->quickWidget->engine()->clearComponentCache(); + + assert(_CANBusModel != nullptr); + _ui->quickWidget->rootContext()->setContextProperty("CANBusModel", _CANBusModel); + + _ui->quickWidget->setSource(url); + + _ui->fileName->setText(url.url()); + + + emit QMLLoaded(_qmlUrl); + + startQMLFileModificationChecks(); + } + + } + + /** + * @brief remembers can bus model for qml + * @param model can bus model + */ + virtual void setModel(CANBusModel* model) override + { + _CANBusModel = model; + } + + /** + * @brief starts checking for qml file modification + */ + void startQMLFileModificationChecks() + { + _qmlLoadTime = QDateTime::currentDateTime(); + _qmlUpdateCheckTimer.start(std::chrono::seconds(1)); + } + + /** + * @brief stops cheking for qml file modification + */ + void stopFileModificationChecks() + { + _qmlUpdateCheckTimer.stop(); + } + + /** + * @brief checks if qml file was modified, ask the user and reloads it if needed + */ + void checkQMLModification() + { + QDateTime modifycationTime = QFileInfo(_qmlUrl.toLocalFile()).lastModified(); + + if(modifycationTime > _qmlLoadTime) + { + stopFileModificationChecks(); + + if(askForQMLReload()) + { + loadQML(_qmlUrl); + + } + + startQMLFileModificationChecks(); + } + } + + /** + * @brief ask if qml reload should be done + * @return true if yes, false if no + */ + bool askForQMLReload() + { + QMessageBox::StandardButton reply; + + reply = QMessageBox::question(mainWidget(), "QML was updated", _qmlUrl.toLocalFile() + " was changed.\nDo you want to reload it?", QMessageBox::Yes|QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + return true; + } + + return false; + } + + /** + * @brief log message to log area + * @param text text to log + */ + void log(const QString& text) + { + _ui->logWindow->appendPlainText(text); + } + + /** + * @brief tries to launch system editor for .qml file + */ + void editQML() + { + if(_qmlUrl.isValid() && !_qmlUrl.isEmpty()) + { + bool status = QDesktopServices::openUrl(_qmlUrl); + + if(!status) + { + log(QString("Failed to launch external editor for file: ") + _qmlUrl.toString()); + } + else + { + log(QString("System editor for QML launched!")); + } + } + } + + +public slots: + /** + * @brief handle status change of qml, just log it + * @param status qml status loading + */ + void handleStatusChange(QQuickWidget::Status status) + { + switch (status) + { + case QQuickWidget::Status::Error: + { + auto errors = _ui->quickWidget->errors(); + + for (auto error : errors) + { + QString errorString = error.toString(); + + log(QString("QML status: error - ") + errorString); + } + } + break; + + case QQuickWidget::Status::Null: + log(QString("QML status: Null")); + break; + + case QQuickWidget::Status::Loading: + log(QString("QML status: Loading")); + break; + + case QQuickWidget::Status::Ready: + log(QString("QML status: Ready")); + break; + } + } + +private: + //! generated ui + Ui::QMLExecutorPrivate* _ui; + + //! main widget + QWidget* _widget; + + //! url of qml file + QUrl _qmlUrl; + + //! qml load time used to determine if file was modified + QDateTime _qmlLoadTime; + + //! timer used to check for qml file modification + QTimer _qmlUpdateCheckTimer; + + CANBusModel* _CANBusModel; +}; + +#endif // QMLEXECUTORGUIIMPL_H diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiint.h b/src/components/qmlexecutor/gui/qmlexecutorguiint.h new file mode 100644 index 000000000..1baf3904e --- /dev/null +++ b/src/components/qmlexecutor/gui/qmlexecutorguiint.h @@ -0,0 +1,61 @@ +#ifndef QMLEXECUTORGUIINT_H +#define QMLEXECUTORGUIINT_H + +#include +#include +#include + +class QWidget; +class CANBusModel; + +/** + * @brief Interface of gui for QMLExecutor plugin + * @todo rename + */ +class QMLExecutorGuiInt : public QObject +{ + + Q_OBJECT + +public: + /** + * @brief constructor + * @param parent qt stuff + */ + QMLExecutorGuiInt(QObject* parent) : QObject(nullptr) {}; + + /** + * @brief virt dtor to get things working + */ + virtual ~QMLExecutorGuiInt() {} + +public: + /** + * @brief not used, return main widget for ui + */ + virtual QWidget* mainWidget() = 0; + +public slots: + /** + * Loads qml file. + * + * @param url url to a qml file + */ + virtual void loadQML(const QUrl& url) = 0; + + /** + * @brief sets can bus model pointer + * @param model can bus model + */ + virtual void setModel(CANBusModel* model) = 0; + +signals: + /** + * @brief this signal is emited when qml was loaded + * @param url url of qml file + */ + void QMLLoaded(const QUrl& url); +}; + + +#endif // QMLEXECUTORGUIINT_H diff --git a/src/components/qmlexecutor/qmlexecutor.cpp b/src/components/qmlexecutor/qmlexecutor.cpp new file mode 100644 index 000000000..4209e3c1c --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutor.cpp @@ -0,0 +1,95 @@ +#include "qmlexecutor.h" +#include "qmlexecutor_p.h" +#include + +#include +#include +#include + + +QMLExecutor::QMLExecutor() + : d_ptr(new QMLExecutorPrivate(this)) +{ +} + +QMLExecutor::QMLExecutor(QMLExecutorCtx&& ctx) + : d_ptr(new QMLExecutorPrivate(this, std::move(ctx))) +{ +} + +QMLExecutor::~QMLExecutor() +{ +} + +QWidget* QMLExecutor::mainWidget() +{ + Q_D(QMLExecutor); + + return d->_ui.mainWidget(); +} + +void QMLExecutor::setConfig(const QJsonObject& json) +{ + Q_D(QMLExecutor); + + d->setSettings(json); +} + +void QMLExecutor::setConfig(const QWidget& qobject) +{ + Q_D(QMLExecutor); + + configHelpers::setQConfig(qobject, getSupportedProperties(), d->_props); +} + +QJsonObject QMLExecutor::getConfig() const +{ + return d_ptr->getSettings(); +} + +std::shared_ptr QMLExecutor::getQConfig() const +{ + const Q_D(QMLExecutor); + + return configHelpers::getQConfig(getSupportedProperties(), d->_props); +} + +void QMLExecutor::configChanged() +{ + d_ptr->configChanged(); +} + +bool QMLExecutor::mainWidgetDocked() const +{ + return d_ptr->_docked; +} + +ComponentInterface::ComponentProperties QMLExecutor::getSupportedProperties() const +{ + return d_ptr->getSupportedProperties(); +} + +void QMLExecutor::stopSimulation() +{ + Q_D(QMLExecutor); + + d->_simStarted = false; +} + +void QMLExecutor::startSimulation() +{ + Q_D(QMLExecutor); + + d->_simStarted = true; +} + +void QMLExecutor::simBcastRcv(const QJsonObject &msg, const QVariant ¶m) +{ + Q_UNUSED(msg); + Q_UNUSED(param); +} + +void QMLExecutor::setCANBusModel(CANBusModel* model) +{ + d_ptr->setCANBusModel(model); +} diff --git a/src/components/qmlexecutor/qmlexecutor.h b/src/components/qmlexecutor/qmlexecutor.h new file mode 100644 index 000000000..b74925c3c --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutor.h @@ -0,0 +1,127 @@ +#ifndef QMLEXECUTOR_H +#define QMLEXECUTOR_H + +#include +#include +#include +#include +#include + +class QMLExecutorPrivate; +class QWidget; +class QMLExecutorGuiInt; +class CANBusModel; + +/** + * @brief single instance ioc/iod container + */ +typedef Context QMLExecutorCtx; + +/** + * @see ComponentInterface + */ +class QMLExecutor : public QObject, public ComponentInterface { + Q_OBJECT + Q_DECLARE_PRIVATE(QMLExecutor) + +public: + /** + * @brief constructor + */ + QMLExecutor(); + + /** + * @brief explicit constructor with context + * @param ctx a context with something inside, most likely an ui + */ + explicit QMLExecutor(QMLExecutorCtx&& ctx); + + /** + * @brief virtual dtor + */ + ~QMLExecutor(); + + /** + * @see ComponentInterface::mainWidget + */ + QWidget* mainWidget() override; + + + /** + * @see ComponentInterface::setConfig + */ + void setConfig(const QJsonObject& json) override; + + /** + * @see ComponentInterface:: + */ + void setConfig(const QWidget& qobject) override; + + /** + * @see ComponentInterface:: + */ + QJsonObject getConfig() const override; + + /** + * @see ComponentInterface::getQConfig + */ + std::shared_ptr getQConfig() const override; + + /** + * @see ComponentInterface::configChanged + */ + void configChanged() override; + + /** + * @see ComponentInterface::mainWidgetDocked + */ + bool mainWidgetDocked() const override; + + /** + * @see ComponentInterface::getSupportedProperties + */ + ComponentInterface::ComponentProperties getSupportedProperties() const override; + +public: + + /** + * @brief sets can bus model used from QML + * @param model can bus model useb from qml + */ + void setCANBusModel(CANBusModel* model); + +signals: + /** + * @see ComponentInterface::mainWidgetDockToggled + */ + void mainWidgetDockToggled(QWidget* widget) override; + + /** + * @see ComponentInterface::simBcastSnd + */ + void simBcastSnd(const QJsonObject &msg, const QVariant ¶m = QVariant()) override; + +public slots: + /** + * @see ComponentInterface::stopSimulation + */ + void stopSimulation() override; + + /** + * @see ComponentInterface::startSimulation + */ + void startSimulation() override; + + /** + * @see ComponentInterface::simBcastRcv + */ + void simBcastRcv(const QJsonObject &msg, const QVariant ¶m) override; + +private: + /** + * @brief qt weird stuff + */ + QScopedPointer d_ptr; +}; + +#endif //QMLEXECUTOR_H diff --git a/src/components/qmlexecutor/qmlexecutor_p.cpp b/src/components/qmlexecutor/qmlexecutor_p.cpp new file mode 100644 index 000000000..2732902f5 --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutor_p.cpp @@ -0,0 +1,73 @@ +#include "qmlexecutor_p.h" +#include + + +QMLExecutorPrivate::QMLExecutorPrivate(QMLExecutor *q, QMLExecutorCtx&& ctx) + : _ctx(std::move(ctx)) + , _ui(_ctx.get()) + , q_ptr(q) +{ + initProps(); + + QObject::connect(&_ui, &QMLExecutorGuiInt::QMLLoaded, this, &QMLExecutorPrivate::QMLLoaded); +} + +void QMLExecutorPrivate::initProps() +{ + for (const auto& p: _supportedProps) + { + _props[ComponentInterface::propertyName(p)]; + } +} + +ComponentInterface::ComponentProperties QMLExecutorPrivate::getSupportedProperties() const +{ + return _supportedProps; +} + +QJsonObject QMLExecutorPrivate::getSettings() +{ + QJsonObject json; + + for (const auto& p : _props) { + json[p.first] = QJsonValue::fromVariant(p.second); + } + + return json; +} + +void QMLExecutorPrivate::setSettings(const QJsonObject& json) +{ + for (const auto& p : _supportedProps) { + const QString& propName = ComponentInterface::propertyName(p); + if (json.contains(propName)) + _props[propName] = json[propName].toVariant(); + } +} + +void QMLExecutorPrivate::configChanged() +{ + QString fileName = _props[_fileProperty].toString(); + + QUrl url = QUrl::fromLocalFile(fileName); + + _ui.loadQML(url); +} + +void QMLExecutorPrivate::startSimulation() +{ +} + +void QMLExecutorPrivate::stopSimulation() +{ +} + +void QMLExecutorPrivate::setCANBusModel(CANBusModel* model) +{ + _ui.setModel(model); +} + +void QMLExecutorPrivate::QMLLoaded(const QUrl& url) +{ + _props[_fileProperty] = url.toLocalFile(); +} diff --git a/src/components/qmlexecutor/qmlexecutor_p.h b/src/components/qmlexecutor/qmlexecutor_p.h new file mode 100644 index 000000000..26f45e324 --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutor_p.h @@ -0,0 +1,111 @@ +#ifndef QMLEXECUTOR_P_H +#define QMLEXECUTOR_P_H + +#include + +#include + +#include "gui/qmlexecutorguiimpl.h" +#include "qmlexecutor.h" +#include "propertyfields.h" + +class QMLExecutor; +class CANBusModel; + +/** + * @brief QMLExecutor plugin private implementation + */ +class QMLExecutorPrivate : public QObject { + Q_OBJECT + Q_DECLARE_PUBLIC(QMLExecutor) + +public: + /** + * @brief constructor carring interface class and ui + * @param q qt stuff, interface class + * @param ioc/iod container with gui, created when not supplied + */ + QMLExecutorPrivate(QMLExecutor* q, QMLExecutorCtx&& ctx = QMLExecutorCtx(new QMLExecutorGuiImpl(nullptr))); + +public: // ComponentInterface inheritance + /** + * @see ComponentInterface::getSupportedProperties + */ + ComponentInterface::ComponentProperties getSupportedProperties() const; + + /** + * @see ComponentInterface::getSettings + */ + QJsonObject getSettings(); + + /** + * @see ComponentInterface::setSettings + */ + void setSettings(const QJsonObject& json); + + /** + * @see ComponentInterface::configChanged + */ + void configChanged(); + + /** + * @see ComponentInterface::configChanged + */ + void startSimulation(); + + /** + * @see ComponentInterface::stopSimulation + */ + void stopSimulation(); + +public slots: + /** + * @brief Sets a can bus model used in QML + * @param model can bus interface model + */ + void setCANBusModel(CANBusModel* model); + + /** + * @brief QMLLoaded slot used for notification of QML load + * @param url url of a qml file + */ + void QMLLoaded(const QUrl& url); + +private: + /** + * @brief populate properties of this plugin + * @see ComponentInterface + */ + void initProps(); + +public: + //! Simulation started flag + bool _simStarted{ false }; + //! ioc/iod container for a gui + QMLExecutorCtx _ctx; + //! gui + QMLExecutorGuiInt& _ui; + + //! not used + bool _docked{ true }; + + //! plugin properties @see ComponentInterface + std::map _props; + +private: + QMLExecutor* q_ptr; + const QString _nameProperty = "name"; + const QString _fileProperty = "QML file"; + + // workaround for clang 3.5 + using cf = ComponentInterface::CustomEditFieldCbk; + + // clang-format off + ComponentInterface::ComponentProperties _supportedProps = { + std::make_tuple(_nameProperty, QVariant::String, true, cf(nullptr)), + std::make_tuple(_fileProperty, QVariant::String, true, cf([] { return new PropertyFieldPath; } )) + }; + // clang-format on +}; + +#endif // QMLEXECUTOR_P_H diff --git a/src/components/qmlexecutor/qmlexecutormodel.cpp b/src/components/qmlexecutor/qmlexecutormodel.cpp new file mode 100644 index 000000000..c2594268a --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutormodel.cpp @@ -0,0 +1,179 @@ +#include +#include + +#include "qmlexecutormodel.h" +#include "qmlexecutorplugin.h" + +#include +#include + + +namespace { + +// clang-format off +const std::map> portMappings = { + { PortType::In, + { + { CanRawData{}.type(), + CanSignalModel{}.type() + } + } + }, + { PortType::Out, + { + { CanRawData{}.type(), + CanSignalModel{}.type() + } + } + } +}; +// clang-format on + +} // namespace + + +QMLExecutorModel::QMLExecutorModel() + : ComponentModel("QMLExecutor") + , _painter(std::make_unique(QMLExecutorPlugin::PluginType::sectionColor())) +{ + _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + _label->setFixedSize(75, 25); + _label->setAttribute(Qt::WA_TranslucentBackground); + + _CANBusModel = std::make_unique(); + + _component.setCANBusModel(_CANBusModel.get()); + + connect(_CANBusModel.get(), &CANBusModel::sendFrame, this, &QMLExecutorModel::sendFrame); + connect(_CANBusModel.get(), &CANBusModel::sendSignal, this, &QMLExecutorModel::sendSignal); + connect(this, &QMLExecutorModel::frameReceived, _CANBusModel.get(), &CANBusModel::frameReceived); + connect(this, &QMLExecutorModel::signalReceived, _CANBusModel.get(), &CANBusModel::signalReceived); + +} + +QtNodes::NodePainterDelegate* QMLExecutorModel::painterDelegate() const +{ + return _painter.get(); +} + +unsigned int QMLExecutorModel::nPorts(PortType portType) const +{ + return (int)portMappings.at(portType).size(); +} + +NodeDataType QMLExecutorModel::dataType(PortType portType, PortIndex ndx) const +{ + if (portMappings.at(portType).size() > static_cast(ndx)) { + return portMappings.at(portType)[ndx]; + } + + cds_error("No port mapping for ndx: {}", ndx); + return { }; +} + +std::shared_ptr QMLExecutorModel::outData(PortIndex index) +{ + if(index == 0) + { + return getNextQueuedFrame(); + } + + if(index == 1) + { + return getNextQueuedSignal(); + } + + // this should not happen + assert(false); +} + +std::shared_ptr QMLExecutorModel::getNextQueuedFrame() +{ + if(_rawSendQueue.size() > 0) + { + auto frame = _rawSendQueue.front(); + _rawSendQueue.pop_front(); + + auto data = std::make_shared(frame); + + return data; + } + + return {}; +} + +std::shared_ptr QMLExecutorModel::getNextQueuedSignal() +{ + if(_signalSendQueue.size() > 0) + { + auto signal = _signalSendQueue.front(); + _signalSendQueue.pop_front(); + + auto data = std::make_shared(signal); + + return data; + } + + return {}; +} + + +void QMLExecutorModel::setInData(std::shared_ptr nodeData, PortIndex index) +{ + if(index == 0) + { + auto data = std::dynamic_pointer_cast(nodeData); + + if(data && data->frame().isValid()) + { + const auto& id = data->frame().frameId(); + const auto& payload = data->frame().payload(); + + emit frameReceived(id, payload); + } + else + cds_warn("Corrupt frame received!"); + + return; + } + + if(index == 1) + { + auto signal = std::dynamic_pointer_cast(nodeData); + + if(signal && signal->name().size() > 0) + { + emit signalReceived(signal->name(), signal->value()); + } + else + { + cds_warn("Corrupt signal received"); + } + + return; + } + + // this should not happen + assert(false); +} + +void QMLExecutorModel::sendFrame(const qint32& frameId, const QByteArray& frameData) +{ + QCanBusFrame frame; + + frame.setFrameId(frameId); + frame.setPayload(frameData); + + _rawSendQueue.push_back(frame); + + emit dataUpdated(0); +} + +void QMLExecutorModel::sendSignal(const QString& name, const QVariant& value ) +{ + auto data = CanSignalModel(name, value, Direction::TX); + + _signalSendQueue.push_back(data); + + emit dataUpdated(1); +} diff --git a/src/components/qmlexecutor/qmlexecutormodel.h b/src/components/qmlexecutor/qmlexecutormodel.h new file mode 100644 index 000000000..1b8ed4767 --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutormodel.h @@ -0,0 +1,122 @@ +#ifndef QMLEXECUTORMODEL_H +#define QMLEXECUTORMODEL_H + +#include + +#include +#include + + +#include +#include +#include +#include "componentmodel.h" +#include "nodepainter.h" +#include "canbusmodel.hpp" + + + +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::PortIndex; +using QtNodes::PortType; + +enum class Direction; + + +/** + * @brief The QMLExecutorModel A model for QML executor component + * @see ComponentModel + */ +class QMLExecutorModel : public ComponentModel { + Q_OBJECT + +public: + /** + * @brief only constructor to satisfy ql + */ + QMLExecutorModel(); + +private: + /// @see NodeDataModel::nPorts + unsigned int nPorts(PortType portType) const override; + + /// @see NodeDataModel::dataType + NodeDataType dataType(PortType portType, PortIndex portIndex) const override; + + /// @see NodeDataModel::outData + std::shared_ptr outData(PortIndex port) override; + + /// @see NodeDataModel::setInData + void setInData(std::shared_ptr nodeData, PortIndex port) override; + + /// @see NodeDataModel::painterDelegate + QtNodes::NodePainterDelegate* painterDelegate() const override; + +public slots: + /** + * @see CANBusModel + */ + void sendFrame(const qint32& frameId, const QByteArray& frameData); + + /** + * @see CANBusModel + */ + void sendSignal(const QString& name, const QVariant& value); + +signals: + /** + * @see CANBusModel + */ + void frameReceived(const qint32& frameId, const QByteArray& frameData); + + /** + * @brief A CAN signal was received + * @param signal name + * @param signal value + */ + void signalReceived(const QString& name, const QVariant& value); + + /** + * @brief not used currently + */ + void simulationStarted(); + + /** + * @brief not used currently + */ + void simulationStopped(); + + /** + * @brief not used currently + */ + void requestRedraw(); + +private: + /** + * @brief Gets next frame to send + * @return shared_ptr to frame or nullptr + */ + std::shared_ptr getNextQueuedFrame(); + + /** + * @brief Gets next signal to send + * @return shared_ptr to signal or nullptr + */ + std::shared_ptr getNextQueuedSignal(); + +private: + //! Componant stuff + std::unique_ptr _painter; + + //! ST queue to buffer frames to send + std::deque _rawSendQueue; + + //! ST queue to buffer signals to send + std::deque _signalSendQueue; + + //! can buf model to create interface with QML + std::unique_ptr _CANBusModel; +}; + +#endif // QMLEXECUTORMODEL_H diff --git a/src/components/qmlexecutor/qmlexecutorplugin.h b/src/components/qmlexecutor/qmlexecutorplugin.h new file mode 100644 index 000000000..d8c93bfbd --- /dev/null +++ b/src/components/qmlexecutor/qmlexecutorplugin.h @@ -0,0 +1,16 @@ +#ifndef QMLEXECUTORPLUGIN_H +#define QMLEXECUTORPLUGIN_H + +#include "plugin_type.h" +#include "qmlexecutormodel.h" + +// Note that max typestring length is limited to 128 chars. 64 causes VS2015 internal error. +using MiscPlugin = PluginBase; + +struct QMLExecutorPlugin { + using Model = QMLExecutorModel; + static constexpr const char* name = "QMLExecutor"; + using PluginType = MiscPlugin; +}; + +#endif // QMLEXECUTORPLUGIN_H diff --git a/src/components/qmlexecutor/tests/qmlexecutor_test.cpp b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp new file mode 100644 index 000000000..92d033863 --- /dev/null +++ b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp @@ -0,0 +1,89 @@ +#include +#include +#define CATCH_CONFIG_RUNNER +#include "log.h" +#include +#include +#include +#include + +std::shared_ptr kDefaultLogger; +// needed for QSignalSpy cause according to qtbug 49623 comments +// automatic detection of types is "flawed" in moc +Q_DECLARE_METATYPE(QCanBusFrame); + +TEST_CASE("Stubbed methods", "[qmlexecutor]") +{ + QMLExecutor c; + + REQUIRE(c.mainWidget() != nullptr); + REQUIRE(c.mainWidgetDocked() == true); +} + +TEST_CASE("setConfig - qobj", "[qmlexecutor]") +{ + QMLExecutor c; + QWidget obj; + + obj.setProperty("name", "Test Name"); + + c.setConfig(obj); +} + +TEST_CASE("setConfig - json", "[qmlexecutor]") +{ + QMLExecutor c; + QJsonObject obj; + + obj["name"] = "Test Name"; + + c.setConfig(obj); +} + +TEST_CASE("getConfig", "[qmlexecutor]") +{ + QMLExecutor c; + + auto abc = c.getConfig(); +} + +TEST_CASE("getQConfig", "[qmlexecutor]") +{ + QMLExecutor c; + + auto abc = c.getQConfig(); +} + +TEST_CASE("configChanged", "[qmlexecutor]") +{ + QMLExecutor c; + + c.configChanged(); +} + +TEST_CASE("getSupportedProperties", "[qmlexecutor]") +{ + QMLExecutor c; + + auto props = c.getSupportedProperties(); + + REQUIRE(props.size() == 1); + + REQUIRE(ComponentInterface::propertyName(props[0]) == "name"); + REQUIRE(ComponentInterface::propertyType(props[0]) == QVariant::String); + REQUIRE(ComponentInterface::propertyEditability(props[0]) == true); + REQUIRE(ComponentInterface::propertyField(props[0]) == nullptr); +} + +int main(int argc, char* argv[]) +{ + bool haveDebug = std::getenv("CDS_DEBUG") != nullptr; + kDefaultLogger = spdlog::stdout_color_mt("cds"); + if (haveDebug) { + kDefaultLogger->set_level(spdlog::level::debug); + } + cds_debug("Starting unit tests"); + qRegisterMetaType(); // required by QSignalSpy + QApplication a(argc, argv); // QApplication must exist when constructing QWidgets TODO check QTest + return Catch::Session().run(argc, argv); +} diff --git a/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp b/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp new file mode 100644 index 000000000..8b2283bab --- /dev/null +++ b/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp @@ -0,0 +1,86 @@ +#include +#include +#define CATCH_CONFIG_RUNNER +#include "log.h" +#include +#include +#include +#include + +std::shared_ptr kDefaultLogger; +// needed for QSignalSpy cause according to qtbug 49623 comments +// automatic detection of types is "flawed" in moc +Q_DECLARE_METATYPE(QCanBusFrame); + +TEST_CASE("Test basic functionality", "[qmlexecutorModel]") +{ + using namespace fakeit; + QMLExecutorModel cm; + REQUIRE(cm.caption() == "QMLExecutor"); + REQUIRE(cm.name() == "QMLExecutor"); + REQUIRE(cm.resizable() == false); + REQUIRE(cm.hasSeparateThread() == false); + REQUIRE(dynamic_cast(cm.clone().get()) != nullptr); + REQUIRE(dynamic_cast(cm.embeddedWidget()) != nullptr); +} + +TEST_CASE("painterDelegate", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + REQUIRE(cm.painterDelegate() != nullptr); +} + +TEST_CASE("nPorts", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + REQUIRE(cm.nPorts(QtNodes::PortType::Out) == 0); + REQUIRE(cm.nPorts(QtNodes::PortType::In) == 0); +} + +TEST_CASE("dataType", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + NodeDataType ndt; + + // ndt = cm.dataType(QtNodes::PortType::Out, 0); + // REQUIRE(ndt.id == "rawframe"); + // REQUIRE(ndt.name == "RAW"); + + // ndt = cm.dataType(QtNodes::PortType::Out, 1); + // REQUIRE(ndt.id == ""); + // REQUIRE(ndt.name == ""); + + // ndt = cm.dataType(QtNodes::PortType::In, 0); + // REQUIRE(ndt.id == ""); + // REQUIRE(ndt.name == ""); +} + +TEST_CASE("outData", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + auto nd = cm.outData(0); + REQUIRE(!nd); +} + +TEST_CASE("setInData", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + cm.setInData({}, 1); +} + +int main(int argc, char* argv[]) +{ + bool haveDebug = std::getenv("CDS_DEBUG") != nullptr; + kDefaultLogger = spdlog::stdout_color_mt("cds"); + if (haveDebug) { + kDefaultLogger->set_level(spdlog::level::debug); + } + cds_debug("Starting unit tests"); + qRegisterMetaType(); // required by QSignalSpy + QApplication a(argc, argv); // QApplication must exist when constructing QWidgets TODO check QTest + return Catch::Session().run(argc, argv); +} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6744060ce..fc84fdd0b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -9,7 +9,7 @@ set(srcs ) add_executable(CANdevStudio MACOSX_BUNDLE ${GUI_TYPE} ${srcs}) -target_link_libraries(CANdevStudio projectconfig) +target_link_libraries(CANdevStudio projectconfig Qt5::Quick Qt5::Widgets Qt5::Qml Qt5::QuickWidgets) target_compile_definitions(CANdevStudio PRIVATE $<$:CDS_DEBUG=true> $<$>:CDS_DEBUG=false>) target_include_directories(CANdevStudio PRIVATE From c5cb16674c2453a4f46dd88ae1bbc793625cd180 Mon Sep 17 00:00:00 2001 From: Sebastian Wieczorek Date: Mon, 21 Sep 2020 16:53:28 +0200 Subject: [PATCH 02/11] Sample qml for QML plugin --- src/components/qmlexecutor/sample.qml | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/components/qmlexecutor/sample.qml diff --git a/src/components/qmlexecutor/sample.qml b/src/components/qmlexecutor/sample.qml new file mode 100644 index 000000000..76cd93361 --- /dev/null +++ b/src/components/qmlexecutor/sample.qml @@ -0,0 +1,142 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.0 +import QtQml 2.0 + + +Item { + Rectangle { + anchors.top: parent.top + width: 500 + height:200 + color: "#040404FF" + + + ListView { + id: listview + anchors.fill: parent + + model: ListModel { + id: messagesModel + } + + header: RowLayout + { + spacing: 0 + Rectangle { + width: 200 + height: 15 + Text { text: "Timestamp" } + } + + Rectangle { + width: 75 + height: 15 + Text { + text: "Type" + anchors.fill: parent + } + } + + Rectangle { + width: 200 + height: 15 + Text { + text: "Identifier" + anchors.fill: parent + } + } + + Rectangle { + width: 100 + height: 15 + Text { + text: "Data" + anchors.fill: parent + } + } + } + + delegate: RowLayout + { + spacing: 2 + Rectangle { + width: 200 + height: 15 + color: "#100000FF" + Text { + text: new Date().toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss.zzz") + anchors.fill: parent + } + } + + Rectangle { + width: 75 + height: 15 + color: "#002000FF" + Text { + text: type + anchors.fill: parent + } + } + + Rectangle { + width: 200 + height: 15 + color: "#002000FF" + Text { + text: identifier + anchors.fill: parent + } + } + + Rectangle { + width: 100 + height: 15 + color: "#000010FF" + Text { + text: binarydata + anchors.fill: parent + } + } + } + + + onCountChanged: { + Qt.callLater( listview.positionViewAtEnd ) + } + } + } + + Item { + function frameReceived(frameId, framePayload) { + var array = new Uint8Array(framePayload) + + var hex = '' + for (var i = 0; i < array.length; i++) + { + hex = hex + array[i].toString(16) + } + + var identifier = "0x" + frameId.toString(16) + + messagesModel.append({ identifier: identifier, binarydata: hex, type: "FRAME" }) + + + CANBusModel.sendFrame(frameId, framePayload) + } + + function signalReceived(signalId, signalValue) { + messagesModel.append({ identifier: signalId, binarydata: signalValue, type: "SIGNAL"}) + + CANBusModel.sendSignal(signalId, signalValue) + } + + Component.onCompleted: { + CANBusModel.frameReceived.connect(frameReceived) + CANBusModel.signalReceived.connect(signalReceived) + } + } + +} From ad755e1aa65d39976f2212f182f8702dd01b5679 Mon Sep 17 00:00:00 2001 From: Sebastian Wieczorek Date: Wed, 30 Sep 2020 12:07:37 +0200 Subject: [PATCH 03/11] QMLExecutor unit tests and some changes --- src/components/qmlexecutor/CMakeLists.txt | 11 +- src/components/qmlexecutor/canbusmodel.hpp | 14 +- .../qmlexecutor/gui/qmlexecutorguiimpl.h | 7 +- .../qmlexecutor/gui/qmlexecutorguiint.h | 7 +- src/components/qmlexecutor/qmlexecutor.cpp | 4 +- src/components/qmlexecutor/qmlexecutor.h | 2 +- src/components/qmlexecutor/qmlexecutor_p.cpp | 35 ++- src/components/qmlexecutor/qmlexecutor_p.h | 7 +- .../qmlexecutor/qmlexecutormodel.cpp | 17 +- src/components/qmlexecutor/qmlexecutormodel.h | 13 +- src/components/qmlexecutor/tests/guimock.h | 41 +++ .../qmlexecutor/tests/qmlexecutor_test.cpp | 164 ++++++++-- .../tests/qmlexecutormodel_test.cpp | 287 ++++++++++++++++-- src/gui/CMakeLists.txt | 2 +- tools/templategen/CMakeLists.txt | 3 + 15 files changed, 535 insertions(+), 79 deletions(-) create mode 100644 src/components/qmlexecutor/tests/guimock.h diff --git a/src/components/qmlexecutor/CMakeLists.txt b/src/components/qmlexecutor/CMakeLists.txt index 19cd2d6cb..e0f31973b 100644 --- a/src/components/qmlexecutor/CMakeLists.txt +++ b/src/components/qmlexecutor/CMakeLists.txt @@ -10,17 +10,20 @@ set(SRC canbusmodel.hpp ) +set(QT_QML_LIBS Qt5::Quick Qt5::Widgets Qt5::Qml Qt5::QuickWidgets) + add_library(${COMPONENT_NAME} ${SRC}) -target_link_libraries(${COMPONENT_NAME} cds-common) +target_link_libraries(${COMPONENT_NAME} cds-common ${QT_QML_LIBS}) target_include_directories(${COMPONENT_NAME} INTERFACE ${Qt5Widgets_INCLUDE_DIRS}) target_include_directories(${COMPONENT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) if(WITH_TESTS) - add_executable(${COMPONENT_NAME}_test tests/${COMPONENT_NAME}_test.cpp) - target_link_libraries(${COMPONENT_NAME}_test ${COMPONENT_NAME} Qt5::Test fakeit) + + add_executable(${COMPONENT_NAME}_test tests/${COMPONENT_NAME}_test.cpp tests/guimock.h) + target_link_libraries(${COMPONENT_NAME}_test ${COMPONENT_NAME} Qt5::Test fakeit ${QT_QML_LIBS}) add_test(NAME ${COMPONENT_NAME}_test COMMAND ${COMPONENT_NAME}_test) add_executable(${COMPONENT_NAME}model_test tests/${COMPONENT_NAME}model_test.cpp) - target_link_libraries(${COMPONENT_NAME}model_test ${COMPONENT_NAME} Qt5::Test fakeit) + target_link_libraries(${COMPONENT_NAME}model_test ${COMPONENT_NAME} Qt5::Test fakeit ${QT_QML_LIBS}) add_test(NAME ${COMPONENT_NAME}model_test COMMAND ${COMPONENT_NAME}model_test) endif() diff --git a/src/components/qmlexecutor/canbusmodel.hpp b/src/components/qmlexecutor/canbusmodel.hpp index 8c723c2a1..c276368fa 100644 --- a/src/components/qmlexecutor/canbusmodel.hpp +++ b/src/components/qmlexecutor/canbusmodel.hpp @@ -20,7 +20,7 @@ class CANBusModel : public QObject * @param frameId frame identifier * @param frameData frame payload */ - void sendFrame(const qint32& frameId, const QByteArray& frameData); + void sendFrame(const quint32& frameId, const QByteArray& frameData); /** * @brief Send a CAN named signal @@ -34,7 +34,7 @@ class CANBusModel : public QObject * @param frameId frame identifier * @param frameData frame payload */ - void frameReceived(const qint32& frameId, const QByteArray& frameData); + void frameReceived(const quint32& frameId, const QByteArray& frameData); /** * @brief A CAN signal was received @@ -42,6 +42,16 @@ class CANBusModel : public QObject * @param signal value */ void signalReceived(const QString& name, const QVariant& value); + + /** + * @brief The simulation was started + */ + void simulationStarted(); + + /** + * @brief The simulation was stopped + */ + void simulationStopped(); }; #endif // CANBUSMODEL_HPP diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h index cb1ce1b4e..46f659794 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h @@ -1,6 +1,9 @@ #ifndef QMLEXECUTORGUIIMPL_H #define QMLEXECUTORGUIIMPL_H +#include +#include + #include #include #include @@ -36,6 +39,7 @@ class QMLExecutorGuiImpl : public QMLExecutorGuiInt , _ui(new Ui::QMLExecutorPrivate) , _widget(new QWidget) , _qmlUpdateCheckTimer(this) + , _CANBusModel(nullptr) { _ui->setupUi(_widget); @@ -87,6 +91,7 @@ public slots: _ui->quickWidget->engine()->clearComponentCache(); assert(_CANBusModel != nullptr); + _ui->quickWidget->rootContext()->setContextProperty("CANBusModel", _CANBusModel); _ui->quickWidget->setSource(url); @@ -116,7 +121,7 @@ public slots: void startQMLFileModificationChecks() { _qmlLoadTime = QDateTime::currentDateTime(); - _qmlUpdateCheckTimer.start(std::chrono::seconds(1)); + _qmlUpdateCheckTimer.start(std::chrono::milliseconds(1000).count()); } /** diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiint.h b/src/components/qmlexecutor/gui/qmlexecutorguiint.h index 1baf3904e..2fe54e5f1 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiint.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiint.h @@ -22,12 +22,15 @@ class QMLExecutorGuiInt : public QObject * @brief constructor * @param parent qt stuff */ - QMLExecutorGuiInt(QObject* parent) : QObject(nullptr) {}; + QMLExecutorGuiInt(QObject* parent) + : QObject(parent){}; /** * @brief virt dtor to get things working */ - virtual ~QMLExecutorGuiInt() {} + virtual ~QMLExecutorGuiInt() + { + } public: /** diff --git a/src/components/qmlexecutor/qmlexecutor.cpp b/src/components/qmlexecutor/qmlexecutor.cpp index 4209e3c1c..23ad21d05 100644 --- a/src/components/qmlexecutor/qmlexecutor.cpp +++ b/src/components/qmlexecutor/qmlexecutor.cpp @@ -73,14 +73,14 @@ void QMLExecutor::stopSimulation() { Q_D(QMLExecutor); - d->_simStarted = false; + d->stopSimulation(); } void QMLExecutor::startSimulation() { Q_D(QMLExecutor); - d->_simStarted = true; + d->startSimulation(); } void QMLExecutor::simBcastRcv(const QJsonObject &msg, const QVariant ¶m) diff --git a/src/components/qmlexecutor/qmlexecutor.h b/src/components/qmlexecutor/qmlexecutor.h index b74925c3c..df6285cf0 100644 --- a/src/components/qmlexecutor/qmlexecutor.h +++ b/src/components/qmlexecutor/qmlexecutor.h @@ -39,7 +39,7 @@ class QMLExecutor : public QObject, public ComponentInterface { /** * @brief virtual dtor */ - ~QMLExecutor(); + virtual ~QMLExecutor(); /** * @see ComponentInterface::mainWidget diff --git a/src/components/qmlexecutor/qmlexecutor_p.cpp b/src/components/qmlexecutor/qmlexecutor_p.cpp index 2732902f5..f42fa5c25 100644 --- a/src/components/qmlexecutor/qmlexecutor_p.cpp +++ b/src/components/qmlexecutor/qmlexecutor_p.cpp @@ -1,4 +1,6 @@ #include "qmlexecutor_p.h" +#include "canbusmodel.hpp" + #include @@ -9,9 +11,15 @@ QMLExecutorPrivate::QMLExecutorPrivate(QMLExecutor *q, QMLExecutorCtx&& ctx) { initProps(); + QObject::connect(&_ui, &QMLExecutorGuiInt::QMLLoaded, this, &QMLExecutorPrivate::QMLLoaded); } +QMLExecutorPrivate::~QMLExecutorPrivate() +{ +} + + void QMLExecutorPrivate::initProps() { for (const auto& p: _supportedProps) @@ -45,29 +53,38 @@ void QMLExecutorPrivate::setSettings(const QJsonObject& json) } } + void QMLExecutorPrivate::configChanged() { QString fileName = _props[_fileProperty].toString(); - QUrl url = QUrl::fromLocalFile(fileName); - - _ui.loadQML(url); + _ui.loadQML(QUrl::fromLocalFile(fileName)); } -void QMLExecutorPrivate::startSimulation() +void QMLExecutorPrivate::setCANBusModel(CANBusModel* model) { + _model = model; + _ui.setModel(model); } -void QMLExecutorPrivate::stopSimulation() +void QMLExecutorPrivate::QMLLoaded(const QUrl& url) { + _props[_fileProperty] = url.toLocalFile(); } -void QMLExecutorPrivate::setCANBusModel(CANBusModel* model) +void QMLExecutorPrivate::startSimulation() { - _ui.setModel(model); + if (_model) + { + emit _model->simulationStarted(); + } } -void QMLExecutorPrivate::QMLLoaded(const QUrl& url) +void QMLExecutorPrivate::stopSimulation() { - _props[_fileProperty] = url.toLocalFile(); + if (_model) + { + emit _model->simulationStopped(); + } } + diff --git a/src/components/qmlexecutor/qmlexecutor_p.h b/src/components/qmlexecutor/qmlexecutor_p.h index 26f45e324..cdd7463be 100644 --- a/src/components/qmlexecutor/qmlexecutor_p.h +++ b/src/components/qmlexecutor/qmlexecutor_p.h @@ -26,6 +26,7 @@ class QMLExecutorPrivate : public QObject { * @param ioc/iod container with gui, created when not supplied */ QMLExecutorPrivate(QMLExecutor* q, QMLExecutorCtx&& ctx = QMLExecutorCtx(new QMLExecutorGuiImpl(nullptr))); + ~QMLExecutorPrivate(); public: // ComponentInterface inheritance /** @@ -49,12 +50,12 @@ class QMLExecutorPrivate : public QObject { void configChanged(); /** - * @see ComponentInterface::configChanged + * @brief start simulation handler */ void startSimulation(); /** - * @see ComponentInterface::stopSimulation + * @brief stop simulation handler */ void stopSimulation(); @@ -96,6 +97,8 @@ public slots: QMLExecutor* q_ptr; const QString _nameProperty = "name"; const QString _fileProperty = "QML file"; + CANBusModel* _model; + // workaround for clang 3.5 using cf = ComponentInterface::CustomEditFieldCbk; diff --git a/src/components/qmlexecutor/qmlexecutormodel.cpp b/src/components/qmlexecutor/qmlexecutormodel.cpp index c2594268a..e717e100c 100644 --- a/src/components/qmlexecutor/qmlexecutormodel.cpp +++ b/src/components/qmlexecutor/qmlexecutormodel.cpp @@ -48,7 +48,8 @@ QMLExecutorModel::QMLExecutorModel() connect(_CANBusModel.get(), &CANBusModel::sendSignal, this, &QMLExecutorModel::sendSignal); connect(this, &QMLExecutorModel::frameReceived, _CANBusModel.get(), &CANBusModel::frameReceived); connect(this, &QMLExecutorModel::signalReceived, _CANBusModel.get(), &CANBusModel::signalReceived); - + connect(this, &QMLExecutorModel::simulationStarted, _CANBusModel.get(), &CANBusModel::simulationStarted); + connect(this, &QMLExecutorModel::simulationStopped, _CANBusModel.get(), &CANBusModel::simulationStopped); } QtNodes::NodePainterDelegate* QMLExecutorModel::painterDelegate() const @@ -63,11 +64,12 @@ unsigned int QMLExecutorModel::nPorts(PortType portType) const NodeDataType QMLExecutorModel::dataType(PortType portType, PortIndex ndx) const { - if (portMappings.at(portType).size() > static_cast(ndx)) { + if (portMappings.find(portType) != std::end(portMappings) && portMappings.at(portType).size() > static_cast(ndx)) { return portMappings.at(portType)[ndx]; } cds_error("No port mapping for ndx: {}", ndx); + return { }; } @@ -84,7 +86,7 @@ std::shared_ptr QMLExecutorModel::outData(PortIndex index) } // this should not happen - assert(false); + return {}; } std::shared_ptr QMLExecutorModel::getNextQueuedFrame() @@ -154,10 +156,10 @@ void QMLExecutorModel::setInData(std::shared_ptr nodeData, PortIndex i } // this should not happen - assert(false); + cds_error("Corrupt in data!"); } -void QMLExecutorModel::sendFrame(const qint32& frameId, const QByteArray& frameData) +void QMLExecutorModel::sendFrame(const quint32& frameId, const QByteArray& frameData) { QCanBusFrame frame; @@ -177,3 +179,8 @@ void QMLExecutorModel::sendSignal(const QString& name, const QVariant& value ) emit dataUpdated(1); } + +CANBusModel* QMLExecutorModel::getCANBusModel() +{ + return _CANBusModel.get(); +} diff --git a/src/components/qmlexecutor/qmlexecutormodel.h b/src/components/qmlexecutor/qmlexecutormodel.h index 1b8ed4767..89137f9c2 100644 --- a/src/components/qmlexecutor/qmlexecutormodel.h +++ b/src/components/qmlexecutor/qmlexecutormodel.h @@ -37,7 +37,7 @@ class QMLExecutorModel : public ComponentModel { */ QMLExecutorModel(); -private: +public: // ComponentModelInterface /// @see NodeDataModel::nPorts unsigned int nPorts(PortType portType) const override; @@ -53,11 +53,18 @@ class QMLExecutorModel : public ComponentModel { /// @see NodeDataModel::painterDelegate QtNodes::NodePainterDelegate* painterDelegate() const override; +public: + /** + * @brief testing facility + * @return can but model used to interface with QML + */ + CANBusModel* getCANBusModel(); + public slots: /** * @see CANBusModel */ - void sendFrame(const qint32& frameId, const QByteArray& frameData); + void sendFrame(const quint32& frameId, const QByteArray& frameData); /** * @see CANBusModel @@ -68,7 +75,7 @@ public slots: /** * @see CANBusModel */ - void frameReceived(const qint32& frameId, const QByteArray& frameData); + void frameReceived(const quint32& frameId, const QByteArray& frameData); /** * @brief A CAN signal was received diff --git a/src/components/qmlexecutor/tests/guimock.h b/src/components/qmlexecutor/tests/guimock.h new file mode 100644 index 000000000..d3cae15ac --- /dev/null +++ b/src/components/qmlexecutor/tests/guimock.h @@ -0,0 +1,41 @@ +#ifndef GUIMOC_H +#define GUIMOC_H + +#include +#include +#include + +#include "gui/qmlexecutorguiint.h" + +/** +* @brief fakeit is broken so wrap broken solution to get it working with qt signals +*/ +class GuiMock : public QMLExecutorGuiInt +{ + Q_OBJECT +public: + GuiMock(QObject* parent = nullptr) + : QMLExecutorGuiInt(parent) + { } + +public: + void loadQML(const QUrl& url) override + { + mock.get().loadQML(url); + } + + void setModel(CANBusModel* model) override + { + mock.get().setModel(model); + } + + QWidget* mainWidget() override + { + return mock.get().mainWidget(); + } + +public: + fakeit::Mock mock; +}; + +#endif diff --git a/src/components/qmlexecutor/tests/qmlexecutor_test.cpp b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp index 92d033863..2e95550b1 100644 --- a/src/components/qmlexecutor/tests/qmlexecutor_test.cpp +++ b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp @@ -1,16 +1,26 @@ -#include -#include #define CATCH_CONFIG_RUNNER -#include "log.h" #include -#include +#include +#include +#include +#include +#include #include +#include #include +#include "gui/qmlexecutorguiint.h" +#include "qmlexecutor.h" +#include "qmlexecutormodel.h" +#include "guimock.h" + +using namespace fakeit; + std::shared_ptr kDefaultLogger; -// needed for QSignalSpy cause according to qtbug 49623 comments -// automatic detection of types is "flawed" in moc -Q_DECLARE_METATYPE(QCanBusFrame); + +static const char* const propertyNameName = "name"; +static const char* const propertyQMLFileName = "QML file"; + TEST_CASE("Stubbed methods", "[qmlexecutor]") { @@ -20,45 +30,100 @@ TEST_CASE("Stubbed methods", "[qmlexecutor]") REQUIRE(c.mainWidgetDocked() == true); } -TEST_CASE("setConfig - qobj", "[qmlexecutor]") +TEST_CASE("setConfig - qobj/getconfig", "[qmlexecutor]") { QMLExecutor c; QWidget obj; + QString fileName{ "sample file" }; + QString objectName{ "sample file" }; + QString sampleName{ "bad file" }; - obj.setProperty("name", "Test Name"); + obj.setProperty(propertyNameName, objectName); + obj.setProperty(propertyQMLFileName, fileName); + obj.setProperty("bad property name", sampleName); c.setConfig(obj); + + auto config = c.getConfig(); + + REQUIRE(config.keys().size() == 2); + REQUIRE(config[propertyNameName].toString() == objectName); + REQUIRE(config[propertyQMLFileName].toString() == fileName); } -TEST_CASE("setConfig - json", "[qmlexecutor]") +TEST_CASE("setConfig - json/getconfig", "[qmlexecutor]") { QMLExecutor c; - QJsonObject obj; + QJsonObject json; + QString fileName{ "sample file" }; + QString objectName{ "sample file" }; + QString sampleName{ "bad file" }; - obj["name"] = "Test Name"; + json[propertyNameName] = objectName; + json[propertyQMLFileName] = fileName; + json["bad property name"] = "sampleName"; - c.setConfig(obj); -} + c.setConfig(json); -TEST_CASE("getConfig", "[qmlexecutor]") -{ - QMLExecutor c; + auto config = c.getConfig(); - auto abc = c.getConfig(); + REQUIRE(config.keys().size() == 2); + REQUIRE(config[propertyNameName].toString() == objectName); + REQUIRE(config[propertyQMLFileName].toString() == fileName); } -TEST_CASE("getQConfig", "[qmlexecutor]") +TEST_CASE("configChanged", "[qmlexecutor]") + { + auto goodMock = new GuiMock(); + + QUrl check; + + Fake(Method(goodMock->mock, setModel)); + When(Method(goodMock->mock, loadQML)).AlwaysDo([&check](const QUrl& url) { check = url; }); + Fake(Method(goodMock->mock, mainWidget)); + + QMLExecutorCtx context(goodMock); + QMLExecutor c(std::move(context)); + + QString fileName{ "c:/sample_file.txt" }; + QJsonObject json{ { propertyQMLFileName, fileName } }; + + json[propertyQMLFileName] = fileName; + + c.setConfig(json); + c.configChanged(); + + // Using( QUrl::fromLocalFile(fileName)) faile here + Verify(Method(goodMock->mock, loadQML)).Once(); + REQUIRE(check == QUrl::fromLocalFile(fileName)); + } + +TEST_CASE("qml changed from ui", "[qmlexecutor]") { - QMLExecutor c; + auto goodMock = new GuiMock(); + + Fake(Method(goodMock->mock, setModel)); + Fake(Method(goodMock->mock, loadQML)); + Fake(Method(goodMock->mock, mainWidget)); + - auto abc = c.getQConfig(); + QMLExecutorCtx context(goodMock); + QMLExecutor c(std::move(context)); + + QString fileName{ "c:/sample_file.txt" }; + + emit goodMock->QMLLoaded(QUrl::fromLocalFile(fileName)); + + auto config = c.getConfig(); + + REQUIRE(config[propertyQMLFileName].toString() == QUrl::fromLocalFile(fileName).toLocalFile()); } -TEST_CASE("configChanged", "[qmlexecutor]") +TEST_CASE("simBcastRcv", "[qmlexecutor]") { - QMLExecutor c; + QMLExecutor c; - c.configChanged(); + REQUIRE_NOTHROW(c.simBcastRcv(QJsonObject(), QVariant())); } TEST_CASE("getSupportedProperties", "[qmlexecutor]") @@ -67,12 +132,57 @@ TEST_CASE("getSupportedProperties", "[qmlexecutor]") auto props = c.getSupportedProperties(); - REQUIRE(props.size() == 1); + REQUIRE(props.size() == 2); - REQUIRE(ComponentInterface::propertyName(props[0]) == "name"); + REQUIRE(ComponentInterface::propertyName(props[0]) == propertyNameName); REQUIRE(ComponentInterface::propertyType(props[0]) == QVariant::String); REQUIRE(ComponentInterface::propertyEditability(props[0]) == true); REQUIRE(ComponentInterface::propertyField(props[0]) == nullptr); + + REQUIRE(ComponentInterface::propertyName(props[1]) == propertyQMLFileName); + REQUIRE(ComponentInterface::propertyType(props[1]) == QVariant::String); + REQUIRE(ComponentInterface::propertyEditability(props[1]) == true); + REQUIRE(ComponentInterface::propertyField(props[1]) != nullptr); + REQUIRE_NOTHROW(ComponentInterface::propertyField(props[1])()); +} + +TEST_CASE("start/stop", "[qmlexecutor]") +{ + QMLExecutor c; + + CANBusModel model; + + c.setCANBusModel(&model); + + int startCount{ 0 }; + int stopCount{ 0 }; + + QObject::connect(&model, &CANBusModel::simulationStarted, [&]() { startCount++; }); + QObject::connect(&model, &CANBusModel::simulationStopped, [&]() { stopCount++; }); + + c.startSimulation(); + c.stopSimulation(); + + REQUIRE(startCount == 1); + REQUIRE(stopCount == 1); +} + +TEST_CASE("getQConfig", "[qmlexecutor]") +{ + QMLExecutor c; + QJsonObject json; + QString fileName{ "sample file" }; + QString objectName{ "sample file" }; + + json[propertyNameName] = objectName; + json[propertyQMLFileName] = fileName; + + c.setConfig(json); + + auto config = c.getQConfig(); + + REQUIRE(config->property(propertyNameName).toString() == objectName); + REQUIRE(config->property(propertyQMLFileName).toString() == fileName); } int main(int argc, char* argv[]) @@ -83,7 +193,7 @@ int main(int argc, char* argv[]) kDefaultLogger->set_level(spdlog::level::debug); } cds_debug("Starting unit tests"); - qRegisterMetaType(); // required by QSignalSpy + QApplication a(argc, argv); // QApplication must exist when constructing QWidgets TODO check QTest return Catch::Session().run(argc, argv); } diff --git a/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp b/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp index 8b2283bab..5173e72a9 100644 --- a/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp +++ b/src/components/qmlexecutor/tests/qmlexecutormodel_test.cpp @@ -1,15 +1,24 @@ -#include -#include #define CATCH_CONFIG_RUNNER -#include "log.h" #include -#include +#include +#include +#include +#include +#include #include +#include #include + +#include "qmlexecutormodel.h" +#include "qmlexecutor.h" +#include "gui/qmlexecutorguiint.h" + + +using namespace fakeit; + std::shared_ptr kDefaultLogger; -// needed for QSignalSpy cause according to qtbug 49623 comments -// automatic detection of types is "flawed" in moc + Q_DECLARE_METATYPE(QCanBusFrame); TEST_CASE("Test basic functionality", "[qmlexecutorModel]") @@ -34,8 +43,8 @@ TEST_CASE("nPorts", "[qmlexecutorModel]") { QMLExecutorModel cm; - REQUIRE(cm.nPorts(QtNodes::PortType::Out) == 0); - REQUIRE(cm.nPorts(QtNodes::PortType::In) == 0); + REQUIRE(cm.nPorts(QtNodes::PortType::Out) == 2); + REQUIRE(cm.nPorts(QtNodes::PortType::In) == 2); } TEST_CASE("dataType", "[qmlexecutorModel]") @@ -44,20 +53,38 @@ TEST_CASE("dataType", "[qmlexecutorModel]") NodeDataType ndt; - // ndt = cm.dataType(QtNodes::PortType::Out, 0); - // REQUIRE(ndt.id == "rawframe"); - // REQUIRE(ndt.name == "RAW"); + ndt = cm.dataType(QtNodes::PortType::In, 0); + REQUIRE(ndt.id == "rawframe"); + REQUIRE(ndt.name == "RAW"); - // ndt = cm.dataType(QtNodes::PortType::Out, 1); - // REQUIRE(ndt.id == ""); - // REQUIRE(ndt.name == ""); + ndt = cm.dataType(QtNodes::PortType::Out, 1); + REQUIRE(ndt.id == "signal"); + REQUIRE(ndt.name == "SIG"); - // ndt = cm.dataType(QtNodes::PortType::In, 0); - // REQUIRE(ndt.id == ""); - // REQUIRE(ndt.name == ""); + ndt = cm.dataType(QtNodes::PortType::Out, 0); + REQUIRE(ndt.id == "rawframe"); + REQUIRE(ndt.name == "RAW"); + + ndt = cm.dataType(QtNodes::PortType::Out, 1); + REQUIRE(ndt.id == "signal"); + REQUIRE(ndt.name == "SIG"); + + ndt = cm.dataType(QtNodes::PortType::None, 0); + REQUIRE(ndt.id == ""); + REQUIRE(ndt.name == ""); + + ndt = cm.dataType(QtNodes::PortType::In, 3); + REQUIRE(ndt.id == ""); + REQUIRE(ndt.name == ""); + REQUIRE(ndt.name == ""); + + ndt = cm.dataType(QtNodes::PortType::Out, 3); + REQUIRE(ndt.id == ""); + REQUIRE(ndt.name == ""); + REQUIRE(ndt.name == ""); } -TEST_CASE("outData", "[qmlexecutorModel]") +TEST_CASE("outData_nodata_raw", "[qmlexecutorModel]") { QMLExecutorModel cm; @@ -65,11 +92,231 @@ TEST_CASE("outData", "[qmlexecutorModel]") REQUIRE(!nd); } -TEST_CASE("setInData", "[qmlexecutorModel]") +TEST_CASE("outData_nodata_signal", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + auto nd = cm.outData(1); + + REQUIRE(!nd); +} + +TEST_CASE("outData_frame_simple", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + quint32 frameId = 123; + QByteArray frameData("ab\0cd", 5); + + cm.sendFrame(frameId, frameData); + auto nd = cm.outData(0); + + REQUIRE(nd); + + auto data = std::dynamic_pointer_cast(nd); + + REQUIRE(frameId == data->frame().frameId()); + REQUIRE(frameData == data->frame().payload()); + + nd = cm.outData(0); + + REQUIRE(!nd); +} + +TEST_CASE("outData_signal_simple", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + QString name{ "name" }; + QString value{ "abcdef" }; + + cm.sendSignal(name, value); + + auto nd = cm.outData(1); + + REQUIRE(nd); + + auto data = std::dynamic_pointer_cast(nd); + + REQUIRE(name == data->name()); + REQUIRE(value == data->value()); +} + +TEST_CASE("outData_nodata_corrupt", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + auto nd = cm.outData(12); + + REQUIRE(!nd); +} + +TEST_CASE("setInData_corrupt_frame", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + auto bad = std::make_shared(QCanBusFrame(QCanBusFrame::InvalidFrame)); + + int count{0}; + QObject::connect(cm.getCANBusModel(), &CANBusModel::frameReceived, + [&count](const quint32&, const QByteArray&) + { + count++; + }); + + cm.setInData(bad, 0); + + REQUIRE(count == 0); +} + +TEST_CASE("setInData_corrupt_signal", "[qmlexecutorModel]") { QMLExecutorModel cm; - cm.setInData({}, 1); + + int count{ 0 }; + + QObject::connect(cm.getCANBusModel(), &CANBusModel::signalReceived, + [&count](const QString&, const QVariant&) + { + count++; + }); + + + cm.setInData(std::make_shared("", ""), 1); + + REQUIRE(count == 0); +} + +TEST_CASE("setInData_corrupt_port", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + + int count{ 0 }; + + QObject::connect(cm.getCANBusModel(), &CANBusModel::signalReceived, + [&count](const QString&, const QVariant&) + { + count++; + }); + + QObject::connect(cm.getCANBusModel(), &CANBusModel::frameReceived, + [&count](const quint32&, const QByteArray&) + { + count++; + }); + + + cm.setInData(std::make_shared("", ""), 123); + + REQUIRE(count == 0); +} + +TEST_CASE("setInData_frame", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + int frameId = 21; + QByteArray data = QByteArray("ab\0cd", 5); + auto frameData = std::make_shared(QCanBusFrame(frameId, data)); + + int count{ 0 }; + int checkFrameId; + QByteArray checkData; + + QObject::connect(cm.getCANBusModel(), &CANBusModel::frameReceived, + [&count, &checkFrameId, &checkData](const quint32& frameId, const QByteArray& frameData) + { + count++; + checkFrameId = frameId; + checkData = frameData; + }); + + cm.setInData(frameData, 0); + + REQUIRE(count == 1); + REQUIRE(frameId == checkFrameId); + REQUIRE(data == checkData); +} + +TEST_CASE("setInData_signal", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + QString name{ "name" }; + QString value{ "abcdef" }; + + int count{ 0 }; + QString checkName; + QVariant checkValue; + + QObject::connect(cm.getCANBusModel(), &CANBusModel::signalReceived, + [&count, &checkName, &checkValue](const QString& name, const QVariant& value) + { + count++; + checkName = name; + checkValue = value; + }); + + + cm.setInData(std::make_shared(name, value), 1); + + REQUIRE(count == 1); + REQUIRE(checkName == checkName); + REQUIRE(value == checkValue.toString()); +} + +TEST_CASE("outData_frame", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + int frameId = 21; + QByteArray frameData = QByteArray("ab\0cd", 5); + + int count{ 0 }; + int checkPort{ -9 }; + QByteArray checkData; + + QObject::connect(&cm, &QMLExecutorModel::dataUpdated, [&count, &checkPort](int portIndex) { + count++; + checkPort = portIndex; + }); + + cm.getCANBusModel()->sendFrame(frameId, frameData); + + auto data = std::dynamic_pointer_cast(cm.outData(0)); + + REQUIRE(count == 1); + REQUIRE(checkPort == 0); + REQUIRE(data); + REQUIRE(frameId == data->frame().frameId()); + REQUIRE(frameData == data->frame().payload()); +} + +TEST_CASE("outData_signal", "[qmlexecutorModel]") +{ + QMLExecutorModel cm; + + QString name{ "name" }; + QString value{ "abcdef" }; + + int count{ 0 }; + QString checkName; + QVariant checkValue; + + QObject::connect(cm.getCANBusModel(), &CANBusModel::signalReceived, + [&count, &checkName, &checkValue](const QString& name, const QVariant& value) { + count++; + checkName = name; + checkValue = value; + }); + + cm.setInData(std::make_shared(name, value), 1); + + REQUIRE(count == 1); + REQUIRE(checkName == checkName); + REQUIRE(value == checkValue.toString()); } int main(int argc, char* argv[]) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index fc84fdd0b..6744060ce 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -9,7 +9,7 @@ set(srcs ) add_executable(CANdevStudio MACOSX_BUNDLE ${GUI_TYPE} ${srcs}) -target_link_libraries(CANdevStudio projectconfig Qt5::Quick Qt5::Widgets Qt5::Qml Qt5::QuickWidgets) +target_link_libraries(CANdevStudio projectconfig) target_compile_definitions(CANdevStudio PRIVATE $<$:CDS_DEBUG=true> $<$>:CDS_DEBUG=false>) target_include_directories(CANdevStudio PRIVATE diff --git a/tools/templategen/CMakeLists.txt b/tools/templategen/CMakeLists.txt index d23d08c8a..0785616c8 100644 --- a/tools/templategen/CMakeLists.txt +++ b/tools/templategen/CMakeLists.txt @@ -9,6 +9,9 @@ set(CMAKE_AUTOMOC OFF) set(CMAKE_AUTOUIC OFF) add_executable(templategen ${SRC}) +target_include_directories(templategen PUBLIC ${Boost_INCLUDE_DIRS}) +#this is needed on windows +#target_link_directories(templategen PUBLIC ${Boost_LIBRARY_DIRS}) target_link_libraries(templategen ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}) set(CMAKE_AUTOMOC ON) From 5ccc093f51b8460bfd077abb6f2dceae3453903f Mon Sep 17 00:00:00 2001 From: Sebastian Wieczorek Date: Tue, 6 Oct 2020 10:18:27 +0200 Subject: [PATCH 04/11] Switching off coverage analysis for gui part --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 4693a9b1f..83e23fde4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,6 +7,7 @@ coverage: - "src/components/cansignalsender/gui" - "src/components/cansignalviewer/gui" - "src/components/canload/canloadpainter.h" + - "src/components/qmlexecutor/gui" - "src/common/guiinterface" - "src/common/candbpainter.cpp" - "src/common/candbpainter.h" From 26ba7bb611821ef7af11c710bd0aa2fa60a2a8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Thu, 15 Oct 2020 15:16:59 +0200 Subject: [PATCH 05/11] - Fix colors for dark scheme - Add dock/undock - Add show/hide log window - Change buttons layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- files/css/darkStyle.css | 4 +- files/css/lightStyle.css | 2 +- src/common/bcastmsgs.h | 1 + src/components/qmlexecutor/gui/qmlexecutor.ui | 252 ++++++++++-------- .../qmlexecutor/gui/qmlexecutorguiimpl.h | 15 ++ .../qmlexecutor/gui/qmlexecutorguiint.h | 7 + src/components/qmlexecutor/qmlexecutor.cpp | 8 +- src/components/qmlexecutor/qmlexecutor_p.cpp | 10 +- src/components/qmlexecutor/qmlexecutor_p.h | 5 + src/components/qmlexecutor/tests/guimock.h | 5 + src/gui/projectconfig/projectconfig_p.h | 6 + 11 files changed, 199 insertions(+), 116 deletions(-) diff --git a/files/css/darkStyle.css b/files/css/darkStyle.css index 762f7789e..1d262dfbd 100644 --- a/files/css/darkStyle.css +++ b/files/css/darkStyle.css @@ -131,7 +131,7 @@ QPushButton#pbStartNew, #pbStartOpen, #close { border: 0px; } -QWidget#CanRawSenderPrivate, #CanRawViewPrivate, #CanRawFilterPrivate, #CanSignalDataPrivate, #CanSignalSenderPrivate { +QWidget#CanRawSenderPrivate, #CanRawViewPrivate, #CanRawFilterPrivate, #CanSignalDataPrivate, #CanSignalSenderPrivate, #QMLExecutorPrivate { background-color: #616161; } @@ -139,7 +139,7 @@ QToolButton#toolSwitch { image: url(:/images/files/images/light/CANbus_icon_Switch_dark.svg); } -QLineEdit#searchLine { +QLineEdit#searchLine, #fileName { background-color: #3a3a3a; border : 1px solid #adadad; padding: 2px; diff --git a/files/css/lightStyle.css b/files/css/lightStyle.css index 133aff911..0e38ba13f 100644 --- a/files/css/lightStyle.css +++ b/files/css/lightStyle.css @@ -147,7 +147,7 @@ QToolButton#toolSwitch { image: url(:/images/files/images/light/CANbus_icon_Switch.svg); } -QLineEdit#searchLine { +QLineEdit#searchLine, #fileName { border : 1px solid #adadad; padding: 2px; } diff --git a/src/common/bcastmsgs.h b/src/common/bcastmsgs.h index 6e3e44180..27db4ba1f 100644 --- a/src/common/bcastmsgs.h +++ b/src/common/bcastmsgs.h @@ -11,6 +11,7 @@ static const QString ConfigChanged = "config_changed"; static const QString NodeDeleted = "node_deleted"; static const QString InitDone = "init_done"; static const QString CanDbUpdated = "can_db_updated"; +static const QString GuiStyleSwitched = "gui_style_switched"; } diff --git a/src/components/qmlexecutor/gui/qmlexecutor.ui b/src/components/qmlexecutor/gui/qmlexecutor.ui index 6e4e74808..f56cae3d2 100644 --- a/src/components/qmlexecutor/gui/qmlexecutor.ui +++ b/src/components/qmlexecutor/gui/qmlexecutor.ui @@ -13,124 +13,152 @@ QMLExecutor - + + + + - + + + + + + 32 + 32 + + + + + 32 + 32 + + + + L + + + + 32 + 32 + + + + true + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + E + + + + 32 + 32 + + + + true + + + - - - - - Load QML - - - - - - - true - - - - - - - - MS Shell Dlg 2 - - - - Edit QML - - - - + - - - - 0 - 0 - - - - Qt::Vertical - - - false - - - false - - - - - 0 - 0 - - - - QFrame::Box - - - QFrame::Plain - - - 1 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QQuickWidget::SizeRootObjectToView - - - - - - - - - 16777215 - 16777215 - - - - QFrame::Box - - - QFrame::Plain - - - true - - + + + + 32 + 32 + + + + + 32 + 32 + + + + LW + + + + 32 + 32 + + + + true + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/images/files/images/light/CANbus_icon_Undock.svg:/images/files/images/light/CANbus_icon_Undock.svg + + + + 32 + 32 + + + + true + + + true + + + + + Qt::Vertical + + + + + + + QQuickWidget::SizeRootObjectToView + + + + + @@ -140,6 +168,8 @@
QtQuickWidgets/QQuickWidget
- + + + diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h index 46f659794..0686f6a9d 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h @@ -42,6 +42,7 @@ class QMLExecutorGuiImpl : public QMLExecutorGuiInt , _CANBusModel(nullptr) { _ui->setupUi(_widget); + _ui->logWindow->setVisible(false); _ui->splitter->setCollapsible(1, true); @@ -52,13 +53,27 @@ class QMLExecutorGuiImpl : public QMLExecutorGuiInt QObject::connect(_ui->editQMLButton, SIGNAL(clicked()), this, SLOT(editQML())); QObject::connect(_ui->loadQMLButton, SIGNAL(clicked()), this, SLOT(browseAndOpenQML())); QObject::connect(&_qmlUpdateCheckTimer, SIGNAL(timeout()), this, SLOT(checkQMLModification())); + QObject::connect(_ui->pbDockUndock, &QPushButton::clicked, this, &QMLExecutorGuiImpl::dockUndock); + QObject::connect(_ui->showLog, &QPushButton::clicked, [this] { + _ui->logWindow->setVisible(!_ui->logWindow->isVisible()); + }); } virtual QWidget* mainWidget() override { + // Using this in constructor is too early (no CSS colors set yet) + // Background color of QQuickWidget cannot be set via CSS + updateUIColor(); + return _widget; } + void updateUIColor() override + { + auto color = _widget->palette().color(_widget->backgroundRole()); + _ui->quickWidget->setClearColor(color); + } + public slots: /** * @brief Slot to do browsing for QML file, calls qml load diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiint.h b/src/components/qmlexecutor/gui/qmlexecutorguiint.h index 2fe54e5f1..8caa23d19 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiint.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiint.h @@ -52,12 +52,19 @@ public slots: */ virtual void setModel(CANBusModel* model) = 0; + /** + * @brief used to align empty QQuickWidget color with current color scheme + */ + virtual void updateUIColor() = 0; + signals: /** * @brief this signal is emited when qml was loaded * @param url url of qml file */ void QMLLoaded(const QUrl& url); + + void dockUndock(); }; diff --git a/src/components/qmlexecutor/qmlexecutor.cpp b/src/components/qmlexecutor/qmlexecutor.cpp index 23ad21d05..7584508bc 100644 --- a/src/components/qmlexecutor/qmlexecutor.cpp +++ b/src/components/qmlexecutor/qmlexecutor.cpp @@ -1,6 +1,7 @@ #include "qmlexecutor.h" #include "qmlexecutor_p.h" #include +#include #include #include @@ -85,8 +86,13 @@ void QMLExecutor::startSimulation() void QMLExecutor::simBcastRcv(const QJsonObject &msg, const QVariant ¶m) { - Q_UNUSED(msg); Q_UNUSED(param); + + if(msg["msg"] == BcastMsg::GuiStyleSwitched) { + Q_D(QMLExecutor); + + d->updateUIColor(); + } } void QMLExecutor::setCANBusModel(CANBusModel* model) diff --git a/src/components/qmlexecutor/qmlexecutor_p.cpp b/src/components/qmlexecutor/qmlexecutor_p.cpp index f42fa5c25..36cd1b869 100644 --- a/src/components/qmlexecutor/qmlexecutor_p.cpp +++ b/src/components/qmlexecutor/qmlexecutor_p.cpp @@ -11,8 +11,12 @@ QMLExecutorPrivate::QMLExecutorPrivate(QMLExecutor *q, QMLExecutorCtx&& ctx) { initProps(); - QObject::connect(&_ui, &QMLExecutorGuiInt::QMLLoaded, this, &QMLExecutorPrivate::QMLLoaded); + + QObject::connect(&_ui, &QMLExecutorGuiInt::dockUndock, [this] { + _docked = !_docked; + emit q_ptr->mainWidgetDockToggled(_ui.mainWidget()); + }); } QMLExecutorPrivate::~QMLExecutorPrivate() @@ -88,3 +92,7 @@ void QMLExecutorPrivate::stopSimulation() } } +void QMLExecutorPrivate::updateUIColor() +{ + _ui.updateUIColor(); +} diff --git a/src/components/qmlexecutor/qmlexecutor_p.h b/src/components/qmlexecutor/qmlexecutor_p.h index cdd7463be..61bd2582a 100644 --- a/src/components/qmlexecutor/qmlexecutor_p.h +++ b/src/components/qmlexecutor/qmlexecutor_p.h @@ -59,6 +59,11 @@ class QMLExecutorPrivate : public QObject { */ void stopSimulation(); + /** + * @brief used to align empty QQuickWidget color with current color scheme + */ + void updateUIColor(); + public slots: /** * @brief Sets a can bus model used in QML diff --git a/src/components/qmlexecutor/tests/guimock.h b/src/components/qmlexecutor/tests/guimock.h index d3cae15ac..3d4463743 100644 --- a/src/components/qmlexecutor/tests/guimock.h +++ b/src/components/qmlexecutor/tests/guimock.h @@ -34,6 +34,11 @@ class GuiMock : public QMLExecutorGuiInt return mock.get().mainWidget(); } + void updateUIColor() override + { + return mock.get().updateUIColor(); + } + public: fakeit::Mock mock; }; diff --git a/src/gui/projectconfig/projectconfig_p.h b/src/gui/projectconfig/projectconfig_p.h index d02c899c5..70d17b25a 100644 --- a/src/gui/projectconfig/projectconfig_p.h +++ b/src/gui/projectconfig/projectconfig_p.h @@ -203,6 +203,12 @@ class ProjectConfigPrivate : public QWidget { node->nodeGraphicsObject().update(); addShadow(*node); }); + + QJsonObject msg; + msg["msg"] = BcastMsg::GuiStyleSwitched; + msg["dark_mode_style"] = darkMode; + + emit simBcast(msg); } signals: From 718084e566b0e505770017f95a58194e2e5e13f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Thu, 15 Oct 2020 17:12:46 +0200 Subject: [PATCH 06/11] Fix missing UT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- .../qmlexecutor/tests/qmlexecutor_test.cpp | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/qmlexecutor/tests/qmlexecutor_test.cpp b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp index 2e95550b1..b0ba16a4a 100644 --- a/src/components/qmlexecutor/tests/qmlexecutor_test.cpp +++ b/src/components/qmlexecutor/tests/qmlexecutor_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "gui/qmlexecutorguiint.h" #include "qmlexecutor.h" @@ -123,7 +124,10 @@ TEST_CASE("simBcastRcv", "[qmlexecutor]") { QMLExecutor c; - REQUIRE_NOTHROW(c.simBcastRcv(QJsonObject(), QVariant())); + QJsonObject msg; + msg["msg"] = BcastMsg::GuiStyleSwitched; + + REQUIRE_NOTHROW(c.simBcastRcv(msg, QVariant())); } TEST_CASE("getSupportedProperties", "[qmlexecutor]") @@ -185,6 +189,27 @@ TEST_CASE("getQConfig", "[qmlexecutor]") REQUIRE(config->property(propertyQMLFileName).toString() == fileName); } +TEST_CASE("dock/undock", "[qmlexecutor]") +{ + auto goodMock = new GuiMock(); + + Fake(Method(goodMock->mock, setModel)); + Fake(Method(goodMock->mock, loadQML)); + Fake(Method(goodMock->mock, mainWidget)); + + QMLExecutorCtx context(goodMock); + QMLExecutor c(std::move(context)); + QSignalSpy dockSpy(&c, &QMLExecutor::mainWidgetDockToggled); + + emit goodMock->dockUndock(); + emit goodMock->dockUndock(); + emit goodMock->dockUndock(); + + REQUIRE(dockSpy.count() == 3); + + Verify(Method(goodMock->mock, mainWidget)).Exactly(3); +} + int main(int argc, char* argv[]) { bool haveDebug = std::getenv("CDS_DEBUG") != nullptr; From 3d71e9e27e139ed3084220b8df9eae4723631d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Thu, 15 Oct 2020 21:05:49 +0200 Subject: [PATCH 07/11] Fix QML reloading; Add new QML example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- .../qmlexecutor/{ => examples}/sample.qml | 0 .../qmlexecutor/examples/simple_forwarder.qml | 41 +++++++++++++++++++ .../qmlexecutor/gui/qmlexecutorguiimpl.h | 27 ++++++++---- 3 files changed, 59 insertions(+), 9 deletions(-) rename src/components/qmlexecutor/{ => examples}/sample.qml (100%) create mode 100644 src/components/qmlexecutor/examples/simple_forwarder.qml diff --git a/src/components/qmlexecutor/sample.qml b/src/components/qmlexecutor/examples/sample.qml similarity index 100% rename from src/components/qmlexecutor/sample.qml rename to src/components/qmlexecutor/examples/sample.qml diff --git a/src/components/qmlexecutor/examples/simple_forwarder.qml b/src/components/qmlexecutor/examples/simple_forwarder.qml new file mode 100644 index 000000000..b803b1ca8 --- /dev/null +++ b/src/components/qmlexecutor/examples/simple_forwarder.qml @@ -0,0 +1,41 @@ +import QtQuick 2.0 + +/* + * This example receives a frame or signal modifies and transmitt it + */ + +Item { + function frameReceived(frameId, framePayload) { + // Increment frame ID of received CAN + frameId += 1 + + // LogWindow is QPlainTextEdit widget exsposed to QML + // You can use it to append text or control its visibility + LogWindow.appendPlainText("Frame: " + frameId + " Forwarded") + + // Transmit frame + // + // CANBusModel is an QObject exposed to QML + // You can use it to receive and transmit frames and signals + CANBusModel.sendFrame(frameId, framePayload) + } + + function signalReceived(signalId, signalValue) { + // Increment signal value + signalValue += 1 + + // Transmit signal + CANBusModel.sendSignal(signalId, signalValue) + } + + Component.onCompleted: { + // We have no GUI to display so LogWindow can be visible + LogWindow.setVisible(true) + + // Connect QML functions to CANBusModel signals + CANBusModel.frameReceived.connect(frameReceived) + CANBusModel.signalReceived.connect(signalReceived) + } +} + + diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h index 0686f6a9d..5c60d8cf5 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h @@ -49,7 +49,8 @@ class QMLExecutorGuiImpl : public QMLExecutorGuiInt constexpr int size = 1234; _ui->splitter->setSizes(QList({size,size})); - QObject::connect(_ui->quickWidget, &QQuickWidget::statusChanged, this, &QMLExecutorGuiImpl::handleStatusChange); + _ui->fileName->setReadOnly(true); + QObject::connect(_ui->editQMLButton, SIGNAL(clicked()), this, SLOT(editQML())); QObject::connect(_ui->loadQMLButton, SIGNAL(clicked()), this, SLOT(browseAndOpenQML())); QObject::connect(&_qmlUpdateCheckTimer, SIGNAL(timeout()), this, SLOT(checkQMLModification())); @@ -96,27 +97,36 @@ public slots: { if(url.isValid() && !url.isEmpty()) { - _qmlUrl = url; + // The easiest way to reload QML seems to be creating new QQuickWidget + auto splitter = static_cast(_ui->quickWidget->parentWidget()); + auto splitState = splitter->saveState(); - _ui->editQMLButton->setEnabled(true); + delete _ui->quickWidget; + _ui->quickWidget = new QQuickWidget(); + updateUIColor(); + splitter->insertWidget(0, _ui->quickWidget); + splitter->restoreState(splitState); - log("Loading qml file: " + url.toLocalFile()); + QObject::connect(_ui->quickWidget, &QQuickWidget::statusChanged, this, &QMLExecutorGuiImpl::handleStatusChange); - _ui->quickWidget->setSource(QUrl()); - _ui->quickWidget->engine()->clearComponentCache(); + _qmlUrl = url; + + log("Loading qml file: " + url.toLocalFile()); assert(_CANBusModel != nullptr); _ui->quickWidget->rootContext()->setContextProperty("CANBusModel", _CANBusModel); + _ui->quickWidget->rootContext()->setContextProperty("LogWindow", _ui->logWindow); _ui->quickWidget->setSource(url); - _ui->fileName->setText(url.url()); - + _ui->fileName->setText(url.toLocalFile()); emit QMLLoaded(_qmlUrl); startQMLFileModificationChecks(); + + _ui->editQMLButton->setEnabled(true); } } @@ -161,7 +171,6 @@ public slots: if(askForQMLReload()) { loadQML(_qmlUrl); - } startQMLFileModificationChecks(); From 7757a1e2f2d55d4ab8599b1548fda8d2ad01710b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Thu, 15 Oct 2020 22:03:41 +0200 Subject: [PATCH 08/11] Icons added; Clear button added; MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- CANdevResources.qrc | 3 + files/images/light/CANbus_icon_Log.svg | 68 +++++++++++++++++ .../light/CANbus_icon_OpenProject_Grey.svg | 52 +++++++++++++ files/images/light/CANbus_icon_edit.svg | 68 +++++++++++++++++ src/components/qmlexecutor/gui/qmlexecutor.ui | 75 +++++++++++++++++-- .../qmlexecutor/gui/qmlexecutorguiimpl.h | 3 + 6 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 files/images/light/CANbus_icon_Log.svg create mode 100644 files/images/light/CANbus_icon_OpenProject_Grey.svg create mode 100644 files/images/light/CANbus_icon_edit.svg diff --git a/CANdevResources.qrc b/CANdevResources.qrc index fc6170b06..281f5d3f0 100644 --- a/CANdevResources.qrc +++ b/CANdevResources.qrc @@ -86,6 +86,9 @@ files/images/light/CANbus_icon_arrow_down.svg files/images/light/CANbus_icon_arrow_up.svg files/images/db_icon.svg + files/images/light/CANbus_icon_Log.svg + files/images/light/CANbus_icon_OpenProject_Grey.svg + files/images/light/CANbus_icon_edit.svg files/json/projectConfigSchema.json diff --git a/files/images/light/CANbus_icon_Log.svg b/files/images/light/CANbus_icon_Log.svg new file mode 100644 index 000000000..2f33e0c9e --- /dev/null +++ b/files/images/light/CANbus_icon_Log.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/files/images/light/CANbus_icon_OpenProject_Grey.svg b/files/images/light/CANbus_icon_OpenProject_Grey.svg new file mode 100644 index 000000000..73b4c5c95 --- /dev/null +++ b/files/images/light/CANbus_icon_OpenProject_Grey.svg @@ -0,0 +1,52 @@ + +image/svg+xml + + + diff --git a/files/images/light/CANbus_icon_edit.svg b/files/images/light/CANbus_icon_edit.svg new file mode 100644 index 000000000..d787118a6 --- /dev/null +++ b/files/images/light/CANbus_icon_edit.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/components/qmlexecutor/gui/qmlexecutor.ui b/src/components/qmlexecutor/gui/qmlexecutor.ui index f56cae3d2..5e64f5814 100644 --- a/src/components/qmlexecutor/gui/qmlexecutor.ui +++ b/src/components/qmlexecutor/gui/qmlexecutor.ui @@ -33,13 +33,20 @@ 32 + + Load QML + - L + + + + + :/images/files/images/light/CANbus_icon_OpenProject_Grey.svg:/images/files/images/light/CANbus_icon_OpenProject_Grey.svg - 32 - 32 + 30 + 30 @@ -61,13 +68,20 @@ 32 + + Edit QML + - E + + + + + :/images/files/images/light/CANbus_icon_edit.svg:/images/files/images/light/CANbus_icon_edit.svg - 32 - 32 + 22 + 22 @@ -79,7 +93,7 @@ - + 32 @@ -92,8 +106,15 @@ 32 + + Clear Log Window + - LW + + + + + :/images/files/images/light/CANbus_icon_Clear.svg:/images/files/images/light/CANbus_icon_Clear.svg @@ -106,6 +127,41 @@ + + + + + 32 + 32 + + + + + 32 + 32 + + + + Show/Hide Log Window + + + + + + + :/images/files/images/light/CANbus_icon_Log.svg:/images/files/images/light/CANbus_icon_Log.svg + + + + 22 + 22 + + + + true + + + @@ -120,6 +176,9 @@ 32 + + Dock/Undock + diff --git a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h index 5c60d8cf5..35fbe96ae 100644 --- a/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h +++ b/src/components/qmlexecutor/gui/qmlexecutorguiimpl.h @@ -58,6 +58,9 @@ class QMLExecutorGuiImpl : public QMLExecutorGuiInt QObject::connect(_ui->showLog, &QPushButton::clicked, [this] { _ui->logWindow->setVisible(!_ui->logWindow->isVisible()); }); + QObject::connect(_ui->pbClean, &QPushButton::clicked, [this] { + _ui->logWindow->clear(); + }); } virtual QWidget* mainWidget() override From 20ce2f8fa77be16efcdedf274ec89c487e1953a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Fri, 16 Oct 2020 18:51:46 +0200 Subject: [PATCH 09/11] Add timer example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- .../qmlexecutor/examples/simple_timer.qml | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/components/qmlexecutor/examples/simple_timer.qml diff --git a/src/components/qmlexecutor/examples/simple_timer.qml b/src/components/qmlexecutor/examples/simple_timer.qml new file mode 100644 index 000000000..abbe6b06b --- /dev/null +++ b/src/components/qmlexecutor/examples/simple_timer.qml @@ -0,0 +1,83 @@ +import QtQuick 2.0 + +/* + * This example shows how to use timers + */ + +Item { + property var frameId1 : 0x112233 + property var payload1 : 0 + property var frameId2 : 0x7ff + property var payload2 : 0 + + Timer { + id: timer1 + interval: 50; running: false; repeat: true + onTriggered: timer1Func() + } + + Timer { + id: timer2 + interval: 10; running: false; repeat: true + onTriggered: timer2Func() + } + + function timer1Func() { + let b = new ArrayBuffer(4); + let view = new DataView(b) + + // x86 architecture is little endian. + // Indicate that we want to store vale using big endian (3rd argument) + view.setUint32(0, payload1++, false) + + CANBusModel.sendFrame(frameId1, b) + } + + function timer2Func() { + + let b = new ArrayBuffer(8); + let view = new Uint8Array(b) + + // Due to differences in endianess + // 0 in the array is number 7 in can frame + // Let's ignore it for now and reverse array at the end + view[0] = payload2 & 0xff + view[1] = (payload2 & 0xff00) >> 8 + view[2] = (payload2 & 0xff0000) >> 16 + view[3] = (payload2 & 0xff000000) >> 24 + view[4] = (payload2 & 0xff00000000) >> 32 + view[5] = (payload2 & 0xff0000000000) >> 40 + view[6] = (payload2 & 0xff000000000000) >> 48 + view[7] = (payload2 & 0xff00000000000000) >> 56 + + payload2++ + + view = view.reverse() + + CANBusModel.sendFrame(frameId2, b) + } + + Component.onCompleted: { + // We have no GUI to display so LogWindow can be visible + LogWindow.setVisible(true) + + // Connect QML functions to CANBusModel signals + CANBusModel.simulationStarted.connect(function(){ + LogWindow.appendPlainText("Simulation started"); + + // Reset payload1 on each simulation start + // payload2 is persistent across simulation start/end + payload1 = 0 + + timer1.start() + timer2.start() + }) + CANBusModel.simulationStopped.connect(function(){ + timer1.stop() + timer2.stop() + LogWindow.appendPlainText("Simulation stopped"); + }) + } +} + + From 9788332aba6a13cabf3fe0210de29f28294fcbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Mon, 31 May 2021 12:24:26 +0200 Subject: [PATCH 10/11] Fix appveyor path to Qt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index b823bb73f..b563a5b84 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,7 +6,7 @@ environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 GENERATOR: '"MinGW Makefiles"' BUILD_TYPE: Release - QT_PATH: C:\Qt\5.15.0\mingw81_32 + QT_PATH: C:\Qt\5.15\mingw81_32 BOOST_PATH: C:\Libraries\boost_1_73_0 PACKAGE: 1 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 From fbcddbc34a6c286f89827042be2e14f8ace39797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remigiusz=20Ko=C5=82=C5=82=C4=85taj?= Date: Mon, 31 May 2021 13:57:13 +0200 Subject: [PATCH 11/11] Change badge address for Travis CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Remigiusz Kołłątaj --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b20ed4591..5d178e7d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # CANdevStudio -[![Build Status](https://travis-ci.org/GENIVI/CANdevStudio.svg?branch=master)](https://travis-ci.org/GENIVI/CANdevStudio) [![Build status](https://ci.appveyor.com/api/projects/status/wak1kdfueyn68h0t/branch/master?svg=true)](https://ci.appveyor.com/project/rkollataj/candevstudio-j9v77/branch/master) [![codecov](https://codecov.io/gh/GENIVI/CANdevStudio/branch/master/graph/badge.svg)](https://codecov.io/gh/GENIVI/CANdevStudio) [![Download](https://api.bintray.com/packages/rkollataj/CANdevStudio/releases/images/download.svg) ](https://bintray.com/rkollataj/CANdevStudio/releases/_latestVersion) [![Download](https://api.bintray.com/packages/rkollataj/CANdevStudio/master/images/download.svg) ](https://bintray.com/rkollataj/CANdevStudio/master/_latestVersion) [![Doxygen](https://img.shields.io/badge/Doxygen-master-blue.svg)](https://genivi.github.io/CANdevStudio/master/) +[![Build Status](https://travis-ci.com/GENIVI/CANdevStudio.svg?branch=master)](https://travis-ci.org/GENIVI/CANdevStudio) [![Build status](https://ci.appveyor.com/api/projects/status/wak1kdfueyn68h0t/branch/master?svg=true)](https://ci.appveyor.com/project/rkollataj/candevstudio-j9v77/branch/master) [![codecov](https://codecov.io/gh/GENIVI/CANdevStudio/branch/master/graph/badge.svg)](https://codecov.io/gh/GENIVI/CANdevStudio) [![Download](https://api.bintray.com/packages/rkollataj/CANdevStudio/releases/images/download.svg) ](https://bintray.com/rkollataj/CANdevStudio/releases/_latestVersion) [![Download](https://api.bintray.com/packages/rkollataj/CANdevStudio/master/images/download.svg) ](https://bintray.com/rkollataj/CANdevStudio/master/_latestVersion) [![Doxygen](https://img.shields.io/badge/Doxygen-master-blue.svg)](https://genivi.github.io/CANdevStudio/master/)