mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 20:00:05 +00:00
commit
85573d6103
27 changed files with 2127 additions and 503 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
msvc_version: 2022
|
||||
qt_version: 6.6.2
|
||||
qt_arch: win64_msvc2019_64
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport
|
||||
qt_tools: ''
|
||||
conan_arch: x86_64
|
||||
conan_compiler: Visual Studio
|
||||
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
compiler: gcc
|
||||
qt_version: 6.6.2
|
||||
qt_arch: gcc_64
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning
|
||||
qt_modules: qtimageformats qtmultimedia qtpositioning qtserialport
|
||||
qt_tools: ''
|
||||
conan_arch: x86_64
|
||||
conan_compiler: gcc
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Supercell Wx uses code from the following dependencies:
|
|||
| [MapLibre Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) |
|
||||
| [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native |
|
||||
| [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) |
|
||||
| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt SQL, Qt SVG, Qt Widgets<br/>Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html |
|
||||
| [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt Serial Port, Qt SQL, Qt SVG, Qt Widgets<br/>Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html |
|
||||
| [re2](https://github.com/google/re2) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) |
|
||||
| [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) |
|
||||
| [SQLite](https://www.sqlite.org/) | Public Domain |
|
||||
|
|
|
|||
1
scwx-qt/res/icons/font-awesome-6/copy-regular.svg
Normal file
1
scwx-qt/res/icons/font-awesome-6/copy-regular.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M384 336H192c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16l140.1 0L400 115.9V320c0 8.8-7.2 16-16 16zM192 384H384c35.3 0 64-28.7 64-64V115.9c0-12.7-5.1-24.9-14.1-33.9L366.1 14.1c-9-9-21.2-14.1-33.9-14.1H192c-35.3 0-64 28.7-64 64V320c0 35.3 28.7 64 64 64zM64 128c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H256c35.3 0 64-28.7 64-64V416H272v32c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192c0-8.8 7.2-16 16-16H96V128H64z"/></svg>
|
||||
|
After Width: | Height: | Size: 646 B |
|
|
@ -28,6 +28,7 @@ find_package(QT NAMES Qt6
|
|||
OpenGL
|
||||
OpenGLWidgets
|
||||
Positioning
|
||||
SerialPort
|
||||
Svg
|
||||
Widgets REQUIRED)
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ find_package(Qt${QT_VERSION_MAJOR}
|
|||
OpenGL
|
||||
OpenGLWidgets
|
||||
Positioning
|
||||
SerialPort
|
||||
Svg
|
||||
Widgets
|
||||
REQUIRED)
|
||||
|
|
@ -97,6 +99,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
|
|||
source/scwx/qt/manager/resource_manager.hpp
|
||||
source/scwx/qt/manager/settings_manager.hpp
|
||||
source/scwx/qt/manager/text_event_manager.hpp
|
||||
source/scwx/qt/manager/thread_manager.hpp
|
||||
source/scwx/qt/manager/timeline_manager.hpp
|
||||
source/scwx/qt/manager/update_manager.hpp)
|
||||
set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
|
||||
|
|
@ -111,6 +114,7 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
|
|||
source/scwx/qt/manager/resource_manager.cpp
|
||||
source/scwx/qt/manager/settings_manager.cpp
|
||||
source/scwx/qt/manager/text_event_manager.cpp
|
||||
source/scwx/qt/manager/thread_manager.cpp
|
||||
source/scwx/qt/manager/timeline_manager.cpp
|
||||
source/scwx/qt/manager/update_manager.cpp)
|
||||
set(HDR_MAP source/scwx/qt/map/alert_layer.hpp
|
||||
|
|
@ -235,6 +239,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
|||
source/scwx/qt/ui/county_dialog.hpp
|
||||
source/scwx/qt/ui/download_dialog.hpp
|
||||
source/scwx/qt/ui/flow_layout.hpp
|
||||
source/scwx/qt/ui/gps_info_dialog.hpp
|
||||
source/scwx/qt/ui/hotkey_edit.hpp
|
||||
source/scwx/qt/ui/imgui_debug_dialog.hpp
|
||||
source/scwx/qt/ui/imgui_debug_widget.hpp
|
||||
|
|
@ -248,6 +253,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
|||
source/scwx/qt/ui/placefile_settings_widget.hpp
|
||||
source/scwx/qt/ui/progress_dialog.hpp
|
||||
source/scwx/qt/ui/radar_site_dialog.hpp
|
||||
source/scwx/qt/ui/serial_port_dialog.hpp
|
||||
source/scwx/qt/ui/settings_dialog.hpp
|
||||
source/scwx/qt/ui/update_dialog.hpp)
|
||||
set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
||||
|
|
@ -258,6 +264,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
|||
source/scwx/qt/ui/county_dialog.cpp
|
||||
source/scwx/qt/ui/download_dialog.cpp
|
||||
source/scwx/qt/ui/flow_layout.cpp
|
||||
source/scwx/qt/ui/gps_info_dialog.cpp
|
||||
source/scwx/qt/ui/hotkey_edit.cpp
|
||||
source/scwx/qt/ui/imgui_debug_dialog.cpp
|
||||
source/scwx/qt/ui/imgui_debug_widget.cpp
|
||||
|
|
@ -272,6 +279,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
|||
source/scwx/qt/ui/progress_dialog.cpp
|
||||
source/scwx/qt/ui/radar_site_dialog.cpp
|
||||
source/scwx/qt/ui/settings_dialog.cpp
|
||||
source/scwx/qt/ui/serial_port_dialog.cpp
|
||||
source/scwx/qt/ui/update_dialog.cpp)
|
||||
set(UI_UI source/scwx/qt/ui/about_dialog.ui
|
||||
source/scwx/qt/ui/alert_dialog.ui
|
||||
|
|
@ -279,6 +287,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui
|
|||
source/scwx/qt/ui/animation_dock_widget.ui
|
||||
source/scwx/qt/ui/collapsible_group.ui
|
||||
source/scwx/qt/ui/county_dialog.ui
|
||||
source/scwx/qt/ui/gps_info_dialog.ui
|
||||
source/scwx/qt/ui/imgui_debug_dialog.ui
|
||||
source/scwx/qt/ui/layer_dialog.ui
|
||||
source/scwx/qt/ui/open_url_dialog.ui
|
||||
|
|
@ -287,6 +296,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui
|
|||
source/scwx/qt/ui/progress_dialog.ui
|
||||
source/scwx/qt/ui/radar_site_dialog.ui
|
||||
source/scwx/qt/ui/settings_dialog.ui
|
||||
source/scwx/qt/ui/serial_port_dialog.ui
|
||||
source/scwx/qt/ui/update_dialog.ui)
|
||||
set(HDR_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.hpp
|
||||
source/scwx/qt/ui/settings/settings_page_widget.hpp
|
||||
|
|
@ -597,11 +607,13 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets
|
|||
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
||||
Qt${QT_VERSION_MAJOR}::Multimedia
|
||||
Qt${QT_VERSION_MAJOR}::Positioning
|
||||
Qt${QT_VERSION_MAJOR}::SerialPort
|
||||
Qt${QT_VERSION_MAJOR}::Svg
|
||||
Boost::json
|
||||
Boost::timer
|
||||
QMapLibre::Core
|
||||
$<$<CXX_COMPILER_ID:MSVC>:opengl32>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:SetupAPI>
|
||||
Fontconfig::Fontconfig
|
||||
GeographicLib::GeographicLib
|
||||
GEOS::geos
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<file>res/icons/font-awesome-6/angles-up-solid.svg</file>
|
||||
<file>res/icons/font-awesome-6/backward-step-solid.svg</file>
|
||||
<file>res/icons/font-awesome-6/book-solid.svg</file>
|
||||
<file>res/icons/font-awesome-6/copy-regular.svg</file>
|
||||
<file>res/icons/font-awesome-6/discord.svg</file>
|
||||
<file>res/icons/font-awesome-6/earth-americas-solid.svg</file>
|
||||
<file>res/icons/font-awesome-6/font-solid.svg</file>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||
#include <scwx/qt/manager/resource_manager.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/manager/thread_manager.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/qt/ui/setup/setup_wizard.hpp>
|
||||
|
|
@ -129,6 +130,9 @@ int main(int argc, char* argv[])
|
|||
// Deinitialize application
|
||||
scwx::qt::manager::RadarProductManager::Cleanup();
|
||||
|
||||
// Stop Qt Threads
|
||||
scwx::qt::manager::ThreadManager::Instance().StopThreads();
|
||||
|
||||
// Gracefully stop the io_context main loop
|
||||
work.reset();
|
||||
threadPool.join();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <scwx/qt/ui/animation_dock_widget.hpp>
|
||||
#include <scwx/qt/ui/collapsible_group.hpp>
|
||||
#include <scwx/qt/ui/flow_layout.hpp>
|
||||
#include <scwx/qt/ui/gps_info_dialog.hpp>
|
||||
#include <scwx/qt/ui/imgui_debug_dialog.hpp>
|
||||
#include <scwx/qt/ui/layer_dialog.hpp>
|
||||
#include <scwx/qt/ui/level2_products_widget.hpp>
|
||||
|
|
@ -85,6 +86,7 @@ public:
|
|||
alertDockWidget_ {nullptr},
|
||||
animationDockWidget_ {nullptr},
|
||||
aboutDialog_ {nullptr},
|
||||
gpsInfoDialog_ {nullptr},
|
||||
imGuiDebugDialog_ {nullptr},
|
||||
layerDialog_ {nullptr},
|
||||
placefileDialog_ {nullptr},
|
||||
|
|
@ -190,6 +192,7 @@ public:
|
|||
ui::AlertDockWidget* alertDockWidget_;
|
||||
ui::AnimationDockWidget* animationDockWidget_;
|
||||
ui::AboutDialog* aboutDialog_;
|
||||
ui::GpsInfoDialog* gpsInfoDialog_;
|
||||
ui::ImGuiDebugDialog* imGuiDebugDialog_;
|
||||
ui::LayerDialog* layerDialog_;
|
||||
ui::PlacefileDialog* placefileDialog_;
|
||||
|
|
@ -264,6 +267,9 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
p->alertDockWidget_->setVisible(false);
|
||||
addDockWidget(Qt::BottomDockWidgetArea, p->alertDockWidget_);
|
||||
|
||||
// GPS Info Dialog
|
||||
p->gpsInfoDialog_ = new ui::GpsInfoDialog(this);
|
||||
|
||||
// Configure Menu
|
||||
ui->menuView->insertAction(ui->actionRadarToolbox,
|
||||
ui->radarToolboxDock->toggleViewAction());
|
||||
|
|
@ -535,6 +541,11 @@ void MainWindow::on_actionExit_triggered()
|
|||
close();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionGpsInfo_triggered()
|
||||
{
|
||||
p->gpsInfoDialog_->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionColorTable_triggered(bool checked)
|
||||
{
|
||||
p->layerModel_->SetLayerDisplayed(types::LayerType::Information,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ private slots:
|
|||
void on_actionOpenTextEvent_triggered();
|
||||
void on_actionSettings_triggered();
|
||||
void on_actionExit_triggered();
|
||||
void on_actionGpsInfo_triggered();
|
||||
void on_actionColorTable_triggered(bool checked);
|
||||
void on_actionRadarRange_triggered(bool checked);
|
||||
void on_actionRadarSites_triggered(bool checked);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1024</width>
|
||||
<height>21</height>
|
||||
<height>33</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
</widget>
|
||||
<addaction name="actionRadarToolbox"/>
|
||||
<addaction name="actionAlerts"/>
|
||||
<addaction name="actionGpsInfo"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuMapLayers"/>
|
||||
</widget>
|
||||
|
|
@ -135,13 +136,13 @@
|
|||
<item>
|
||||
<widget class="QScrollArea" name="radarToolboxScrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
<enum>Qt::ScrollBarPolicy::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
|
|
@ -151,8 +152,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>193</width>
|
||||
<height>688</height>
|
||||
<width>190</width>
|
||||
<height>686</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
|
|
@ -171,10 +172,10 @@
|
|||
<item>
|
||||
<widget class="QFrame" name="radarInfoFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0">
|
||||
<item row="0" column="2">
|
||||
|
|
@ -209,10 +210,10 @@
|
|||
<item row="0" column="4">
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -260,7 +261,7 @@
|
|||
<normaloff>:/res/icons/font-awesome-6/star-solid.svg</normaloff>:/res/icons/font-awesome-6/star-solid.svg</iconset>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -340,7 +341,7 @@
|
|||
<item>
|
||||
<spacer name="radarToolboxSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -481,6 +482,11 @@
|
|||
<string>Radar &Sites</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGpsInfo">
|
||||
<property name="text">
|
||||
<string>&GPS Info</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../../scwx-qt.qrc"/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/manager/settings_manager.hpp>
|
||||
#include <scwx/qt/manager/thread_manager.hpp>
|
||||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/common/geographic.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QGeoPositionInfoSource>
|
||||
|
||||
namespace scwx
|
||||
|
|
@ -23,47 +29,82 @@ public:
|
|||
explicit Impl(PositionManager* self) :
|
||||
self_ {self}, trackingUuid_ {boost::uuids::random_generator()()}
|
||||
{
|
||||
// TODO: macOS requires permission
|
||||
geoPositionInfoSource_ =
|
||||
QGeoPositionInfoSource::createDefaultSource(self);
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
if (geoPositionInfoSource_ != nullptr)
|
||||
{
|
||||
logger_->debug("Using position source: {}",
|
||||
geoPositionInfoSource_->sourceName().toStdString());
|
||||
gpsParent_->moveToThread(gpsThread_);
|
||||
|
||||
QObject::connect(geoPositionInfoSource_,
|
||||
&QGeoPositionInfoSource::positionUpdated,
|
||||
logger_->debug(
|
||||
"Available sources: {}",
|
||||
QGeoPositionInfoSource::availableSources().join(", ").toStdString());
|
||||
|
||||
CreatePositionSourceAsync();
|
||||
|
||||
positioningPluginCallbackUuid_ =
|
||||
generalSettings.positioning_plugin().RegisterValueChangedCallback(
|
||||
[this](const std::string&)
|
||||
{ createPositionSourcePending_ = true; });
|
||||
nmeaBaudRateCallbackUuid_ =
|
||||
generalSettings.nmea_baud_rate().RegisterValueChangedCallback(
|
||||
[this](const std::int64_t&)
|
||||
{ createPositionSourcePending_ = true; });
|
||||
nmeaSourceCallbackUuid_ =
|
||||
generalSettings.nmea_source().RegisterValueChangedCallback(
|
||||
[this](const std::string&)
|
||||
{ createPositionSourcePending_ = true; });
|
||||
|
||||
connect(&SettingsManager::Instance(),
|
||||
&SettingsManager::SettingsSaved,
|
||||
self_,
|
||||
[this](const QGeoPositionInfo& info)
|
||||
[this]()
|
||||
{
|
||||
auto coordinate = info.coordinate();
|
||||
|
||||
if (coordinate != position_.coordinate())
|
||||
if (createPositionSourcePending_)
|
||||
{
|
||||
logger_->debug("Position updated: {}, {}",
|
||||
coordinate.latitude(),
|
||||
coordinate.longitude());
|
||||
CreatePositionSourceAsync();
|
||||
}
|
||||
|
||||
position_ = info;
|
||||
|
||||
Q_EMIT self_->PositionUpdated(info);
|
||||
});
|
||||
}
|
||||
~Impl()
|
||||
{
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
generalSettings.positioning_plugin().UnregisterValueChangedCallback(
|
||||
positioningPluginCallbackUuid_);
|
||||
generalSettings.nmea_baud_rate().UnregisterValueChangedCallback(
|
||||
nmeaBaudRateCallbackUuid_);
|
||||
generalSettings.nmea_source().UnregisterValueChangedCallback(
|
||||
nmeaSourceCallbackUuid_);
|
||||
|
||||
gpsParent_->deleteLater();
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
void CreatePositionSource();
|
||||
void CreatePositionSourceAsync();
|
||||
void EnablePositionUpdates(boost::uuids::uuid uuid, bool enabled);
|
||||
|
||||
PositionManager* self_;
|
||||
QThread* gpsThread_ {ThreadManager::Instance().thread("position_manager")};
|
||||
|
||||
boost::uuids::uuid trackingUuid_;
|
||||
bool trackingEnabled_ {false};
|
||||
|
||||
std::set<boost::uuids::uuid> uuids_ {};
|
||||
|
||||
std::mutex positionSourceMutex_ {};
|
||||
|
||||
QObject* gpsParent_ {new QObject};
|
||||
QGeoPositionInfoSource* geoPositionInfoSource_ {};
|
||||
QGeoPositionInfo position_ {};
|
||||
|
||||
types::PositioningPlugin lastPositioningPlugin_ {
|
||||
types::PositioningPlugin::Unknown};
|
||||
std::int64_t lastNmeaBaudRate_ {-1};
|
||||
std::string lastNmeaSource_ {"?"};
|
||||
|
||||
boost::uuids::uuid positioningPluginCallbackUuid_ {};
|
||||
boost::uuids::uuid nmeaBaudRateCallbackUuid_ {};
|
||||
boost::uuids::uuid nmeaSourceCallbackUuid_ {};
|
||||
|
||||
bool createPositionSourcePending_ {false};
|
||||
};
|
||||
|
||||
PositionManager::PositionManager() : p(std::make_unique<Impl>(this)) {}
|
||||
|
|
@ -79,30 +120,131 @@ bool PositionManager::IsLocationTracked()
|
|||
return p->trackingEnabled_;
|
||||
}
|
||||
|
||||
void PositionManager::Impl::CreatePositionSourceAsync()
|
||||
{
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(gpsThread_),
|
||||
[this]() { CreatePositionSource(); });
|
||||
}
|
||||
|
||||
void PositionManager::Impl::CreatePositionSource()
|
||||
{
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
createPositionSourcePending_ = false;
|
||||
|
||||
types::PositioningPlugin positioningPlugin = types::GetPositioningPlugin(
|
||||
generalSettings.positioning_plugin().GetValue());
|
||||
std::int64_t nmeaBaudRate = generalSettings.nmea_baud_rate().GetValue();
|
||||
std::string nmeaSource = generalSettings.nmea_source().GetValue();
|
||||
|
||||
if (positioningPlugin == lastPositioningPlugin_ &&
|
||||
nmeaBaudRate == lastNmeaBaudRate_ && nmeaSource == lastNmeaSource_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QGeoPositionInfoSource* positionSource = nullptr;
|
||||
|
||||
// TODO: macOS requires permission
|
||||
if (positioningPlugin == types::PositioningPlugin::Default)
|
||||
{
|
||||
positionSource = QGeoPositionInfoSource::createDefaultSource(gpsParent_);
|
||||
}
|
||||
else if (positioningPlugin == types::PositioningPlugin::Nmea)
|
||||
{
|
||||
QVariantMap params {};
|
||||
params["nmea.source"] = QString::fromStdString(nmeaSource);
|
||||
params["nmea.baudrate"] = static_cast<int>(nmeaBaudRate);
|
||||
|
||||
positionSource =
|
||||
QGeoPositionInfoSource::createSource("nmea", params, gpsParent_);
|
||||
}
|
||||
|
||||
if (positionSource != nullptr)
|
||||
{
|
||||
logger_->debug("Using position source: {}",
|
||||
positionSource->sourceName().toStdString());
|
||||
|
||||
QObject::connect(positionSource,
|
||||
&QGeoPositionInfoSource::positionUpdated,
|
||||
self_,
|
||||
[this](const QGeoPositionInfo& info)
|
||||
{
|
||||
auto coordinate = info.coordinate();
|
||||
|
||||
if (coordinate != position_.coordinate())
|
||||
{
|
||||
logger_->trace("Position updated: {}, {}",
|
||||
coordinate.latitude(),
|
||||
coordinate.longitude());
|
||||
}
|
||||
|
||||
position_ = info;
|
||||
|
||||
Q_EMIT self_->PositionUpdated(info);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->error("Unable to create position source for plugin: {}",
|
||||
types::GetPositioningPluginName(positioningPlugin));
|
||||
return;
|
||||
}
|
||||
|
||||
lastPositioningPlugin_ = positioningPlugin;
|
||||
lastNmeaBaudRate_ = nmeaBaudRate;
|
||||
lastNmeaSource_ = nmeaSource;
|
||||
|
||||
std::unique_lock lock {positionSourceMutex_};
|
||||
|
||||
if (geoPositionInfoSource_ != nullptr)
|
||||
{
|
||||
geoPositionInfoSource_->stopUpdates();
|
||||
delete geoPositionInfoSource_;
|
||||
}
|
||||
|
||||
geoPositionInfoSource_ = positionSource;
|
||||
|
||||
if (!uuids_.empty())
|
||||
{
|
||||
positionSource->startUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
void PositionManager::EnablePositionUpdates(boost::uuids::uuid uuid,
|
||||
bool enabled)
|
||||
{
|
||||
if (p->geoPositionInfoSource_ == nullptr)
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(p->gpsThread_),
|
||||
[=, this]()
|
||||
{ p->EnablePositionUpdates(uuid, enabled); });
|
||||
}
|
||||
|
||||
void PositionManager::Impl::EnablePositionUpdates(boost::uuids::uuid uuid,
|
||||
bool enabled)
|
||||
{
|
||||
std::unique_lock lock {positionSourceMutex_};
|
||||
|
||||
if (geoPositionInfoSource_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
if (p->uuids_.empty())
|
||||
if (uuids_.empty())
|
||||
{
|
||||
p->geoPositionInfoSource_->startUpdates();
|
||||
geoPositionInfoSource_->startUpdates();
|
||||
}
|
||||
|
||||
p->uuids_.insert(uuid);
|
||||
uuids_.insert(uuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
p->uuids_.erase(uuid);
|
||||
uuids_.erase(uuid);
|
||||
|
||||
if (p->uuids_.empty())
|
||||
if (uuids_.empty())
|
||||
{
|
||||
p->geoPositionInfoSource_->stopUpdates();
|
||||
geoPositionInfoSource_->stopUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
scwx-qt/source/scwx/qt/manager/thread_manager.cpp
Normal file
97
scwx-qt/source/scwx/qt/manager/thread_manager.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#include <scwx/qt/manager/thread_manager.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <execution>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <QThread>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::manager::thread_manager";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class ThreadManager::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl() {}
|
||||
~Impl() {}
|
||||
|
||||
std::mutex mutex_ {};
|
||||
|
||||
boost::unordered_flat_map<std::string, QThread*> threadMap_ {};
|
||||
};
|
||||
|
||||
ThreadManager::ThreadManager() : p(std::make_unique<Impl>()) {}
|
||||
ThreadManager::~ThreadManager() = default;
|
||||
|
||||
QThread* ThreadManager::thread(const std::string& id, bool autoStart)
|
||||
{
|
||||
std::unique_lock lock {p->mutex_};
|
||||
QThread* thread = nullptr;
|
||||
|
||||
auto it = p->threadMap_.find(id);
|
||||
if (it != p->threadMap_.cend())
|
||||
{
|
||||
thread = it->second;
|
||||
}
|
||||
|
||||
if (thread == nullptr)
|
||||
{
|
||||
logger_->debug("Creating thread: {}", id);
|
||||
|
||||
thread = new QThread(this);
|
||||
p->threadMap_.insert_or_assign(id, thread);
|
||||
|
||||
if (autoStart)
|
||||
{
|
||||
thread->start();
|
||||
}
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
void ThreadManager::StopThreads()
|
||||
{
|
||||
std::unique_lock lock {p->mutex_};
|
||||
|
||||
logger_->debug("Stopping threads");
|
||||
|
||||
std::for_each(std::execution::par_unseq,
|
||||
p->threadMap_.begin(),
|
||||
p->threadMap_.end(),
|
||||
[](auto& thread)
|
||||
{
|
||||
logger_->trace("Stopping thread: {}", thread.first);
|
||||
|
||||
thread.second->quit();
|
||||
if (!thread.second->wait(5000))
|
||||
{
|
||||
logger_->warn("Terminating thread: {}", thread.first);
|
||||
|
||||
thread.second->terminate();
|
||||
thread.second->wait();
|
||||
}
|
||||
|
||||
delete thread.second;
|
||||
});
|
||||
|
||||
p->threadMap_.clear();
|
||||
}
|
||||
|
||||
ThreadManager& ThreadManager::Instance()
|
||||
{
|
||||
static ThreadManager threadManager_ {};
|
||||
return threadManager_;
|
||||
}
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
37
scwx-qt/source/scwx/qt/manager/thread_manager.hpp
Normal file
37
scwx-qt/source/scwx/qt/manager/thread_manager.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
class ThreadManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(ThreadManager)
|
||||
|
||||
public:
|
||||
explicit ThreadManager();
|
||||
~ThreadManager();
|
||||
|
||||
QThread* thread(const std::string& id, bool autoStart = true);
|
||||
|
||||
void StopThreads();
|
||||
|
||||
static ThreadManager& Instance();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace manager
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include <scwx/qt/settings/settings_definitions.hpp>
|
||||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/types/alert_types.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/qt/types/time_types.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
|
@ -37,6 +38,8 @@ public:
|
|||
types::GetDefaultTimeZoneName(types::DefaultTimeZone::Radar);
|
||||
std::string defaultMapProviderValue =
|
||||
map::GetMapProviderName(map::MapProvider::MapTiler);
|
||||
std::string defaultPositioningPlugin =
|
||||
types::GetPositioningPluginName(types::PositioningPlugin::Default);
|
||||
std::string defaultThemeValue =
|
||||
types::GetUiStyleName(types::UiStyle::Default);
|
||||
|
||||
|
|
@ -44,6 +47,7 @@ public:
|
|||
boost::to_lower(defaultDefaultAlertActionValue);
|
||||
boost::to_lower(defaultDefaultTimeZoneValue);
|
||||
boost::to_lower(defaultMapProviderValue);
|
||||
boost::to_lower(defaultPositioningPlugin);
|
||||
boost::to_lower(defaultThemeValue);
|
||||
|
||||
antiAliasingEnabled_.SetDefault(true);
|
||||
|
|
@ -61,6 +65,9 @@ public:
|
|||
mapProvider_.SetDefault(defaultMapProviderValue);
|
||||
mapboxApiKey_.SetDefault("?");
|
||||
maptilerApiKey_.SetDefault("?");
|
||||
nmeaBaudRate_.SetDefault(9600);
|
||||
nmeaSource_.SetDefault("");
|
||||
positioningPlugin_.SetDefault(defaultPositioningPlugin);
|
||||
showMapAttribution_.SetDefault(true);
|
||||
showMapCenter_.SetDefault(false);
|
||||
showMapLogo_.SetDefault(true);
|
||||
|
|
@ -83,6 +90,8 @@ public:
|
|||
loopSpeed_.SetMaximum(99.99);
|
||||
loopTime_.SetMinimum(1);
|
||||
loopTime_.SetMaximum(1440);
|
||||
nmeaBaudRate_.SetMinimum(1);
|
||||
nmeaBaudRate_.SetMaximum(999999999);
|
||||
|
||||
clockFormat_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(scwx::util::ClockFormat,
|
||||
|
|
@ -104,6 +113,10 @@ public:
|
|||
{ return !value.empty(); });
|
||||
maptilerApiKey_.SetValidator([](const std::string& value)
|
||||
{ return !value.empty(); });
|
||||
positioningPlugin_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::PositioningPlugin,
|
||||
types::PositioningPluginIterator(),
|
||||
types::GetPositioningPluginName));
|
||||
theme_.SetValidator( //
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::UiStyle, //
|
||||
types::UiStyleIterator(),
|
||||
|
|
@ -130,6 +143,9 @@ public:
|
|||
SettingsVariable<std::string> mapProvider_ {"map_provider"};
|
||||
SettingsVariable<std::string> mapboxApiKey_ {"mapbox_api_key"};
|
||||
SettingsVariable<std::string> maptilerApiKey_ {"maptiler_api_key"};
|
||||
SettingsVariable<std::int64_t> nmeaBaudRate_ {"nmea_baud_rate"};
|
||||
SettingsVariable<std::string> nmeaSource_ {"nmea_source"};
|
||||
SettingsVariable<std::string> positioningPlugin_ {"positioning_plugin"};
|
||||
SettingsVariable<bool> showMapAttribution_ {"show_map_attribution"};
|
||||
SettingsVariable<bool> showMapCenter_ {"show_map_center"};
|
||||
SettingsVariable<bool> showMapLogo_ {"show_map_logo"};
|
||||
|
|
@ -157,6 +173,9 @@ GeneralSettings::GeneralSettings() :
|
|||
&p->mapProvider_,
|
||||
&p->mapboxApiKey_,
|
||||
&p->maptilerApiKey_,
|
||||
&p->nmeaBaudRate_,
|
||||
&p->nmeaSource_,
|
||||
&p->positioningPlugin_,
|
||||
&p->showMapAttribution_,
|
||||
&p->showMapCenter_,
|
||||
&p->showMapLogo_,
|
||||
|
|
@ -248,6 +267,21 @@ SettingsVariable<std::string>& GeneralSettings::maptiler_api_key() const
|
|||
return p->maptilerApiKey_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::int64_t>& GeneralSettings::nmea_baud_rate() const
|
||||
{
|
||||
return p->nmeaBaudRate_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::string>& GeneralSettings::nmea_source() const
|
||||
{
|
||||
return p->nmeaSource_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::string>& GeneralSettings::positioning_plugin() const
|
||||
{
|
||||
return p->positioningPlugin_;
|
||||
}
|
||||
|
||||
SettingsVariable<bool>& GeneralSettings::show_map_attribution() const
|
||||
{
|
||||
return p->showMapAttribution_;
|
||||
|
|
@ -319,6 +353,9 @@ bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs)
|
|||
lhs.p->mapProvider_ == rhs.p->mapProvider_ &&
|
||||
lhs.p->mapboxApiKey_ == rhs.p->mapboxApiKey_ &&
|
||||
lhs.p->maptilerApiKey_ == rhs.p->maptilerApiKey_ &&
|
||||
lhs.p->nmeaBaudRate_ == rhs.p->nmeaBaudRate_ &&
|
||||
lhs.p->nmeaSource_ == rhs.p->nmeaSource_ &&
|
||||
lhs.p->positioningPlugin_ == rhs.p->positioningPlugin_ &&
|
||||
lhs.p->showMapAttribution_ == rhs.p->showMapAttribution_ &&
|
||||
lhs.p->showMapCenter_ == rhs.p->showMapCenter_ &&
|
||||
lhs.p->showMapLogo_ == rhs.p->showMapLogo_ &&
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ public:
|
|||
SettingsVariable<std::string>& map_provider() const;
|
||||
SettingsVariable<std::string>& mapbox_api_key() const;
|
||||
SettingsVariable<std::string>& maptiler_api_key() const;
|
||||
SettingsVariable<std::int64_t>& nmea_baud_rate() const;
|
||||
SettingsVariable<std::string>& nmea_source() const;
|
||||
SettingsVariable<std::string>& positioning_plugin() const;
|
||||
SettingsVariable<bool>& show_map_attribution() const;
|
||||
SettingsVariable<bool>& show_map_center() const;
|
||||
SettingsVariable<bool>& show_map_logo() const;
|
||||
|
|
|
|||
|
|
@ -19,13 +19,24 @@ static const std::unordered_map<LocationMethod, std::string>
|
|||
{LocationMethod::All, "All"},
|
||||
{LocationMethod::Unknown, "?"}};
|
||||
|
||||
static const std::unordered_map<PositioningPlugin, std::string>
|
||||
positioningPluginName_ {{PositioningPlugin::Default, "Default"},
|
||||
{PositioningPlugin::Nmea, "NMEA"},
|
||||
{PositioningPlugin::Unknown, "?"}};
|
||||
|
||||
SCWX_GET_ENUM(LocationMethod, GetLocationMethod, locationMethodName_)
|
||||
SCWX_GET_ENUM(PositioningPlugin, GetPositioningPlugin, positioningPluginName_)
|
||||
|
||||
const std::string& GetLocationMethodName(LocationMethod locationMethod)
|
||||
{
|
||||
return locationMethodName_.at(locationMethod);
|
||||
}
|
||||
|
||||
const std::string& GetPositioningPluginName(PositioningPlugin positioningPlugin)
|
||||
{
|
||||
return positioningPluginName_.at(positioningPlugin);
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -23,9 +23,24 @@ typedef scwx::util::
|
|||
Iterator<LocationMethod, LocationMethod::Fixed, LocationMethod::All>
|
||||
LocationMethodIterator;
|
||||
|
||||
enum class PositioningPlugin
|
||||
{
|
||||
Default,
|
||||
Nmea,
|
||||
Unknown
|
||||
};
|
||||
typedef scwx::util::Iterator<PositioningPlugin,
|
||||
PositioningPlugin::Default,
|
||||
PositioningPlugin::Nmea>
|
||||
PositioningPluginIterator;
|
||||
|
||||
LocationMethod GetLocationMethod(const std::string& name);
|
||||
const std::string& GetLocationMethodName(LocationMethod locationMethod);
|
||||
|
||||
PositioningPlugin GetPositioningPlugin(const std::string& name);
|
||||
const std::string&
|
||||
GetPositioningPluginName(PositioningPlugin positioningPlugin);
|
||||
|
||||
} // namespace types
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
201
scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp
Normal file
201
scwx-qt/source/scwx/qt/ui/gps_info_dialog.cpp
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
#include "gps_info_dialog.hpp"
|
||||
#include "ui_gps_info_dialog.h"
|
||||
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/common/geographic.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/velocity.h>
|
||||
#include <QClipboard>
|
||||
#include <QGeoPositionInfo>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
|
||||
static const QString kDisabledString_ = "---";
|
||||
|
||||
class GpsInfoDialog::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(GpsInfoDialog* self) : self_ {self} {};
|
||||
~Impl() = default;
|
||||
|
||||
std::shared_ptr<manager::PositionManager> positionManager_ {
|
||||
manager::PositionManager::Instance()};
|
||||
|
||||
void Update(const QGeoPositionInfo& info, bool updateTime = true);
|
||||
|
||||
GpsInfoDialog* self_;
|
||||
};
|
||||
|
||||
GpsInfoDialog::GpsInfoDialog(QWidget* parent) :
|
||||
QDialog(parent), p {std::make_unique<Impl>(this)}, ui(new Ui::GpsInfoDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
p->Update({}, false);
|
||||
|
||||
connect(p->positionManager_.get(),
|
||||
&manager::PositionManager::PositionUpdated,
|
||||
this,
|
||||
[this](const QGeoPositionInfo& info) { p->Update(info); });
|
||||
|
||||
connect(ui->copyCoordinateButton,
|
||||
&QAbstractButton::clicked,
|
||||
this,
|
||||
[this]()
|
||||
{
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
clipboard->setText(ui->coordinateLabel->text());
|
||||
});
|
||||
}
|
||||
|
||||
GpsInfoDialog::~GpsInfoDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void GpsInfoDialog::Impl::Update(const QGeoPositionInfo& info, bool updateTime)
|
||||
{
|
||||
auto coordinate = info.coordinate();
|
||||
|
||||
if (coordinate.isValid())
|
||||
{
|
||||
const QString latitude = QString::fromStdString(
|
||||
common::GetLatitudeString(coordinate.latitude()));
|
||||
const QString longitude = QString::fromStdString(
|
||||
common::GetLongitudeString(coordinate.longitude()));
|
||||
|
||||
self_->ui->coordinateLabel->setText(
|
||||
QString("%1, %2").arg(latitude).arg(longitude));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->coordinateLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (coordinate.type() == QGeoCoordinate::CoordinateType::Coordinate3D)
|
||||
{
|
||||
units::length::meters<double> altitude {coordinate.altitude()};
|
||||
self_->ui->altitudeLabel->setText(
|
||||
QString::fromStdString(units::to_string(altitude)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->altitudeLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::Direction))
|
||||
{
|
||||
units::angle::degrees<double> direction {
|
||||
info.attribute(QGeoPositionInfo::Attribute::Direction)};
|
||||
self_->ui->directionLabel->setText(
|
||||
QString::fromStdString(units::to_string(direction)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->directionLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::GroundSpeed))
|
||||
{
|
||||
units::velocity::meters_per_second<double> groundSpeed {
|
||||
info.attribute(QGeoPositionInfo::Attribute::GroundSpeed)};
|
||||
self_->ui->groundSpeedLabel->setText(
|
||||
QString::fromStdString(units::to_string(groundSpeed)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->groundSpeedLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::VerticalSpeed))
|
||||
{
|
||||
units::velocity::meters_per_second<double> verticalSpeed {
|
||||
info.attribute(QGeoPositionInfo::Attribute::VerticalSpeed)};
|
||||
self_->ui->verticalSpeedLabel->setText(
|
||||
QString::fromStdString(units::to_string(verticalSpeed)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->verticalSpeedLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::MagneticVariation))
|
||||
{
|
||||
units::angle::degrees<double> magneticVariation {
|
||||
info.attribute(QGeoPositionInfo::Attribute::MagneticVariation)};
|
||||
self_->ui->magneticVariationLabel->setText(
|
||||
QString::fromStdString(units::to_string(magneticVariation)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->magneticVariationLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::HorizontalAccuracy))
|
||||
{
|
||||
units::length::meters<double> horizontalAccuracy {
|
||||
info.attribute(QGeoPositionInfo::Attribute::HorizontalAccuracy)};
|
||||
if (!std::isnan(horizontalAccuracy.value()))
|
||||
{
|
||||
self_->ui->horizontalAccuracyLabel->setText(
|
||||
QString::fromStdString(units::to_string(horizontalAccuracy)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->horizontalAccuracyLabel->setText(kDisabledString_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->horizontalAccuracyLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::VerticalAccuracy))
|
||||
{
|
||||
units::length::meters<double> verticalAccuracy {
|
||||
info.attribute(QGeoPositionInfo::Attribute::VerticalAccuracy)};
|
||||
if (!std::isnan(verticalAccuracy.value()))
|
||||
{
|
||||
self_->ui->verticalAccuracyLabel->setText(
|
||||
QString::fromStdString(units::to_string(verticalAccuracy)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->verticalAccuracyLabel->setText(kDisabledString_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->verticalAccuracyLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (info.hasAttribute(QGeoPositionInfo::Attribute::DirectionAccuracy))
|
||||
{
|
||||
units::angle::degrees<double> directionAccuracy {
|
||||
info.attribute(QGeoPositionInfo::Attribute::DirectionAccuracy)};
|
||||
self_->ui->directionAccuracyLabel->setText(
|
||||
QString::fromStdString(units::to_string(directionAccuracy)));
|
||||
}
|
||||
else
|
||||
{
|
||||
self_->ui->directionAccuracyLabel->setText(kDisabledString_);
|
||||
}
|
||||
|
||||
if (updateTime)
|
||||
{
|
||||
self_->ui->lastUpdateLabel->setText(
|
||||
info.timestamp().toString(Qt::DateFormat::ISODate));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
36
scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp
Normal file
36
scwx-qt/source/scwx/qt/ui/gps_info_dialog.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class GpsInfoDialog;
|
||||
}
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
|
||||
class GpsInfoDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(GpsInfoDialog)
|
||||
|
||||
public:
|
||||
explicit GpsInfoDialog(QWidget* parent = nullptr);
|
||||
~GpsInfoDialog();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
Ui::GpsInfoDialog* ui;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
266
scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui
Normal file
266
scwx-qt/source/scwx/qt/ui/gps_info_dialog.ui
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GpsInfoDialog</class>
|
||||
<widget class="QDialog" name="GpsInfoDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>313</width>
|
||||
<height>292</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>GPS Info</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Direction Accuracy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="magneticVariationLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="horizontalAccuracyLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLabel" name="directionAccuracyLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Magnetic Variation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Vertical Speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="directionLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="altitudeLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Vertical Accuracy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="copyCoordinateButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/copy-regular.svg</normaloff>:/res/icons/font-awesome-6/copy-regular.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Horizonal Accuracy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="groundSpeedLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Ground Speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="verticalSpeedLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="coordinateLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Coordinate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLabel" name="verticalAccuracyLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Altitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Last Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLabel" name="lastUpdateLabel">
|
||||
<property name="text">
|
||||
<string>Never</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>verticalAccuracyLabel</zorder>
|
||||
<zorder>label_11</zorder>
|
||||
<zorder>groundSpeedLabel</zorder>
|
||||
<zorder>magneticVariationLabel</zorder>
|
||||
<zorder>label_2</zorder>
|
||||
<zorder>coordinateLabel</zorder>
|
||||
<zorder>altitudeLabel</zorder>
|
||||
<zorder>directionAccuracyLabel</zorder>
|
||||
<zorder>label_5</zorder>
|
||||
<zorder>label_15</zorder>
|
||||
<zorder>label_13</zorder>
|
||||
<zorder>verticalSpeedLabel</zorder>
|
||||
<zorder>label_7</zorder>
|
||||
<zorder>label</zorder>
|
||||
<zorder>horizontalAccuracyLabel</zorder>
|
||||
<zorder>label_17</zorder>
|
||||
<zorder>directionLabel</zorder>
|
||||
<zorder>label_9</zorder>
|
||||
<zorder>copyCoordinateButton</zorder>
|
||||
<zorder>label_3</zorder>
|
||||
<zorder>lastUpdateLabel</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../../scwx-qt.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>GpsInfoDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>GpsInfoDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
613
scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp
Normal file
613
scwx-qt/source/scwx/qt/ui/serial_port_dialog.cpp
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
#define __STDC_WANT_LIB_EXT1__ 1
|
||||
|
||||
#include "serial_port_dialog.hpp"
|
||||
#include "ui_serial_port_dialog.h"
|
||||
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/strings.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
#if defined(_WIN32)
|
||||
# include <Windows.h>
|
||||
# include <SetupAPI.h>
|
||||
# include <initguid.h>
|
||||
# include <devguid.h>
|
||||
# include <devpkey.h>
|
||||
# include <tchar.h>
|
||||
# include <cstdlib>
|
||||
#endif
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::ui::serial_port_dialog";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class SerialPortDialog::Impl
|
||||
{
|
||||
public:
|
||||
struct PortProperties
|
||||
{
|
||||
std::string busReportedDeviceDescription_ {};
|
||||
};
|
||||
|
||||
struct PortSettings
|
||||
{
|
||||
int baudRate_ {-1}; // Positive
|
||||
std::string parity_ {"n"}; // [e]ven, [o]dd, [m]ark, [s]pace, [n]one
|
||||
int dataBits_ {8}; // [4-8]
|
||||
float stopBits_ {1}; // 1, 1.5, 2
|
||||
std::string
|
||||
flowControl_ {}; // "" (none), "p" (hardware), "x" (Xon / Xoff)
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::string, QSerialPortInfo> PortInfoMap;
|
||||
typedef std::unordered_map<std::string, PortProperties> PortPropertiesMap;
|
||||
typedef std::unordered_map<std::string, PortSettings> PortSettingsMap;
|
||||
|
||||
explicit Impl(SerialPortDialog* self) :
|
||||
self_ {self},
|
||||
model_ {new QStandardItemModel(self)},
|
||||
proxyModel_ {new QSortFilterProxyModel(self)}
|
||||
{
|
||||
}
|
||||
~Impl() = default;
|
||||
|
||||
void LogSerialPortInfo(const QSerialPortInfo& info);
|
||||
void RefreshSerialDevices();
|
||||
void UpdateModel();
|
||||
|
||||
static void ReadComPortProperties(PortPropertiesMap& portPropertiesMap);
|
||||
static void ReadComPortSettings(PortSettingsMap& portSettingsMap);
|
||||
static void StorePortSettings(const std::string& portName,
|
||||
const std::string& settingsString,
|
||||
PortSettingsMap& portSettingsMap);
|
||||
|
||||
#if defined(_WIN32)
|
||||
static std::string GetDevicePropertyString(HDEVINFO& deviceInfoSet,
|
||||
SP_DEVINFO_DATA& deviceInfoData,
|
||||
DEVPROPKEY propertyKey);
|
||||
static std::string GetRegistryValueDataString(HKEY hKey, LPCTSTR lpValue);
|
||||
#endif
|
||||
|
||||
SerialPortDialog* self_;
|
||||
QStandardItemModel* model_;
|
||||
QSortFilterProxyModel* proxyModel_;
|
||||
|
||||
std::string selectedSerialPort_ {"?"};
|
||||
|
||||
PortInfoMap portInfoMap_ {};
|
||||
PortPropertiesMap portPropertiesMap_ {};
|
||||
PortSettingsMap portSettingsMap_ {};
|
||||
};
|
||||
|
||||
SerialPortDialog::SerialPortDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
p {std::make_unique<Impl>(this)},
|
||||
ui(new Ui::SerialPortDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
p->proxyModel_->setSourceModel(p->model_);
|
||||
ui->serialPortView->setModel(p->proxyModel_);
|
||||
ui->serialPortView->setEditTriggers(
|
||||
QAbstractItemView::EditTrigger::NoEditTriggers);
|
||||
ui->serialPortView->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
||||
|
||||
p->RefreshSerialDevices();
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
|
||||
->setEnabled(false);
|
||||
|
||||
connect(ui->refreshButton,
|
||||
&QAbstractButton::clicked,
|
||||
this,
|
||||
[this]() { p->RefreshSerialDevices(); });
|
||||
|
||||
connect(ui->serialPortView,
|
||||
&QTreeView::doubleClicked,
|
||||
this,
|
||||
[this]() { Q_EMIT accept(); });
|
||||
connect(
|
||||
ui->serialPortView->selectionModel(),
|
||||
&QItemSelectionModel::selectionChanged,
|
||||
this,
|
||||
[this](const QItemSelection& selected, const QItemSelection& deselected)
|
||||
{
|
||||
if (selected.size() == 0 && deselected.size() == 0)
|
||||
{
|
||||
// Items which stay selected but change their index are not
|
||||
// included in selected and deselected. Thus, this signal might
|
||||
// be emitted with both selected and deselected empty, if only
|
||||
// the indices of selected items change.
|
||||
return;
|
||||
}
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(selected.size() > 0);
|
||||
|
||||
if (selected.size() > 0)
|
||||
{
|
||||
QModelIndex selectedIndex =
|
||||
p->proxyModel_->mapToSource(selected[0].indexes()[0]);
|
||||
selectedIndex = p->model_->index(selectedIndex.row(), 0);
|
||||
QVariant variantData = p->model_->data(selectedIndex);
|
||||
if (variantData.typeId() == QMetaType::QString)
|
||||
{
|
||||
p->selectedSerialPort_ = variantData.toString().toStdString();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Unexpected selection data type");
|
||||
p->selectedSerialPort_ = std::string {"?"};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p->selectedSerialPort_ = std::string {"?"};
|
||||
}
|
||||
|
||||
logger_->debug("Selected: {}", p->selectedSerialPort_);
|
||||
});
|
||||
}
|
||||
|
||||
SerialPortDialog::~SerialPortDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
std::string SerialPortDialog::serial_port()
|
||||
{
|
||||
return p->selectedSerialPort_;
|
||||
}
|
||||
|
||||
int SerialPortDialog::baud_rate()
|
||||
{
|
||||
int baudRate = -1;
|
||||
|
||||
auto it = p->portSettingsMap_.find(p->selectedSerialPort_);
|
||||
if (it != p->portSettingsMap_.cend())
|
||||
{
|
||||
baudRate = it->second.baudRate_;
|
||||
}
|
||||
|
||||
return baudRate;
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::LogSerialPortInfo(const QSerialPortInfo& info)
|
||||
{
|
||||
logger_->trace("Serial Port: {}", info.portName().toStdString());
|
||||
logger_->trace(" Description: {}", info.description().toStdString());
|
||||
logger_->trace(" System Loc: {}", info.systemLocation().toStdString());
|
||||
logger_->trace(" Manufacturer: {}", info.manufacturer().toStdString());
|
||||
logger_->trace(" Vendor ID: {}", info.vendorIdentifier());
|
||||
logger_->trace(" Product ID: {}", info.productIdentifier());
|
||||
logger_->trace(" Serial No: {}", info.serialNumber().toStdString());
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::RefreshSerialDevices()
|
||||
{
|
||||
QList<QSerialPortInfo> availablePorts = QSerialPortInfo::availablePorts();
|
||||
|
||||
PortInfoMap newPortInfoMap {};
|
||||
PortPropertiesMap newPortPropertiesMap {};
|
||||
PortSettingsMap newPortSettingsMap {};
|
||||
|
||||
for (auto& port : availablePorts)
|
||||
{
|
||||
LogSerialPortInfo(port);
|
||||
newPortInfoMap.insert_or_assign(port.portName().toStdString(), port);
|
||||
}
|
||||
|
||||
ReadComPortProperties(newPortPropertiesMap);
|
||||
ReadComPortSettings(newPortSettingsMap);
|
||||
|
||||
portInfoMap_.swap(newPortInfoMap);
|
||||
portPropertiesMap_.swap(newPortPropertiesMap);
|
||||
portSettingsMap_.swap(newPortSettingsMap);
|
||||
|
||||
UpdateModel();
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::UpdateModel()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
static const QStringList headerLabels {
|
||||
tr("Port"), tr("Description"), tr("Device")};
|
||||
#else
|
||||
static const QStringList headerLabels {tr("Port"), tr("Description")};
|
||||
#endif
|
||||
|
||||
// Clear existing serial ports
|
||||
model_->clear();
|
||||
|
||||
// Reset selected serial port and disable OK button
|
||||
selectedSerialPort_ = std::string {"?"};
|
||||
self_->ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
|
||||
->setEnabled(false);
|
||||
|
||||
// Reset headers
|
||||
model_->setHorizontalHeaderLabels(headerLabels);
|
||||
|
||||
QStandardItem* root = model_->invisibleRootItem();
|
||||
|
||||
for (auto& port : portInfoMap_)
|
||||
{
|
||||
const QString portName = port.second.portName();
|
||||
const QString description = port.second.description();
|
||||
|
||||
#if defined(_WIN32)
|
||||
QString device {};
|
||||
|
||||
auto portPropertiesIt = portPropertiesMap_.find(port.first);
|
||||
if (portPropertiesIt != portPropertiesMap_.cend())
|
||||
{
|
||||
device = QString::fromStdString(
|
||||
portPropertiesIt->second.busReportedDeviceDescription_);
|
||||
}
|
||||
|
||||
root->appendRow({new QStandardItem(portName),
|
||||
new QStandardItem(description),
|
||||
new QStandardItem(device)});
|
||||
#else
|
||||
root->appendRow(
|
||||
{new QStandardItem(portName), new QStandardItem(description)});
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int column = 0; column < model_->columnCount(); column++)
|
||||
{
|
||||
self_->ui->serialPortView->resizeColumnToContents(column);
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::ReadComPortProperties(
|
||||
[[maybe_unused]] PortPropertiesMap& portPropertiesMap)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
GUID classGuid = GUID_DEVCLASS_PORTS;
|
||||
PCWSTR enumerator = nullptr;
|
||||
HWND hwndParent = nullptr;
|
||||
DWORD flags = DIGCF_PRESENT;
|
||||
HDEVINFO deviceInfoSet;
|
||||
|
||||
// Retrieve COM port devices
|
||||
deviceInfoSet =
|
||||
SetupDiGetClassDevs(&classGuid, enumerator, hwndParent, flags);
|
||||
if (deviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
logger_->error("Error getting COM port devices");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD memberIndex = 0;
|
||||
SP_DEVINFO_DATA deviceInfoData {};
|
||||
deviceInfoData.cbSize = sizeof(deviceInfoData);
|
||||
flags = 0;
|
||||
|
||||
// For each COM port device
|
||||
while (SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex++, &deviceInfoData))
|
||||
{
|
||||
DWORD scope = DICS_FLAG_GLOBAL;
|
||||
DWORD hwProfile = 0;
|
||||
DWORD keyType = DIREG_DEV;
|
||||
REGSAM samDesired = KEY_READ;
|
||||
HKEY devRegKey = SetupDiOpenDevRegKey(
|
||||
deviceInfoSet, &deviceInfoData, scope, hwProfile, keyType, samDesired);
|
||||
|
||||
if (devRegKey == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
logger_->error("Unable to open device registry key: {}",
|
||||
GetLastError());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read Port Name and Device Description
|
||||
std::string portName =
|
||||
GetRegistryValueDataString(devRegKey, TEXT("PortName"));
|
||||
|
||||
if (portName.empty())
|
||||
{
|
||||
// Ignore device without port name
|
||||
continue;
|
||||
}
|
||||
|
||||
PortProperties properties {};
|
||||
properties.busReportedDeviceDescription_ = GetDevicePropertyString(
|
||||
deviceInfoSet, deviceInfoData, DEVPKEY_Device_BusReportedDeviceDesc);
|
||||
|
||||
logger_->trace(
|
||||
"Port: {} ({})", portName, properties.busReportedDeviceDescription_);
|
||||
|
||||
portPropertiesMap.emplace(portName, std::move(properties));
|
||||
|
||||
RegCloseKey(devRegKey);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::ReadComPortSettings(
|
||||
[[maybe_unused]] PortSettingsMap& portSettingsMap)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
const LPCTSTR lpSubKey =
|
||||
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports");
|
||||
DWORD ulOptions = 0;
|
||||
REGSAM samDesired = KEY_READ;
|
||||
HKEY hkResult;
|
||||
LSTATUS status;
|
||||
|
||||
// Open Port Settings Key
|
||||
status = RegOpenKeyEx(
|
||||
HKEY_LOCAL_MACHINE, lpSubKey, ulOptions, samDesired, &hkResult);
|
||||
|
||||
if (status == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD dwIndex = 0;
|
||||
TCHAR valueName[MAX_PATH];
|
||||
LPDWORD lpReserved = nullptr;
|
||||
DWORD type;
|
||||
TCHAR valueData[64];
|
||||
char buffer[MAX_PATH]; // Buffer for string conversion
|
||||
|
||||
// Number of characters, not including terminating null
|
||||
static constexpr DWORD maxValueNameSize =
|
||||
sizeof(valueName) / sizeof(TCHAR) - 1;
|
||||
DWORD valueNameSize = maxValueNameSize;
|
||||
|
||||
// Number of bytes
|
||||
DWORD valueDataSize = sizeof(valueData);
|
||||
|
||||
static constexpr std::size_t bufferSize = sizeof(buffer);
|
||||
|
||||
// Enumerate each port value
|
||||
while ((status = RegEnumValue(hkResult,
|
||||
dwIndex++,
|
||||
valueName,
|
||||
&valueNameSize,
|
||||
lpReserved,
|
||||
&type,
|
||||
reinterpret_cast<LPBYTE>(&valueData),
|
||||
&valueDataSize)) == ERROR_SUCCESS ||
|
||||
status == ERROR_MORE_DATA)
|
||||
{
|
||||
// Validate port value
|
||||
if (status == ERROR_SUCCESS && //
|
||||
type == REG_SZ && //
|
||||
valueNameSize >= 5 && // COM#:
|
||||
valueNameSize < sizeof(buffer) - 1 && // Strip off :
|
||||
valueDataSize > sizeof(TCHAR) && // Null character
|
||||
_tcsncmp(valueName, TEXT("COM"), 3) == 0)
|
||||
{
|
||||
errno_t error;
|
||||
std::size_t returnValue;
|
||||
|
||||
// Get port name
|
||||
if ((error = wcstombs_s(&returnValue,
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
valueName,
|
||||
valueNameSize - 1)) != 0)
|
||||
{
|
||||
logger_->error(
|
||||
"Error converting registry value name to string: {}",
|
||||
returnValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string portName = buffer;
|
||||
|
||||
// Get port data
|
||||
if ((error = wcstombs_s(&returnValue,
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
valueData,
|
||||
sizeof(buffer) - 1)) != 0)
|
||||
{
|
||||
logger_->error(
|
||||
"Error converting registry value data to string: {}",
|
||||
returnValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string portData = buffer;
|
||||
|
||||
logger_->trace("Port Settings: {} ({})", portName, portData);
|
||||
|
||||
StorePortSettings(portName, portData, portSettingsMap);
|
||||
}
|
||||
|
||||
valueNameSize = maxValueNameSize;
|
||||
valueDataSize = sizeof(valueData);
|
||||
}
|
||||
|
||||
RegCloseKey(hkResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->error("Could not open COM port settings registry key: {}",
|
||||
status);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialPortDialog::Impl::StorePortSettings(
|
||||
const std::string& portName,
|
||||
const std::string& settingsString,
|
||||
PortSettingsMap& portSettingsMap)
|
||||
{
|
||||
PortSettings portSettings {};
|
||||
|
||||
std::vector<std::string> tokenList =
|
||||
util::ParseTokens(settingsString, {",", ",", ",", ",", ","});
|
||||
|
||||
try
|
||||
{
|
||||
if (tokenList.size() >= 1)
|
||||
{
|
||||
// Positive integer
|
||||
portSettings.baudRate_ = std::stoi(tokenList.at(0));
|
||||
}
|
||||
if (tokenList.size() >= 2)
|
||||
{
|
||||
// [e]ven, [o]dd, [m]ark, [s]pace, [n]one
|
||||
portSettings.parity_ = tokenList.at(1);
|
||||
}
|
||||
if (tokenList.size() >= 3)
|
||||
{
|
||||
// [4-8]
|
||||
portSettings.dataBits_ = std::stoi(tokenList.at(2));
|
||||
}
|
||||
if (tokenList.size() >= 4)
|
||||
{
|
||||
// 1, 1.5, 2
|
||||
portSettings.stopBits_ = std::stof(tokenList.at(3));
|
||||
}
|
||||
if (tokenList.size() >= 5)
|
||||
{
|
||||
// "" (none), "p" (hardware), "x" (Xon / Xoff)
|
||||
portSettings.flowControl_ = tokenList.at(4);
|
||||
}
|
||||
|
||||
portSettingsMap.emplace(portName, std::move(portSettings));
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
logger_->error(
|
||||
"Could not parse {} port settings: {}", portName, settingsString);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::string
|
||||
SerialPortDialog::Impl::GetDevicePropertyString(HDEVINFO& deviceInfoSet,
|
||||
SP_DEVINFO_DATA& deviceInfoData,
|
||||
DEVPROPKEY propertyKey)
|
||||
{
|
||||
std::string devicePropertyString {};
|
||||
|
||||
DEVPROPTYPE propertyType = 0;
|
||||
std::vector<TCHAR> propertyBuffer {};
|
||||
DWORD requiredSize = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
BOOL status = SetupDiGetDeviceProperty(deviceInfoSet,
|
||||
&deviceInfoData,
|
||||
&propertyKey,
|
||||
&propertyType,
|
||||
nullptr,
|
||||
0,
|
||||
&requiredSize,
|
||||
flags);
|
||||
|
||||
if (requiredSize > 0)
|
||||
{
|
||||
propertyBuffer.reserve(requiredSize / sizeof(TCHAR));
|
||||
|
||||
status = SetupDiGetDeviceProperty(
|
||||
deviceInfoSet,
|
||||
&deviceInfoData,
|
||||
&propertyKey,
|
||||
&propertyType,
|
||||
reinterpret_cast<PBYTE>(propertyBuffer.data()),
|
||||
static_cast<DWORD>(propertyBuffer.capacity() * sizeof(TCHAR)),
|
||||
&requiredSize,
|
||||
flags);
|
||||
}
|
||||
|
||||
if (status && requiredSize > 0)
|
||||
{
|
||||
errno_t error;
|
||||
std::size_t returnValue;
|
||||
|
||||
devicePropertyString.resize(requiredSize / sizeof(TCHAR));
|
||||
|
||||
if ((error = wcstombs_s(&returnValue,
|
||||
devicePropertyString.data(),
|
||||
devicePropertyString.size(),
|
||||
propertyBuffer.data(),
|
||||
_TRUNCATE)) != 0)
|
||||
{
|
||||
logger_->error("Error converting device property string: {}",
|
||||
returnValue);
|
||||
devicePropertyString.clear();
|
||||
}
|
||||
else if (!devicePropertyString.empty())
|
||||
{
|
||||
// Remove trailing null
|
||||
devicePropertyString.erase(devicePropertyString.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return devicePropertyString;
|
||||
}
|
||||
|
||||
std::string SerialPortDialog::Impl::GetRegistryValueDataString(HKEY hKey,
|
||||
LPCTSTR lpValue)
|
||||
{
|
||||
std::string dataString {};
|
||||
|
||||
LPCTSTR lpSubKey = nullptr;
|
||||
DWORD dwFlags = RRF_RT_REG_SZ; // Restrict type to REG_SZ
|
||||
DWORD dwType;
|
||||
|
||||
std::vector<TCHAR> dataBuffer {};
|
||||
DWORD dataBufferSize = 0;
|
||||
|
||||
LSTATUS status = RegGetValue(
|
||||
hKey, lpSubKey, lpValue, dwFlags, &dwType, nullptr, &dataBufferSize);
|
||||
|
||||
if (status == ERROR_SUCCESS && dataBufferSize > 0)
|
||||
{
|
||||
dataBuffer.reserve(dataBufferSize / sizeof(TCHAR));
|
||||
|
||||
status = RegGetValue(hKey,
|
||||
lpSubKey,
|
||||
lpValue,
|
||||
dwFlags,
|
||||
&dwType,
|
||||
reinterpret_cast<PVOID>(dataBuffer.data()),
|
||||
&dataBufferSize);
|
||||
}
|
||||
|
||||
if (status == ERROR_SUCCESS && dataBufferSize > 0)
|
||||
{
|
||||
errno_t error;
|
||||
std::size_t returnValue;
|
||||
|
||||
dataString.resize(dataBufferSize / sizeof(TCHAR));
|
||||
|
||||
if ((error = wcstombs_s(&returnValue,
|
||||
dataString.data(),
|
||||
dataString.size(),
|
||||
dataBuffer.data(),
|
||||
_TRUNCATE)) != 0)
|
||||
{
|
||||
logger_->error("Error converting registry value data string: {}",
|
||||
returnValue);
|
||||
dataString.clear();
|
||||
}
|
||||
else if (!dataString.empty())
|
||||
{
|
||||
// Remove trailing null
|
||||
dataString.erase(dataString.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return dataString;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
36
scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp
Normal file
36
scwx-qt/source/scwx/qt/ui/serial_port_dialog.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class SerialPortDialog;
|
||||
}
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace ui
|
||||
{
|
||||
class SerialPortDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(SerialPortDialog)
|
||||
|
||||
public:
|
||||
explicit SerialPortDialog(QWidget* parent = nullptr);
|
||||
~SerialPortDialog();
|
||||
|
||||
std::string serial_port();
|
||||
int baud_rate();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
Ui::SerialPortDialog* ui;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
102
scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui
Normal file
102
scwx-qt/source/scwx/qt/ui/serial_port_dialog.ui
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SerialPortDialog</class>
|
||||
<widget class="QDialog" name="SerialPortDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Serial Port</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="serialPortView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshButton">
|
||||
<property name="text">
|
||||
<string>&Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SerialPortDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SerialPortDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
|
@ -4,6 +4,41 @@
|
|||
|
||||
#include <QWidget>
|
||||
|
||||
#define SCWX_ENUM_MAP_FROM_VALUE(Iterator, ToName) \
|
||||
[](const std::string& text) -> std::string \
|
||||
{ \
|
||||
for (auto enumValue : Iterator) \
|
||||
{ \
|
||||
const std::string enumName = ToName(enumValue); \
|
||||
\
|
||||
if (boost::iequals(text, enumName)) \
|
||||
{ \
|
||||
/* Return label */ \
|
||||
return enumName; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* Label not found, return unknown */ \
|
||||
return "?"; \
|
||||
}
|
||||
|
||||
#define SCWX_SETTINGS_COMBO_BOX(settingsInterface, comboBox, Iterator, ToName) \
|
||||
for (const auto& enumValue : Iterator) \
|
||||
{ \
|
||||
comboBox->addItem(QString::fromStdString(ToName(enumValue))); \
|
||||
} \
|
||||
\
|
||||
settingsInterface.SetMapFromValueFunction( \
|
||||
SCWX_ENUM_MAP_FROM_VALUE(Iterator, ToName)); \
|
||||
settingsInterface.SetMapToValueFunction( \
|
||||
[](std::string text) -> std::string \
|
||||
{ \
|
||||
boost::to_lower(text); \
|
||||
return text; \
|
||||
}); \
|
||||
\
|
||||
settingsInterface.SetEditWidget(comboBox);
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
|
|||
|
|
@ -12,36 +12,6 @@
|
|||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#define SCWX_SETTINGS_COMBO_BOX(settingsInterface, comboBox, Iterator, ToName) \
|
||||
for (const auto& enumValue : Iterator) \
|
||||
{ \
|
||||
comboBox->addItem(QString::fromStdString(ToName(enumValue))); \
|
||||
} \
|
||||
\
|
||||
settingsInterface.SetMapFromValueFunction( \
|
||||
[](const std::string& text) -> std::string \
|
||||
{ \
|
||||
for (const auto& enumValue : Iterator) \
|
||||
{ \
|
||||
const std::string valueName = ToName(enumValue); \
|
||||
\
|
||||
if (boost::iequals(text, valueName)) \
|
||||
{ \
|
||||
return valueName; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
return "?"; \
|
||||
}); \
|
||||
settingsInterface.SetMapToValueFunction( \
|
||||
[](std::string text) -> std::string \
|
||||
{ \
|
||||
boost::to_lower(text); \
|
||||
return text; \
|
||||
}); \
|
||||
\
|
||||
settingsInterface.SetEditWidget(comboBox);
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <scwx/qt/types/time_types.hpp>
|
||||
#include <scwx/qt/ui/county_dialog.hpp>
|
||||
#include <scwx/qt/ui/radar_site_dialog.hpp>
|
||||
#include <scwx/qt/ui/serial_port_dialog.hpp>
|
||||
#include <scwx/qt/ui/settings/hotkey_settings_widget.hpp>
|
||||
#include <scwx/qt/ui/settings/unit_settings_widget.hpp>
|
||||
#include <scwx/qt/util/color.hpp>
|
||||
|
|
@ -96,30 +97,13 @@ static const std::unordered_map<std::string, ColorTableConversions>
|
|||
{"VIL", {0u, 255u, 1.0f, 2.5f}},
|
||||
{"???", {0u, 15u, 0.0f, 1.0f}}};
|
||||
|
||||
#define SCWX_ENUM_MAP_FROM_VALUE(Type, Iterator, ToName) \
|
||||
[](const std::string& text) -> std::string \
|
||||
{ \
|
||||
for (Type enumValue : Iterator) \
|
||||
{ \
|
||||
const std::string enumName = ToName(enumValue); \
|
||||
\
|
||||
if (boost::iequals(text, enumName)) \
|
||||
{ \
|
||||
/* Return label */ \
|
||||
return enumName; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* Label not found, return unknown */ \
|
||||
return "?"; \
|
||||
}
|
||||
|
||||
class SettingsDialogImpl
|
||||
{
|
||||
public:
|
||||
explicit SettingsDialogImpl(SettingsDialog* self) :
|
||||
self_ {self},
|
||||
radarSiteDialog_ {new RadarSiteDialog(self)},
|
||||
gpsSourceDialog_ {new SerialPortDialog(self)},
|
||||
countyDialog_ {new CountyDialog(self)},
|
||||
fontDialog_ {new QFontDialog(self)},
|
||||
fontCategoryModel_ {new QStandardItemModel(self)},
|
||||
|
|
@ -134,6 +118,9 @@ public:
|
|||
&defaultAlertAction_,
|
||||
&clockFormat_,
|
||||
&defaultTimeZone_,
|
||||
&positioningPlugin_,
|
||||
&nmeaBaudRate_,
|
||||
&nmeaSource_,
|
||||
&warningsProvider_,
|
||||
&antiAliasingEnabled_,
|
||||
&showMapAttribution_,
|
||||
|
|
@ -210,6 +197,7 @@ public:
|
|||
|
||||
SettingsDialog* self_;
|
||||
RadarSiteDialog* radarSiteDialog_;
|
||||
SerialPortDialog* gpsSourceDialog_;
|
||||
CountyDialog* countyDialog_;
|
||||
QFontDialog* fontDialog_;
|
||||
|
||||
|
|
@ -235,6 +223,9 @@ public:
|
|||
settings::SettingsInterface<std::string> defaultAlertAction_ {};
|
||||
settings::SettingsInterface<std::string> clockFormat_ {};
|
||||
settings::SettingsInterface<std::string> defaultTimeZone_ {};
|
||||
settings::SettingsInterface<std::string> positioningPlugin_ {};
|
||||
settings::SettingsInterface<std::int64_t> nmeaBaudRate_ {};
|
||||
settings::SettingsInterface<std::string> nmeaSource_ {};
|
||||
settings::SettingsInterface<std::string> theme_ {};
|
||||
settings::SettingsInterface<std::string> warningsProvider_ {};
|
||||
settings::SettingsInterface<bool> antiAliasingEnabled_ {};
|
||||
|
|
@ -344,6 +335,32 @@ void SettingsDialogImpl::ConnectSignals()
|
|||
}
|
||||
});
|
||||
|
||||
QObject::connect(self_->ui->gpsSourceSelectButton,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
[this]() { gpsSourceDialog_->show(); });
|
||||
|
||||
QObject::connect(gpsSourceDialog_,
|
||||
&SerialPortDialog::accepted,
|
||||
self_,
|
||||
[this]()
|
||||
{
|
||||
std::string serialPort = gpsSourceDialog_->serial_port();
|
||||
int baudRate = gpsSourceDialog_->baud_rate();
|
||||
|
||||
if (!serialPort.empty() && serialPort != "?")
|
||||
{
|
||||
std::string source =
|
||||
fmt::format("serial:{}", serialPort);
|
||||
nmeaSource_.StageValue(source);
|
||||
}
|
||||
|
||||
if (baudRate > 0)
|
||||
{
|
||||
self_->ui->nmeaBaudRateSpinBox->setValue(baudRate);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the Radar Site dialog "map" location with the currently selected
|
||||
// radar site
|
||||
auto& defaultRadarSite = *defaultRadarSite_.GetSettingsVariable();
|
||||
|
|
@ -467,38 +484,11 @@ void SettingsDialogImpl::SetupGeneralTab()
|
|||
settings::GeneralSettings& generalSettings =
|
||||
settings::GeneralSettings::Instance();
|
||||
|
||||
for (const auto& uiStyle : types::UiStyleIterator())
|
||||
{
|
||||
self_->ui->themeComboBox->addItem(
|
||||
QString::fromStdString(types::GetUiStyleName(uiStyle)));
|
||||
}
|
||||
|
||||
theme_.SetSettingsVariable(generalSettings.theme());
|
||||
theme_.SetMapFromValueFunction(
|
||||
[](const std::string& text) -> std::string
|
||||
{
|
||||
for (types::UiStyle uiStyle : types::UiStyleIterator())
|
||||
{
|
||||
const std::string uiStyleName = types::GetUiStyleName(uiStyle);
|
||||
|
||||
if (boost::iequals(text, uiStyleName))
|
||||
{
|
||||
// Return UI style label
|
||||
return uiStyleName;
|
||||
}
|
||||
}
|
||||
|
||||
// UI style label not found, return unknown
|
||||
return "?";
|
||||
});
|
||||
theme_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
theme_.SetEditWidget(self_->ui->themeComboBox);
|
||||
SCWX_SETTINGS_COMBO_BOX(theme_,
|
||||
self_->ui->themeComboBox,
|
||||
types::UiStyleIterator(),
|
||||
types::GetUiStyleName);
|
||||
theme_.SetResetButton(self_->ui->resetThemeButton);
|
||||
|
||||
auto radarSites = config::RadarSite::GetAll();
|
||||
|
|
@ -561,39 +551,11 @@ void SettingsDialogImpl::SetupGeneralTab()
|
|||
gridHeight_.SetEditWidget(self_->ui->gridHeightSpinBox);
|
||||
gridHeight_.SetResetButton(self_->ui->resetGridHeightButton);
|
||||
|
||||
for (const auto& mapProvider : map::MapProviderIterator())
|
||||
{
|
||||
self_->ui->mapProviderComboBox->addItem(
|
||||
QString::fromStdString(map::GetMapProviderName(mapProvider)));
|
||||
}
|
||||
|
||||
mapProvider_.SetSettingsVariable(generalSettings.map_provider());
|
||||
mapProvider_.SetMapFromValueFunction(
|
||||
[](const std::string& text) -> std::string
|
||||
{
|
||||
for (map::MapProvider mapProvider : map::MapProviderIterator())
|
||||
{
|
||||
const std::string mapProviderName =
|
||||
map::GetMapProviderName(mapProvider);
|
||||
|
||||
if (boost::iequals(text, mapProviderName))
|
||||
{
|
||||
// Return map provider label
|
||||
return mapProviderName;
|
||||
}
|
||||
}
|
||||
|
||||
// Map provider label not found, return unknown
|
||||
return "?";
|
||||
});
|
||||
mapProvider_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
mapProvider_.SetEditWidget(self_->ui->mapProviderComboBox);
|
||||
SCWX_SETTINGS_COMBO_BOX(mapProvider_,
|
||||
self_->ui->mapProviderComboBox,
|
||||
map::MapProviderIterator(),
|
||||
map::GetMapProviderName);
|
||||
mapProvider_.SetResetButton(self_->ui->resetMapProviderButton);
|
||||
|
||||
mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key());
|
||||
|
|
@ -604,70 +566,62 @@ void SettingsDialogImpl::SetupGeneralTab()
|
|||
mapTilerApiKey_.SetEditWidget(self_->ui->mapTilerApiKeyLineEdit);
|
||||
mapTilerApiKey_.SetResetButton(self_->ui->resetMapTilerApiKeyButton);
|
||||
|
||||
for (const auto& alertAction : types::AlertActionIterator())
|
||||
{
|
||||
self_->ui->defaultAlertActionComboBox->addItem(
|
||||
QString::fromStdString(types::GetAlertActionName(alertAction)));
|
||||
}
|
||||
|
||||
defaultAlertAction_.SetSettingsVariable(
|
||||
generalSettings.default_alert_action());
|
||||
defaultAlertAction_.SetMapFromValueFunction(
|
||||
SCWX_ENUM_MAP_FROM_VALUE(types::AlertAction,
|
||||
SCWX_SETTINGS_COMBO_BOX(defaultAlertAction_,
|
||||
self_->ui->defaultAlertActionComboBox,
|
||||
types::AlertActionIterator(),
|
||||
types::GetAlertActionName));
|
||||
defaultAlertAction_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
defaultAlertAction_.SetEditWidget(self_->ui->defaultAlertActionComboBox);
|
||||
types::GetAlertActionName);
|
||||
defaultAlertAction_.SetResetButton(self_->ui->resetDefaultAlertActionButton);
|
||||
|
||||
for (const auto& clockFormat : scwx::util::ClockFormatIterator())
|
||||
{
|
||||
self_->ui->clockFormatComboBox->addItem(
|
||||
QString::fromStdString(scwx::util::GetClockFormatName(clockFormat)));
|
||||
}
|
||||
|
||||
clockFormat_.SetSettingsVariable(generalSettings.clock_format());
|
||||
clockFormat_.SetMapFromValueFunction(
|
||||
SCWX_ENUM_MAP_FROM_VALUE(scwx::util::ClockFormat,
|
||||
SCWX_SETTINGS_COMBO_BOX(clockFormat_,
|
||||
self_->ui->clockFormatComboBox,
|
||||
scwx::util::ClockFormatIterator(),
|
||||
scwx::util::GetClockFormatName));
|
||||
clockFormat_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
clockFormat_.SetEditWidget(self_->ui->clockFormatComboBox);
|
||||
scwx::util::GetClockFormatName);
|
||||
clockFormat_.SetResetButton(self_->ui->resetClockFormatButton);
|
||||
|
||||
for (const auto& timeZone : types::DefaultTimeZoneIterator())
|
||||
{
|
||||
self_->ui->defaultTimeZoneComboBox->addItem(
|
||||
QString::fromStdString(types::GetDefaultTimeZoneName(timeZone)));
|
||||
}
|
||||
|
||||
defaultTimeZone_.SetSettingsVariable(generalSettings.default_time_zone());
|
||||
defaultTimeZone_.SetMapFromValueFunction(
|
||||
SCWX_ENUM_MAP_FROM_VALUE(types::DefaultTimeZone,
|
||||
SCWX_SETTINGS_COMBO_BOX(defaultTimeZone_,
|
||||
self_->ui->defaultTimeZoneComboBox,
|
||||
types::DefaultTimeZoneIterator(),
|
||||
types::GetDefaultTimeZoneName));
|
||||
defaultTimeZone_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
defaultTimeZone_.SetEditWidget(self_->ui->defaultTimeZoneComboBox);
|
||||
types::GetDefaultTimeZoneName);
|
||||
defaultTimeZone_.SetResetButton(self_->ui->resetDefaultTimeZoneButton);
|
||||
|
||||
QObject::connect(
|
||||
self_->ui->positioningPluginComboBox,
|
||||
&QComboBox::currentTextChanged,
|
||||
self_,
|
||||
[this](const QString& text)
|
||||
{
|
||||
types::PositioningPlugin positioningPlugin =
|
||||
types::GetPositioningPlugin(text.toStdString());
|
||||
|
||||
bool gpsSourceEnabled =
|
||||
positioningPlugin == types::PositioningPlugin::Nmea;
|
||||
|
||||
self_->ui->nmeaSourceLineEdit->setEnabled(gpsSourceEnabled);
|
||||
self_->ui->gpsSourceSelectButton->setEnabled(gpsSourceEnabled);
|
||||
self_->ui->nmeaBaudRateSpinBox->setEnabled(gpsSourceEnabled);
|
||||
self_->ui->resetNmeaSourceButton->setEnabled(gpsSourceEnabled);
|
||||
self_->ui->resetNmeaBaudRateButton->setEnabled(gpsSourceEnabled);
|
||||
});
|
||||
|
||||
positioningPlugin_.SetSettingsVariable(generalSettings.positioning_plugin());
|
||||
SCWX_SETTINGS_COMBO_BOX(positioningPlugin_,
|
||||
self_->ui->positioningPluginComboBox,
|
||||
types::PositioningPluginIterator(),
|
||||
types::GetPositioningPluginName);
|
||||
positioningPlugin_.SetResetButton(self_->ui->resetPositioningPluginButton);
|
||||
|
||||
nmeaBaudRate_.SetSettingsVariable(generalSettings.nmea_baud_rate());
|
||||
nmeaBaudRate_.SetEditWidget(self_->ui->nmeaBaudRateSpinBox);
|
||||
nmeaBaudRate_.SetResetButton(self_->ui->resetNmeaBaudRateButton);
|
||||
|
||||
nmeaSource_.SetSettingsVariable(generalSettings.nmea_source());
|
||||
nmeaSource_.SetEditWidget(self_->ui->nmeaSourceLineEdit);
|
||||
nmeaSource_.SetResetButton(self_->ui->resetNmeaSourceButton);
|
||||
|
||||
warningsProvider_.SetSettingsVariable(generalSettings.warnings_provider());
|
||||
warningsProvider_.SetEditWidget(self_->ui->warningsProviderLineEdit);
|
||||
warningsProvider_.SetResetButton(self_->ui->resetWarningsProviderButton);
|
||||
|
|
@ -991,27 +945,12 @@ void SettingsDialogImpl::SetupAudioTab()
|
|||
dialog->open();
|
||||
});
|
||||
|
||||
for (const auto& locationMethod : types::LocationMethodIterator())
|
||||
{
|
||||
self_->ui->alertAudioLocationMethodComboBox->addItem(
|
||||
QString::fromStdString(types::GetLocationMethodName(locationMethod)));
|
||||
}
|
||||
|
||||
alertAudioLocationMethod_.SetSettingsVariable(
|
||||
audioSettings.alert_location_method());
|
||||
alertAudioLocationMethod_.SetMapFromValueFunction(
|
||||
SCWX_ENUM_MAP_FROM_VALUE(types::LocationMethod,
|
||||
SCWX_SETTINGS_COMBO_BOX(alertAudioLocationMethod_,
|
||||
self_->ui->alertAudioLocationMethodComboBox,
|
||||
types::LocationMethodIterator(),
|
||||
types::GetLocationMethodName));
|
||||
alertAudioLocationMethod_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
alertAudioLocationMethod_.SetEditWidget(
|
||||
self_->ui->alertAudioLocationMethodComboBox);
|
||||
types::GetLocationMethodName);
|
||||
alertAudioLocationMethod_.SetResetButton(
|
||||
self_->ui->resetAlertAudioLocationMethodButton);
|
||||
|
||||
|
|
@ -1159,40 +1098,11 @@ void SettingsDialogImpl::SetupTextTab()
|
|||
hoverTextWrap_.SetEditWidget(self_->ui->hoverTextWrapSpinBox);
|
||||
hoverTextWrap_.SetResetButton(self_->ui->resetHoverTextWrapButton);
|
||||
|
||||
for (const auto& tooltipMethod : types::TooltipMethodIterator())
|
||||
{
|
||||
self_->ui->tooltipMethodComboBox->addItem(
|
||||
QString::fromStdString(types::GetTooltipMethodName(tooltipMethod)));
|
||||
}
|
||||
|
||||
tooltipMethod_.SetSettingsVariable(textSettings.tooltip_method());
|
||||
tooltipMethod_.SetMapFromValueFunction(
|
||||
[](const std::string& text) -> std::string
|
||||
{
|
||||
for (types::TooltipMethod tooltipMethod :
|
||||
types::TooltipMethodIterator())
|
||||
{
|
||||
const std::string tooltipMethodName =
|
||||
types::GetTooltipMethodName(tooltipMethod);
|
||||
|
||||
if (boost::iequals(text, tooltipMethodName))
|
||||
{
|
||||
// Return tooltip method label
|
||||
return tooltipMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltip method label not found, return unknown
|
||||
return "?";
|
||||
});
|
||||
tooltipMethod_.SetMapToValueFunction(
|
||||
[](std::string text) -> std::string
|
||||
{
|
||||
// Convert label to lower case and return
|
||||
boost::to_lower(text);
|
||||
return text;
|
||||
});
|
||||
tooltipMethod_.SetEditWidget(self_->ui->tooltipMethodComboBox);
|
||||
SCWX_SETTINGS_COMBO_BOX(tooltipMethod_,
|
||||
self_->ui->tooltipMethodComboBox,
|
||||
types::TooltipMethodIterator(),
|
||||
types::GetTooltipMethodName);
|
||||
tooltipMethod_.SetResetButton(self_->ui->resetTooltipMethodButton);
|
||||
|
||||
placefileTextDropShadowEnabled_.SetSettingsVariable(
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QListWidget" name="listWidget">
|
||||
<property name="sizePolicy">
|
||||
|
|
@ -48,13 +48,13 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
<enum>QListView::ResizeMode::Adjust</enum>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::ListMode</enum>
|
||||
<enum>QListView::ViewMode::ListMode</enum>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
|
|
@ -137,14 +137,14 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>513</width>
|
||||
<height>482</height>
|
||||
<height>566</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_2">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -159,93 +159,14 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="8" column="2">
|
||||
<widget class="QComboBox" name="defaultAlertActionComboBox"/>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>MapTiler API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="4">
|
||||
<widget class="QToolButton" name="resetMapboxApiKeyButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Mapbox API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="4">
|
||||
<widget class="QToolButton" name="resetClockFormatButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Default Time Zone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="4">
|
||||
<widget class="QToolButton" name="resetDefaultAlertActionButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Clock Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<widget class="QToolButton" name="resetGridWidthButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QToolButton" name="resetRadarSiteButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Grid Height</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QToolButton" name="resetGridHeightButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
|
|
@ -257,23 +178,17 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="11" column="2">
|
||||
<widget class="QLineEdit" name="warningsProviderLineEdit"/>
|
||||
<widget class="QComboBox" name="mapProviderComboBox"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="themeComboBox"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Warnings Provider</string>
|
||||
<string>Default Time Zone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="gridWidthSpinBox"/>
|
||||
</item>
|
||||
<item row="5" column="4">
|
||||
<widget class="QToolButton" name="resetMapProviderButton">
|
||||
<item row="12" column="4">
|
||||
<widget class="QToolButton" name="resetMapboxApiKeyButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
|
|
@ -284,106 +199,9 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QLineEdit" name="mapboxApiKeyLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QToolButton" name="resetThemeButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="gridHeightSpinBox"/>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QComboBox" name="clockFormatComboBox"/>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<widget class="QComboBox" name="defaultTimeZoneComboBox"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QToolButton" name="radarSiteSelectButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>MapTiler API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Map Provider</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="defaultAlertActionLabel">
|
||||
<property name="text">
|
||||
<string>Default Alert Action</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Grid Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Default Radar Site</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QComboBox" name="mapProviderComboBox"/>
|
||||
</item>
|
||||
<item row="7" column="4">
|
||||
<widget class="QToolButton" name="resetMapTilerApiKeyButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="radarSiteComboBox"/>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<widget class="QLineEdit" name="mapTilerApiKeyLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="4">
|
||||
<item row="4" column="4">
|
||||
<widget class="QToolButton" name="resetDefaultTimeZoneButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
|
|
@ -394,7 +212,72 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="4">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Map Provider</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QComboBox" name="defaultTimeZoneComboBox"/>
|
||||
</item>
|
||||
<item row="18" column="4">
|
||||
<widget class="QToolButton" name="resetThemeButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="clockFormatComboBox"/>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="radarSiteComboBox"/>
|
||||
</item>
|
||||
<item row="9" column="3">
|
||||
<widget class="QToolButton" name="gpsSourceSelectButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="2">
|
||||
<widget class="QLineEdit" name="mapboxApiKeyLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::EchoMode::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>GPS Plugin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>GPS Baud Rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QSpinBox" name="gridWidthSpinBox"/>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QToolButton" name="radarSiteSelectButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="4">
|
||||
<widget class="QToolButton" name="resetWarningsProviderButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
|
|
@ -405,6 +288,200 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Grid Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QLineEdit" name="nmeaSourceLineEdit"/>
|
||||
</item>
|
||||
<item row="18" column="2">
|
||||
<widget class="QComboBox" name="themeComboBox"/>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QComboBox" name="positioningPluginComboBox"/>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>GPS Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="21" column="2">
|
||||
<widget class="QLineEdit" name="warningsProviderLineEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Default Radar Site</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QToolButton" name="resetRadarSiteButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Grid Height</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<widget class="QToolButton" name="resetDefaultAlertActionButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QToolButton" name="resetClockFormatButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Mapbox API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Clock Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="2">
|
||||
<widget class="QLineEdit" name="mapTilerApiKeyLineEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::EchoMode::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="4">
|
||||
<widget class="QToolButton" name="resetGridWidthButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="4">
|
||||
<widget class="QToolButton" name="resetMapTilerApiKeyButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="defaultAlertActionComboBox"/>
|
||||
</item>
|
||||
<item row="21" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Warnings Provider</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<widget class="QSpinBox" name="nmeaBaudRateSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="defaultAlertActionLabel">
|
||||
<property name="text">
|
||||
<string>Default Alert Action</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="4">
|
||||
<widget class="QToolButton" name="resetMapProviderButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="4">
|
||||
<widget class="QToolButton" name="resetPositioningPluginButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="4">
|
||||
<widget class="QToolButton" name="resetNmeaSourceButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="4">
|
||||
<widget class="QToolButton" name="resetNmeaBaudRateButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -453,7 +530,7 @@
|
|||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -491,15 +568,15 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>506</width>
|
||||
<height>383</height>
|
||||
<width>63</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -523,10 +600,10 @@
|
|||
<item>
|
||||
<widget class="QFrame" name="alertsFrame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -547,7 +624,7 @@
|
|||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -769,7 +846,7 @@
|
|||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -786,19 +863,19 @@
|
|||
<item>
|
||||
<widget class="QFrame" name="frame_4">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
<enum>QFrame::Shadow::Plain</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_8" columnstretch="2,3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QFrame" name="frame_5">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -829,10 +906,10 @@
|
|||
<item row="0" column="1">
|
||||
<widget class="QFrame" name="frame_6">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -850,7 +927,7 @@
|
|||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -863,10 +940,10 @@
|
|||
<item row="7" column="0" colspan="5">
|
||||
<widget class="QFrame" name="frame_7">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
<enum>QFrame::Shape::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
<enum>QFrame::Shadow::Plain</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
|
|
@ -875,7 +952,7 @@
|
|||
<string>Tornado Warning expires in 15 minutes</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
|
@ -944,7 +1021,7 @@
|
|||
<item row="6" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -974,10 +1051,10 @@
|
|||
<item>
|
||||
<widget class="QFrame" name="frame_3">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<property name="leftMargin">
|
||||
|
|
@ -1058,7 +1135,7 @@
|
|||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -1080,10 +1157,10 @@
|
|||
<item row="1" column="0">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Discard|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
|
||||
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Discard|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit af115273844804d29c502b5ecbb94eee2b4b02f4
|
||||
Subproject commit 35e3e40d63bc020dfdc50c438c700c368fdf32fc
|
||||
Loading…
Add table
Add a link
Reference in a new issue