Merge pull request #210 from dpaulat/feature/nmea

NMEA GPS Support
This commit is contained in:
Dan Paulat 2024-05-19 02:00:49 -05:00 committed by GitHub
commit 85573d6103
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 2127 additions and 503 deletions

View file

@ -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

View file

@ -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 |

View 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

View file

@ -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

View file

@ -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>

View 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();

View file

@ -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,

View file

@ -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);

View file

@ -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 &amp;Sites</string>
</property>
</action>
<action name="actionGpsInfo">
<property name="text">
<string>&amp;GPS Info</string>
</property>
</action>
</widget>
<resources>
<include location="../../../../scwx-qt.qrc"/>

View file

@ -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,
self_,
[this](const QGeoPositionInfo& info)
{
auto coordinate = info.coordinate();
logger_->debug(
"Available sources: {}",
QGeoPositionInfoSource::availableSources().join(", ").toStdString());
if (coordinate != position_.coordinate())
{
logger_->debug("Position updated: {}, {}",
coordinate.latitude(),
coordinate.longitude());
}
CreatePositionSourceAsync();
position_ = info;
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; });
Q_EMIT self_->PositionUpdated(info);
});
}
connect(&SettingsManager::Instance(),
&SettingsManager::SettingsSaved,
self_,
[this]()
{
if (createPositionSourcePending_)
{
CreatePositionSourceAsync();
}
});
}
~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();
}
}
}

View 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

View 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

View file

@ -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(),
@ -128,13 +141,16 @@ public:
SettingsVariable<double> loopSpeed_ {"loop_speed"};
SettingsVariable<std::int64_t> loopTime_ {"loop_time"};
SettingsVariable<std::string> mapProvider_ {"map_provider"};
SettingsVariable<std::string> mapboxApiKey_ {"mapbox_api_key"};
SettingsVariable<std::string> maptilerApiKey_ {"maptiler_api_key"};
SettingsVariable<bool> showMapAttribution_ {"show_map_attribution"};
SettingsVariable<bool> showMapCenter_ {"show_map_center"};
SettingsVariable<bool> showMapLogo_ {"show_map_logo"};
SettingsVariable<std::string> theme_ {"theme"};
SettingsVariable<bool> trackLocation_ {"track_location"};
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"};
SettingsVariable<std::string> theme_ {"theme"};
SettingsVariable<bool> trackLocation_ {"track_location"};
SettingsVariable<bool> updateNotificationsEnabled_ {"update_notifications"};
SettingsVariable<std::string> warningsProvider_ {"warnings_provider"};
};
@ -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_ &&

View file

@ -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;

View file

@ -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

View file

@ -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

View 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

View 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

View 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>

View 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

View 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

View 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>&amp;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>

View file

@ -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

View file

@ -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

View file

@ -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_,
@ -208,10 +195,11 @@ public:
RadarSiteLabel(std::shared_ptr<config::RadarSite>& radarSite);
static void SetBackgroundColor(const std::string& value, QFrame* frame);
SettingsDialog* self_;
RadarSiteDialog* radarSiteDialog_;
CountyDialog* countyDialog_;
QFontDialog* fontDialog_;
SettingsDialog* self_;
RadarSiteDialog* radarSiteDialog_;
SerialPortDialog* gpsSourceDialog_;
CountyDialog* countyDialog_;
QFontDialog* fontDialog_;
QStandardItemModel* fontCategoryModel_;
@ -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,
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);
SCWX_SETTINGS_COMBO_BOX(defaultAlertAction_,
self_->ui->defaultAlertActionComboBox,
types::AlertActionIterator(),
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::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_SETTINGS_COMBO_BOX(clockFormat_,
self_->ui->clockFormatComboBox,
scwx::util::ClockFormatIterator(),
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,
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);
SCWX_SETTINGS_COMBO_BOX(defaultTimeZone_,
self_->ui->defaultTimeZoneComboBox,
types::DefaultTimeZoneIterator(),
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,
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);
SCWX_SETTINGS_COMBO_BOX(alertAudioLocationMethod_,
self_->ui->alertAudioLocationMethodComboBox,
types::LocationMethodIterator(),
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(

View file

@ -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