mirror of
https://github.com/Sani7/remote-ui.git
synced 2026-10-26 21:51:13 +01:00
client
now works on windows
This commit is contained in:
+37
-31
@@ -6,43 +6,17 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
|||||||
file(GLOB_RECURSE ui "ui/*")
|
file(GLOB_RECURSE ui "ui/*")
|
||||||
|
|
||||||
set(PROJECT_SOURCES
|
set(PROJECT_SOURCES
|
||||||
${ui}
|
|
||||||
helpers.cpp
|
|
||||||
helpers.hpp
|
|
||||||
IterableEnum.hpp
|
IterableEnum.hpp
|
||||||
main.cpp
|
main.cpp
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
mainwindow.hpp
|
mainwindow.hpp
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
ui_base.cpp
|
|
||||||
ui_base.hpp
|
|
||||||
web_socket_wrapper.cpp
|
|
||||||
web_socket_wrapper.hpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
|
||||||
qt_add_executable(unisim_client
|
qt_add_executable(unisim_client
|
||||||
MANUAL_FINALIZATION
|
MANUAL_FINALIZATION
|
||||||
${PROJECT_SOURCES}
|
${PROJECT_SOURCES}
|
||||||
$<$<BOOL:${EMSCRIPTEN}>:${sims}>
|
|
||||||
)
|
)
|
||||||
# Define target properties for Android with Qt 6 as:
|
|
||||||
# set_property(TARGET Simulator - CVS_I10 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
|
||||||
# ${CMAKE_CURRENT_SOURCE_DIR } / android)
|
|
||||||
# For more information, see https: // doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
|
||||||
else()
|
|
||||||
if(ANDROID)
|
|
||||||
add_library(unisim_client SHARED
|
|
||||||
${PROJECT_SOURCES}
|
|
||||||
)
|
|
||||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
|
||||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
|
||||||
else()
|
|
||||||
add_executable(unisim_client
|
|
||||||
${PROJECT_SOURCES}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
unisim_client
|
unisim_client
|
||||||
@@ -50,18 +24,16 @@ set_target_properties(
|
|||||||
AUTOMOC ON
|
AUTOMOC ON
|
||||||
AUTORCC ON
|
AUTORCC ON
|
||||||
AUTOUIC ON
|
AUTOUIC ON
|
||||||
LINK_FLAGS "-Wl,--export-dynamic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(unisim_client
|
target_include_directories(unisim_client
|
||||||
PUBLIC
|
PUBLIC
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/simulators
|
${CMAKE_CURRENT_LIST_DIR}/simulators
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui
|
|
||||||
${PROJECT_BINARY_DIR}/
|
${PROJECT_BINARY_DIR}/
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(unisim_client PRIVATE qwt spdlog Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WebSockets magic_enum::magic_enum nlohmann_json::nlohmann_json git_tag)
|
target_link_libraries(unisim_client PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WebSockets spdlog magic_enum::magic_enum nlohmann_json::nlohmann_json git_tag client_common)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(TARGETS unisim_client
|
install(TARGETS unisim_client
|
||||||
@@ -74,6 +46,40 @@ if(QT_VERSION_MAJOR EQUAL 6)
|
|||||||
qt_finalize_executable(unisim_client)
|
qt_finalize_executable(unisim_client)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_library(client_common SHARED)
|
||||||
|
target_sources(client_common
|
||||||
|
PRIVATE
|
||||||
|
${ui}
|
||||||
|
helpers.cpp
|
||||||
|
helpers.hpp
|
||||||
|
ui_base.cpp
|
||||||
|
ui_base.hpp
|
||||||
|
web_socket_wrapper.cpp
|
||||||
|
web_socket_wrapper.hpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(client_common PRIVATE
|
||||||
|
INTERNAL
|
||||||
|
)
|
||||||
|
target_link_libraries(client_common
|
||||||
|
PUBLIC
|
||||||
|
qwt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WebSockets magic_enum::magic_enum nlohmann_json::nlohmann_json
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(
|
||||||
|
client_common
|
||||||
|
PROPERTIES
|
||||||
|
AUTOMOC ON
|
||||||
|
AUTORCC ON
|
||||||
|
AUTOUIC ON
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(client_common
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/ui
|
||||||
|
${PROJECT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
if (NOT EMSCRIPTEN)
|
if (NOT EMSCRIPTEN)
|
||||||
file(GLOB_RECURSE sims "simulators/*.cpp")
|
file(GLOB_RECURSE sims "simulators/*.cpp")
|
||||||
# Loop through each .cpp file and create a library for it
|
# Loop through each .cpp file and create a library for it
|
||||||
@@ -83,8 +89,8 @@ if (NOT EMSCRIPTEN)
|
|||||||
|
|
||||||
# Create a library for each .cpp file
|
# Create a library for each .cpp file
|
||||||
add_library(ui_${LIB_NAME} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.hpp ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.cpp ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.ui)
|
add_library(ui_${LIB_NAME} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.hpp ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.cpp ${CMAKE_CURRENT_SOURCE_DIR}/simulators/${LIB_NAME}.ui)
|
||||||
target_link_libraries(ui_${LIB_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets magic_enum::magic_enum nlohmann_json::nlohmann_json)
|
target_link_libraries(ui_${LIB_NAME} PUBLIC Qt${QT_VERSION_MAJOR}::Widgets client_common)
|
||||||
target_include_directories(ui_${LIB_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ui ${CMAKE_SOURCE_DIR}/external/qwt/src ${PROJECT_BINARY_DIR}/)
|
target_include_directories(ui_${LIB_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ui ${PROJECT_BINARY_DIR}/)
|
||||||
|
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
ui_${LIB_NAME}
|
ui_${LIB_NAME}
|
||||||
|
|||||||
@@ -16,8 +16,14 @@ MainWindow::MainWindow(QUrl ws_url, QString sim, QWidget *parent)
|
|||||||
m_web_socket(new Web_socket_wrapper(ws_url))
|
m_web_socket(new Web_socket_wrapper(ws_url))
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
connect(m_web_socket.get(), &Web_socket_wrapper::log_signal, this,
|
||||||
|
[](const char *filename_in, int line_in, const char *funcname_in, int level, QString msg) {
|
||||||
|
spdlog::default_logger_raw()->log(spdlog::source_loc{filename_in, line_in, funcname_in},
|
||||||
|
(spdlog::level::level_enum)level, msg.toStdString());
|
||||||
|
});
|
||||||
|
|
||||||
QLibrary lib;
|
QLibrary lib;
|
||||||
QDirIterator it(QCoreApplication::applicationDirPath(), QStringList() << "*.so", QDir::Files,
|
QDirIterator it(QCoreApplication::applicationDirPath(), QStringList() << "*.so" << "*.dll", QDir::Files,
|
||||||
QDirIterator::Subdirectories);
|
QDirIterator::Subdirectories);
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,17 @@
|
|||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Class representing a better radiobutton group
|
* @brief Class representing a better radiobutton group
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Better_Radiobutton : public QWidget
|
class EXPORT Better_Radiobutton : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
#include "hex_spinbox.hpp"
|
#include "hex_spinbox.hpp"
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class Can_Transceive;
|
class Can_Transceive;
|
||||||
@@ -11,7 +17,7 @@ class Can_Transceive;
|
|||||||
* @brief Class representing a CAN transceive UI component
|
* @brief Class representing a CAN transceive UI component
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Can_Transceive : public QWidget
|
class EXPORT Can_Transceive : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,19 @@
|
|||||||
#include <QRegularExpressionValidator>
|
#include <QRegularExpressionValidator>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Class representing a hexadecimal spin box
|
* @brief Class representing a hexadecimal spin box
|
||||||
* @note Since QSpinBox uses int as the storage type, the effective editing range
|
* @note Since QSpinBox uses int as the storage type, the effective editing range
|
||||||
* is +/- 0x7FFF FFFF, so it can't handle a full unsigned int.
|
* is +/- 0x7FFF FFFF, so it can't handle a full unsigned int.
|
||||||
* QDoubleSpinBox would be a more suitable base class if a wider range is needed.
|
* QDoubleSpinBox would be a more suitable base class if a wider range is needed.
|
||||||
*/
|
*/
|
||||||
class HexSpinBox : public QSpinBox
|
class EXPORT HexSpinBox : public QSpinBox
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -1,11 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "QPushButton"
|
#include "QPushButton"
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Class representing an LED widget
|
* @brief Class representing an LED widget
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Led : public QPushButton
|
class EXPORT Led : public QPushButton
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -4,11 +4,17 @@
|
|||||||
#include <qwt_plot_curve.h>
|
#include <qwt_plot_curve.h>
|
||||||
#include <qwt_text.h>
|
#include <qwt_text.h>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Class wrapping a QwtPlot for plotting data
|
* @brief Class wrapping a QwtPlot for plotting data
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Plot_wrapper : public QwtPlot
|
class EXPORT Plot_wrapper : public QwtPlot
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|||||||
+7
-1
@@ -3,6 +3,12 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
Q_FORWARD_DECLARE_OBJC_CLASS(Web_socket_wrapper);
|
Q_FORWARD_DECLARE_OBJC_CLASS(Web_socket_wrapper);
|
||||||
Q_FORWARD_DECLARE_OBJC_CLASS(QTimer);
|
Q_FORWARD_DECLARE_OBJC_CLASS(QTimer);
|
||||||
Q_FORWARD_DECLARE_OBJC_CLASS(QLabel);
|
Q_FORWARD_DECLARE_OBJC_CLASS(QLabel);
|
||||||
@@ -28,7 +34,7 @@ using json = nlohmann::json;
|
|||||||
/**
|
/**
|
||||||
* @brief Base class for client UIs
|
* @brief Base class for client UIs
|
||||||
*/
|
*/
|
||||||
class UI_base : public QMainWindow
|
class EXPORT UI_base : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
#include "web_socket_wrapper.hpp"
|
#include "web_socket_wrapper.hpp"
|
||||||
#include "spdlog/spdlog.h"
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QtWebSockets/QWebSocket>
|
#include <QtWebSockets/QWebSocket>
|
||||||
#include <magic_enum/magic_enum.hpp>
|
#include <magic_enum/magic_enum.hpp>
|
||||||
|
|
||||||
|
#define LOG_SRC_LOC(level, msg) emit log_signal(__FILE__, __LINE__, __FUNCTION__, (int)level, msg)
|
||||||
|
#define LOG_TRACE(msg) LOG_SRC_LOC(Log_level_enum::trace, msg)
|
||||||
|
#define LOG_DEBUG(msg) LOG_SRC_LOC(Log_level_enum::debug, msg)
|
||||||
|
#define LOG_INFO(msg) LOG_SRC_LOC(Log_level_enum::info, msg)
|
||||||
|
#define LOG_WARN(msg) LOG_SRC_LOC(Log_level_enum::warn, msg)
|
||||||
|
#define LOG_ERROR(msg) LOG_SRC_LOC(Log_level_enum::err, msg)
|
||||||
|
#define LOG_CRITICAL(msg) LOG_SRC_LOC(Log_level_enum::critical, msg)
|
||||||
|
|
||||||
Web_socket_wrapper::Web_socket_wrapper(const QUrl &url, QObject *parent)
|
Web_socket_wrapper::Web_socket_wrapper(const QUrl &url, QObject *parent)
|
||||||
: QObject(parent), m_web_socket(new QWebSocket("", QWebSocketProtocol::VersionLatest, this)),
|
: QObject(parent), m_web_socket(new QWebSocket("", QWebSocketProtocol::VersionLatest, this)),
|
||||||
m_ping_timer(new QTimer(this)), m_pong_timer(new QTimer(this))
|
m_ping_timer(new QTimer(this)), m_pong_timer(new QTimer(this))
|
||||||
@@ -11,8 +18,9 @@ Web_socket_wrapper::Web_socket_wrapper(const QUrl &url, QObject *parent)
|
|||||||
connect(m_web_socket, &QWebSocket::connected, this, &Web_socket_wrapper::m_on_connected);
|
connect(m_web_socket, &QWebSocket::connected, this, &Web_socket_wrapper::m_on_connected);
|
||||||
connect(m_web_socket, &QWebSocket::disconnected, this, &Web_socket_wrapper::on_closed);
|
connect(m_web_socket, &QWebSocket::disconnected, this, &Web_socket_wrapper::on_closed);
|
||||||
connect(m_web_socket, &QWebSocket::pong, this, &Web_socket_wrapper::m_on_pong);
|
connect(m_web_socket, &QWebSocket::pong, this, &Web_socket_wrapper::m_on_pong);
|
||||||
connect(m_web_socket, &QWebSocket::errorOccurred, this,
|
connect(m_web_socket, &QWebSocket::errorOccurred, this, [this](QAbstractSocket::SocketError error) {
|
||||||
[=](QAbstractSocket::SocketError error) { SPDLOG_ERROR(magic_enum::enum_name(error)); });
|
LOG_ERROR(QString::fromStdString(std::string(magic_enum::enum_name(error))));
|
||||||
|
});
|
||||||
connect(m_ping_timer, &QTimer::timeout, this, [=, this] {
|
connect(m_ping_timer, &QTimer::timeout, this, [=, this] {
|
||||||
m_web_socket->ping();
|
m_web_socket->ping();
|
||||||
m_pong_timer->start(1000);
|
m_pong_timer->start(1000);
|
||||||
@@ -200,7 +208,7 @@ void Web_socket_wrapper::m_on_pong(quint64 elapsedTime)
|
|||||||
|
|
||||||
void Web_socket_wrapper::m_on_pong_timeout()
|
void Web_socket_wrapper::m_on_pong_timeout()
|
||||||
{
|
{
|
||||||
SPDLOG_CRITICAL("Pong timeout");
|
LOG_CRITICAL("Pong timeout");
|
||||||
m_web_socket->close();
|
m_web_socket->close();
|
||||||
emit on_closed();
|
emit on_closed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef INTERNAL
|
||||||
|
#define EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
Q_FORWARD_DECLARE_OBJC_CLASS(QWebSocket);
|
Q_FORWARD_DECLARE_OBJC_CLASS(QWebSocket);
|
||||||
Q_FORWARD_DECLARE_OBJC_CLASS(QTimer);
|
Q_FORWARD_DECLARE_OBJC_CLASS(QTimer);
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -9,7 +16,7 @@ using json = nlohmann::json;
|
|||||||
* @brief Class wrapping a WebSocket connection
|
* @brief Class wrapping a WebSocket connection
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Web_socket_wrapper : public QObject
|
class EXPORT Web_socket_wrapper : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -27,6 +34,18 @@ class Web_socket_wrapper : public QObject
|
|||||||
begin = 0
|
begin = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Log_level_enum : int
|
||||||
|
{
|
||||||
|
trace,
|
||||||
|
debug,
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
err,
|
||||||
|
critical,
|
||||||
|
off,
|
||||||
|
n_levels
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enumeration of events
|
* @brief Enumeration of events
|
||||||
*
|
*
|
||||||
@@ -121,7 +140,7 @@ class Web_socket_wrapper : public QObject
|
|||||||
*/
|
*/
|
||||||
void inhibit_events(bool inhibit);
|
void inhibit_events(bool inhibit);
|
||||||
|
|
||||||
Q_SIGNALS:
|
signals:
|
||||||
/**
|
/**
|
||||||
* @brief Signal emitted when the WebSocket is connected
|
* @brief Signal emitted when the WebSocket is connected
|
||||||
*
|
*
|
||||||
@@ -145,6 +164,8 @@ class Web_socket_wrapper : public QObject
|
|||||||
*/
|
*/
|
||||||
void on_event_cb(json &data);
|
void on_event_cb(json &data);
|
||||||
|
|
||||||
|
void log_signal(const char *filename_in, int line_in, const char *funcname_in, int level, QString msg);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
/**
|
/**
|
||||||
* @brief Internal slot for handling WebSocket connection established
|
* @brief Internal slot for handling WebSocket connection established
|
||||||
|
|||||||
Vendored
+1
@@ -1,3 +1,4 @@
|
|||||||
|
set(QWT_BUILD_SHARED ON)
|
||||||
add_subdirectory(color)
|
add_subdirectory(color)
|
||||||
add_subdirectory(cpp-templates)
|
add_subdirectory(cpp-templates)
|
||||||
add_subdirectory(git_tag_cpp)
|
add_subdirectory(git_tag_cpp)
|
||||||
|
|||||||
Vendored
+1
-1
Submodule external/qwt updated: a5dff947d9...2a2b02f310
Reference in New Issue
Block a user