Merge pull request #188 from dpaulat/feature/keybinds

Hotkeys
This commit is contained in:
Dan Paulat 2024-04-18 00:28:48 -05:00 committed by GitHub
commit 83e3291db2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1466 additions and 68 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 112c-8.8 0-16 7.2-16 16V384c0 8.8 7.2 16 16 16H512c8.8 0 16-7.2 16-16V128c0-8.8-7.2-16-16-16H64zM0 128C0 92.7 28.7 64 64 64H512c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM176 320H400c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V336c0-8.8 7.2-16 16-16zm-72-72c0-8.8 7.2-16 16-16h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H120c-8.8 0-16-7.2-16-16V248zm16-96h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H120c-8.8 0-16-7.2-16-16V168c0-8.8 7.2-16 16-16zm64 96c0-8.8 7.2-16 16-16h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H200c-8.8 0-16-7.2-16-16V248zm16-96h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H200c-8.8 0-16-7.2-16-16V168c0-8.8 7.2-16 16-16zm64 96c0-8.8 7.2-16 16-16h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H280c-8.8 0-16-7.2-16-16V248zm16-96h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H280c-8.8 0-16-7.2-16-16V168c0-8.8 7.2-16 16-16zm64 96c0-8.8 7.2-16 16-16h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H360c-8.8 0-16-7.2-16-16V248zm16-96h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H360c-8.8 0-16-7.2-16-16V168c0-8.8 7.2-16 16-16zm64 96c0-8.8 7.2-16 16-16h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H440c-8.8 0-16-7.2-16-16V248zm16-96h16c8.8 0 16 7.2 16 16v16c0 8.8-7.2 16-16 16H440c-8.8 0-16-7.2-16-16V168c0-8.8 7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -88,6 +88,7 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp
set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
source/scwx/qt/manager/download_manager.hpp
source/scwx/qt/manager/font_manager.hpp
source/scwx/qt/manager/hotkey_manager.hpp
source/scwx/qt/manager/media_manager.hpp
source/scwx/qt/manager/placefile_manager.hpp
source/scwx/qt/manager/position_manager.hpp
@ -101,6 +102,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
source/scwx/qt/manager/download_manager.cpp
source/scwx/qt/manager/font_manager.cpp
source/scwx/qt/manager/hotkey_manager.cpp
source/scwx/qt/manager/media_manager.cpp
source/scwx/qt/manager/placefile_manager.cpp
source/scwx/qt/manager/position_manager.cpp
@ -162,6 +164,7 @@ set(SRC_REQUEST source/scwx/qt/request/download_request.cpp
source/scwx/qt/request/nexrad_file_request.cpp)
set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp
source/scwx/qt/settings/general_settings.hpp
source/scwx/qt/settings/hotkey_settings.hpp
source/scwx/qt/settings/map_settings.hpp
source/scwx/qt/settings/palette_settings.hpp
source/scwx/qt/settings/product_settings.hpp
@ -176,6 +179,7 @@ set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp
source/scwx/qt/settings/ui_settings.hpp)
set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp
source/scwx/qt/settings/general_settings.cpp
source/scwx/qt/settings/hotkey_settings.cpp
source/scwx/qt/settings/map_settings.cpp
source/scwx/qt/settings/palette_settings.cpp
source/scwx/qt/settings/product_settings.cpp
@ -191,6 +195,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
source/scwx/qt/types/event_types.hpp
source/scwx/qt/types/font_types.hpp
source/scwx/qt/types/github_types.hpp
source/scwx/qt/types/hotkey_types.hpp
source/scwx/qt/types/icon_types.hpp
source/scwx/qt/types/imgui_font.hpp
source/scwx/qt/types/layer_types.hpp
@ -205,6 +210,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
source/scwx/qt/types/time_types.hpp)
set(SRC_TYPES source/scwx/qt/types/alert_types.cpp
source/scwx/qt/types/github_types.cpp
source/scwx/qt/types/hotkey_types.cpp
source/scwx/qt/types/icon_types.cpp
source/scwx/qt/types/imgui_font.cpp
source/scwx/qt/types/layer_types.cpp
@ -225,6 +231,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/hotkey_edit.hpp
source/scwx/qt/ui/imgui_debug_dialog.hpp
source/scwx/qt/ui/imgui_debug_widget.hpp
source/scwx/qt/ui/layer_dialog.hpp
@ -247,6 +254,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/hotkey_edit.cpp
source/scwx/qt/ui/imgui_debug_dialog.cpp
source/scwx/qt/ui/imgui_debug_widget.cpp
source/scwx/qt/ui/layer_dialog.cpp
@ -276,6 +284,10 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui
source/scwx/qt/ui/radar_site_dialog.ui
source/scwx/qt/ui/settings_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)
set(SRC_UI_SETTINGS source/scwx/qt/ui/settings/hotkey_settings_widget.cpp
source/scwx/qt/ui/settings/settings_page_widget.cpp)
set(HDR_UI_SETUP source/scwx/qt/ui/setup/audio_codec_page.hpp
source/scwx/qt/ui/setup/finish_page.hpp
source/scwx/qt/ui/setup/map_layout_page.hpp
@ -387,6 +399,8 @@ set(PROJECT_SOURCES ${HDR_MAIN}
${HDR_UI}
${SRC_UI}
${UI_UI}
${HDR_UI_SETTINGS}
${SRC_UI_SETTINGS}
${HDR_UI_SETUP}
${SRC_UI_SETUP}
${HDR_UTIL}
@ -400,41 +414,43 @@ set(PROJECT_SOURCES ${HDR_MAIN}
${CMAKE_FILES})
set(EXECUTABLE_SOURCES ${SRC_EXE_MAIN})
source_group("Header Files\\main" FILES ${HDR_MAIN})
source_group("Source Files\\main" FILES ${SRC_MAIN})
source_group("Header Files\\config" FILES ${HDR_CONFIG})
source_group("Source Files\\config" FILES ${SRC_CONFIG})
source_group("Source Files\\external" FILES ${SRC_EXTERNAL})
source_group("Header Files\\gl" FILES ${HDR_GL})
source_group("Source Files\\gl" FILES ${SRC_GL})
source_group("Header Files\\gl\\draw" FILES ${HDR_GL_DRAW})
source_group("Source Files\\gl\\draw" FILES ${SRC_GL_DRAW})
source_group("Header Files\\manager" FILES ${HDR_MANAGER})
source_group("Source Files\\manager" FILES ${SRC_MANAGER})
source_group("UI Files\\main" FILES ${UI_MAIN})
source_group("Header Files\\map" FILES ${HDR_MAP})
source_group("Source Files\\map" FILES ${SRC_MAP})
source_group("Header Files\\model" FILES ${HDR_MODEL})
source_group("Source Files\\model" FILES ${SRC_MODEL})
source_group("Header Files\\request" FILES ${HDR_REQUEST})
source_group("Source Files\\request" FILES ${SRC_REQUEST})
source_group("Header Files\\settings" FILES ${HDR_SETTINGS})
source_group("Source Files\\settings" FILES ${SRC_SETTINGS})
source_group("Header Files\\types" FILES ${HDR_TYPES})
source_group("Source Files\\types" FILES ${SRC_TYPES})
source_group("Header Files\\ui" FILES ${HDR_UI})
source_group("Source Files\\ui" FILES ${SRC_UI})
source_group("Header Files\\ui\\setup" FILES ${HDR_UI_SETUP})
source_group("Source Files\\ui\\setup" FILES ${SRC_UI_SETUP})
source_group("UI Files\\ui" FILES ${UI_UI})
source_group("Header Files\\util" FILES ${HDR_UTIL})
source_group("Source Files\\util" FILES ${SRC_UTIL})
source_group("Header Files\\view" FILES ${HDR_VIEW})
source_group("Source Files\\view" FILES ${SRC_VIEW})
source_group("OpenGL Shaders" FILES ${SHADER_FILES})
source_group("Resources" FILES ${RESOURCE_FILES})
source_group("Resources\\json" FILES ${JSON_FILES})
source_group("I18N Files" FILES ${TS_FILES})
source_group("Header Files\\main" FILES ${HDR_MAIN})
source_group("Source Files\\main" FILES ${SRC_MAIN})
source_group("Header Files\\config" FILES ${HDR_CONFIG})
source_group("Source Files\\config" FILES ${SRC_CONFIG})
source_group("Source Files\\external" FILES ${SRC_EXTERNAL})
source_group("Header Files\\gl" FILES ${HDR_GL})
source_group("Source Files\\gl" FILES ${SRC_GL})
source_group("Header Files\\gl\\draw" FILES ${HDR_GL_DRAW})
source_group("Source Files\\gl\\draw" FILES ${SRC_GL_DRAW})
source_group("Header Files\\manager" FILES ${HDR_MANAGER})
source_group("Source Files\\manager" FILES ${SRC_MANAGER})
source_group("UI Files\\main" FILES ${UI_MAIN})
source_group("Header Files\\map" FILES ${HDR_MAP})
source_group("Source Files\\map" FILES ${SRC_MAP})
source_group("Header Files\\model" FILES ${HDR_MODEL})
source_group("Source Files\\model" FILES ${SRC_MODEL})
source_group("Header Files\\request" FILES ${HDR_REQUEST})
source_group("Source Files\\request" FILES ${SRC_REQUEST})
source_group("Header Files\\settings" FILES ${HDR_SETTINGS})
source_group("Source Files\\settings" FILES ${SRC_SETTINGS})
source_group("Header Files\\types" FILES ${HDR_TYPES})
source_group("Source Files\\types" FILES ${SRC_TYPES})
source_group("Header Files\\ui" FILES ${HDR_UI})
source_group("Source Files\\ui" FILES ${SRC_UI})
source_group("Header Files\\ui\\settings" FILES ${HDR_UI_SETTINGS})
source_group("Source Files\\ui\\settings" FILES ${SRC_UI_SETTINGS})
source_group("Header Files\\ui\\setup" FILES ${HDR_UI_SETUP})
source_group("Source Files\\ui\\setup" FILES ${SRC_UI_SETUP})
source_group("UI Files\\ui" FILES ${UI_UI})
source_group("Header Files\\util" FILES ${HDR_UTIL})
source_group("Source Files\\util" FILES ${SRC_UTIL})
source_group("Header Files\\view" FILES ${HDR_VIEW})
source_group("Source Files\\view" FILES ${SRC_VIEW})
source_group("OpenGL Shaders" FILES ${SHADER_FILES})
source_group("Resources" FILES ${RESOURCE_FILES})
source_group("Resources\\json" FILES ${JSON_FILES})
source_group("I18N Files" FILES ${TS_FILES})
add_library(scwx-qt OBJECT ${PROJECT_SOURCES})
set_property(TARGET scwx-qt PROPERTY AUTOMOC ON)

View file

@ -39,6 +39,7 @@
<file>res/icons/font-awesome-6/gears-solid.svg</file>
<file>res/icons/font-awesome-6/github.svg</file>
<file>res/icons/font-awesome-6/house-solid.svg</file>
<file>res/icons/font-awesome-6/keyboard-regular.svg</file>
<file>res/icons/font-awesome-6/layer-group-solid.svg</file>
<file>res/icons/font-awesome-6/palette-solid.svg</file>
<file>res/icons/font-awesome-6/pause-solid.svg</file>

View file

@ -4,6 +4,7 @@
#include <scwx/qt/main/application.hpp>
#include <scwx/qt/main/versions.hpp>
#include <scwx/qt/manager/alert_manager.hpp>
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/qt/manager/placefile_manager.hpp>
#include <scwx/qt/manager/position_manager.hpp>
#include <scwx/qt/manager/radar_product_manager.hpp>
@ -42,6 +43,7 @@
#include <boost/asio/post.hpp>
#include <boost/asio/thread_pool.hpp>
#include <QDesktopServices>
#include <QKeyEvent>
#include <QFileDialog>
#include <QMessageBox>
#include <QSplitter>
@ -197,7 +199,9 @@ public:
QTimer clockTimer_ {};
std::shared_ptr<manager::AlertManager> alertManager_;
std::shared_ptr<manager::AlertManager> alertManager_;
std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
manager::HotkeyManager::Instance()};
std::shared_ptr<manager::PlacefileManager> placefileManager_;
std::shared_ptr<manager::PositionManager> positionManager_;
std::shared_ptr<manager::TextEventManager> textEventManager_;
@ -398,6 +402,24 @@ MainWindow::~MainWindow()
delete ui;
}
void MainWindow::keyPressEvent(QKeyEvent* ev)
{
if (p->hotkeyManager_->HandleKeyPress(ev))
{
p->activeMap_->update();
ev->accept();
}
}
void MainWindow::keyReleaseEvent(QKeyEvent* ev)
{
if (p->hotkeyManager_->HandleKeyRelease(ev))
{
p->activeMap_->update();
ev->accept();
}
}
void MainWindow::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);

View file

@ -26,6 +26,8 @@ public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
void keyPressEvent(QKeyEvent* ev) override final;
void keyReleaseEvent(QKeyEvent* ev) override final;
void showEvent(QShowEvent* event) override;
signals:

View file

@ -0,0 +1,128 @@
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/qt/settings/hotkey_settings.hpp>
#include <scwx/util/logger.hpp>
#include <vector>
#include <boost/container/flat_map.hpp>
#include <QKeyEvent>
#include <QKeySequence>
namespace scwx
{
namespace qt
{
namespace manager
{
static const std::string logPrefix_ = "scwx::qt::manager::hotkey_manager";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
class HotkeyManager::Impl
{
public:
explicit Impl()
{
auto& hotkeySettings = settings::HotkeySettings::Instance();
for (auto hotkey : types::HotkeyIterator())
{
auto& hotkeyVariable = hotkeySettings.hotkey(hotkey);
UpdateHotkey(hotkey, hotkeyVariable.GetValue());
callbacks_.emplace_back(hotkeyVariable,
hotkeyVariable.RegisterValueChangedCallback(
[this, hotkey](const std::string& value)
{ UpdateHotkey(hotkey, value); }));
}
}
~Impl()
{
for (auto& callback : callbacks_)
{
callback.first.UnregisterValueChangedCallback(callback.second);
}
}
void UpdateHotkey(types::Hotkey hotkey, const std::string& value);
std::vector<
std::pair<settings::SettingsVariable<std::string>&, boost::uuids::uuid>>
callbacks_ {};
boost::container::flat_map<types::Hotkey, QKeySequence> hotkeys_ {};
};
HotkeyManager::HotkeyManager() : p(std::make_unique<Impl>()) {}
HotkeyManager::~HotkeyManager() = default;
void HotkeyManager::Impl::UpdateHotkey(types::Hotkey hotkey,
const std::string& value)
{
hotkeys_.insert_or_assign(hotkey,
QKeySequence {QString::fromStdString(value)});
}
bool HotkeyManager::HandleKeyPress(QKeyEvent* ev)
{
logger_->trace("HandleKeyPress: {}, {}",
ev->keyCombination().toCombined(),
ev->isAutoRepeat());
bool hotkeyPressed = false;
for (auto& hotkey : p->hotkeys_)
{
if (hotkey.second.count() == 1 &&
hotkey.second[0] == ev->keyCombination())
{
hotkeyPressed = true;
Q_EMIT HotkeyPressed(hotkey.first, ev->isAutoRepeat());
}
}
return hotkeyPressed;
}
bool HotkeyManager::HandleKeyRelease(QKeyEvent* ev)
{
logger_->trace("HandleKeyRelease: {}", ev->keyCombination().toCombined());
bool hotkeyReleased = false;
for (auto& hotkey : p->hotkeys_)
{
if (hotkey.second.count() == 1 &&
hotkey.second[0] == ev->keyCombination())
{
hotkeyReleased = true;
Q_EMIT HotkeyReleased(hotkey.first);
}
}
return hotkeyReleased;
}
std::shared_ptr<HotkeyManager> HotkeyManager::Instance()
{
static std::weak_ptr<HotkeyManager> hotkeyManagerReference_ {};
static std::mutex instanceMutex_ {};
std::unique_lock lock(instanceMutex_);
std::shared_ptr<HotkeyManager> hotkeyManager =
hotkeyManagerReference_.lock();
if (hotkeyManager == nullptr)
{
hotkeyManager = std::make_shared<HotkeyManager>();
hotkeyManagerReference_ = hotkeyManager;
}
return hotkeyManager;
}
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,43 @@
#pragma once
#include <scwx/qt/types/hotkey_types.hpp>
#include <memory>
#include <QObject>
class QKeyEvent;
namespace scwx
{
namespace qt
{
namespace manager
{
class HotkeyManager : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(HotkeyManager)
public:
explicit HotkeyManager();
~HotkeyManager();
bool HandleKeyPress(QKeyEvent* event);
bool HandleKeyRelease(QKeyEvent* event);
static std::shared_ptr<HotkeyManager> Instance();
signals:
void HotkeyPressed(scwx::qt::types::Hotkey hotkey, bool isAutoRepeat);
void HotkeyReleased(scwx::qt::types::Hotkey hotkey);
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -2,6 +2,7 @@
#include <scwx/qt/map/map_provider.hpp>
#include <scwx/qt/settings/audio_settings.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/settings/hotkey_settings.hpp>
#include <scwx/qt/settings/map_settings.hpp>
#include <scwx/qt/settings/palette_settings.hpp>
#include <scwx/qt/settings/product_settings.hpp>
@ -132,6 +133,7 @@ boost::json::value SettingsManager::Impl::ConvertSettingsToJson()
settings::GeneralSettings::Instance().WriteJson(settingsJson);
settings::AudioSettings::Instance().WriteJson(settingsJson);
settings::HotkeySettings::Instance().WriteJson(settingsJson);
settings::MapSettings::Instance().WriteJson(settingsJson);
settings::PaletteSettings::Instance().WriteJson(settingsJson);
settings::ProductSettings::Instance().WriteJson(settingsJson);
@ -147,6 +149,7 @@ void SettingsManager::Impl::GenerateDefaultSettings()
settings::GeneralSettings::Instance().SetDefaults();
settings::AudioSettings::Instance().SetDefaults();
settings::HotkeySettings::Instance().SetDefaults();
settings::MapSettings::Instance().SetDefaults();
settings::PaletteSettings::Instance().SetDefaults();
settings::ProductSettings::Instance().SetDefaults();
@ -163,6 +166,7 @@ bool SettingsManager::Impl::LoadSettings(
jsonDirty |= !settings::GeneralSettings::Instance().ReadJson(settingsJson);
jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson);
jsonDirty |= !settings::HotkeySettings::Instance().ReadJson(settingsJson);
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
jsonDirty |= !settings::ProductSettings::Instance().ReadJson(settingsJson);

View file

@ -1,6 +1,7 @@
#include <scwx/qt/map/map_widget.hpp>
#include <scwx/qt/gl/gl.hpp>
#include <scwx/qt/manager/font_manager.hpp>
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/qt/manager/placefile_manager.hpp>
#include <scwx/qt/manager/radar_product_manager.hpp>
#include <scwx/qt/map/alert_layer.hpp>
@ -39,6 +40,7 @@
#include <imgui.h>
#include <re2/re2.h>
#include <QApplication>
#include <QClipboard>
#include <QColor>
#include <QDebug>
#include <QFile>
@ -147,6 +149,9 @@ public:
const std::string& before);
void ConnectMapSignals();
void ConnectSignals();
void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat);
void HandleHotkeyReleased(types::Hotkey hotkey);
void HandleHotkeyUpdates();
void ImGuiCheckFonts();
void InitializeNewRadarProductView(const std::string& colorPalette);
void RadarProductManagerConnect();
@ -190,6 +195,8 @@ public:
std::shared_ptr<model::LayerModel> layerModel_ {
model::LayerModel::Instance()};
std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
manager::HotkeyManager::Instance()};
std::shared_ptr<manager::PlacefileManager> placefileManager_ {
manager::PlacefileManager::Instance()};
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
@ -227,6 +234,9 @@ public:
double prevBearing_;
double prevPitch_;
std::set<types::Hotkey> activeHotkeys_ {};
std::chrono::system_clock::time_point prevHotkeyTime_ {};
public slots:
void Update();
};
@ -331,6 +341,160 @@ void MapWidgetImpl::ConnectSignals()
[this](const QModelIndex& /* parent */, //
int /* first */,
int /* last */) { AddLayers(); });
connect(hotkeyManager_.get(),
&manager::HotkeyManager::HotkeyPressed,
this,
&MapWidgetImpl::HandleHotkeyPressed);
connect(hotkeyManager_.get(),
&manager::HotkeyManager::HotkeyReleased,
this,
&MapWidgetImpl::HandleHotkeyReleased);
}
void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat)
{
Q_UNUSED(isAutoRepeat);
switch (hotkey)
{
case types::Hotkey::ChangeMapStyle:
if (context_->settings().isActive_)
{
widget_->changeStyle();
}
break;
case types::Hotkey::CopyCursorCoordinates:
if (hasMouse_)
{
QClipboard* clipboard = QGuiApplication::clipboard();
auto coordinate = map_->coordinateForPixel(lastPos_);
std::string text =
fmt::format("{}, {}", coordinate.first, coordinate.second);
clipboard->setText(QString::fromStdString(text));
}
break;
case types::Hotkey::CopyMapCoordinates:
if (context_->settings().isActive_)
{
QClipboard* clipboard = QGuiApplication::clipboard();
auto coordinate = map_->coordinate();
std::string text =
fmt::format("{}, {}", coordinate.first, coordinate.second);
clipboard->setText(QString::fromStdString(text));
}
break;
default:
break;
}
activeHotkeys_.insert(hotkey);
}
void MapWidgetImpl::HandleHotkeyReleased(types::Hotkey hotkey)
{
// Erase the hotkey from the active set regardless of whether this is the
// active map
activeHotkeys_.erase(hotkey);
}
void MapWidgetImpl::HandleHotkeyUpdates()
{
using namespace std::chrono_literals;
static constexpr float kMapPanFactor = 0.2f;
static constexpr float kMapRotateFactor = 0.2f;
static constexpr double kMapScaleFactor = 1000.0;
std::chrono::system_clock::time_point hotkeyTime =
std::chrono::system_clock::now();
std::chrono::milliseconds hotkeyElapsed =
std::min(std::chrono::duration_cast<std::chrono::milliseconds>(
hotkeyTime - prevHotkeyTime_),
100ms);
prevHotkeyTime_ = hotkeyTime;
if (!context_->settings().isActive_)
{
// Don't attempt to handle a hotkey if this is not the active map
return;
}
for (auto& hotkey : activeHotkeys_)
{
switch (hotkey)
{
case types::Hotkey::MapPanUp:
{
QPointF delta {0.0f, kMapPanFactor * hotkeyElapsed.count()};
map_->moveBy(delta);
break;
}
case types::Hotkey::MapPanDown:
{
QPointF delta {0.0f, -kMapPanFactor * hotkeyElapsed.count()};
map_->moveBy(delta);
break;
}
case types::Hotkey::MapPanLeft:
{
QPointF delta {kMapPanFactor * hotkeyElapsed.count(), 0.0f};
map_->moveBy(delta);
break;
}
case types::Hotkey::MapPanRight:
{
QPointF delta {-kMapPanFactor * hotkeyElapsed.count(), 0.0f};
map_->moveBy(delta);
break;
}
case types::Hotkey::MapRotateClockwise:
{
QPointF delta {-kMapRotateFactor * hotkeyElapsed.count(), 0.0f};
map_->rotateBy({}, delta);
break;
}
case types::Hotkey::MapRotateCounterclockwise:
{
QPointF delta {kMapRotateFactor * hotkeyElapsed.count(), 0.0f};
map_->rotateBy({}, delta);
break;
}
case types::Hotkey::MapZoomIn:
{
auto widgetSize = widget_->size();
QPointF center = {widgetSize.width() * 0.5f,
widgetSize.height() * 0.5f};
double scale = std::pow(2.0, hotkeyElapsed.count() / kMapScaleFactor);
map_->scaleBy(scale, center);
break;
}
case types::Hotkey::MapZoomOut:
{
auto widgetSize = widget_->size();
QPointF center = {widgetSize.width() * 0.5f,
widgetSize.height() * 0.5f};
double scale =
1.0 / std::pow(2.0, hotkeyElapsed.count() / kMapScaleFactor);
map_->scaleBy(scale, center);
break;
}
default:
break;
}
}
}
common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories()
@ -1061,16 +1225,18 @@ void MapWidget::leaveEvent(QEvent* /* ev */)
void MapWidget::keyPressEvent(QKeyEvent* ev)
{
switch (ev->key())
if (p->hotkeyManager_->HandleKeyPress(ev))
{
case Qt::Key_S:
changeStyle();
break;
default:
break;
ev->accept();
}
}
ev->accept();
void MapWidget::keyReleaseEvent(QKeyEvent* ev)
{
if (p->hotkeyManager_->HandleKeyRelease(ev))
{
ev->accept();
}
}
void MapWidget::mousePressEvent(QMouseEvent* ev)
@ -1197,6 +1363,9 @@ void MapWidget::paintGL()
p->frameDraws_++;
// Handle hotkey updates
p->HandleHotkeyUpdates();
// Setup ImGui Frame
ImGui::SetCurrentContext(p->imGuiContext_);

View file

@ -125,6 +125,7 @@ private:
bool event(QEvent* e) override;
void enterEvent(QEnterEvent* ev) override final;
void keyPressEvent(QKeyEvent* ev) override final;
void keyReleaseEvent(QKeyEvent* ev) override final;
void leaveEvent(QEvent* ev) override final;
void mousePressEvent(QMouseEvent* ev) override final;
void mouseMoveEvent(QMouseEvent* ev) override final;

View file

@ -0,0 +1,126 @@
#include <scwx/qt/settings/hotkey_settings.hpp>
#include <QKeySequence>
namespace scwx
{
namespace qt
{
namespace settings
{
static const std::string logPrefix_ = "scwx::qt::settings::hotkey_settings";
static const std::unordered_map<types::Hotkey, QKeySequence> kDefaultHotkeys_ {
{types::Hotkey::ChangeMapStyle, QKeySequence {Qt::Key::Key_Z}},
{types::Hotkey::CopyCursorCoordinates,
QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier,
Qt::Key::Key_C}}},
{types::Hotkey::CopyMapCoordinates,
QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier |
Qt::KeyboardModifier::ShiftModifier,
Qt::Key::Key_C}}},
{types::Hotkey::MapPanUp, QKeySequence {Qt::Key::Key_W}},
{types::Hotkey::MapPanDown, QKeySequence {Qt::Key::Key_S}},
{types::Hotkey::MapPanLeft, QKeySequence {Qt::Key::Key_A}},
{types::Hotkey::MapPanRight, QKeySequence {Qt::Key::Key_D}},
{types::Hotkey::MapRotateClockwise, QKeySequence {Qt::Key::Key_E}},
{types::Hotkey::MapRotateCounterclockwise, QKeySequence {Qt::Key::Key_Q}},
{types::Hotkey::MapZoomIn, QKeySequence {Qt::Key::Key_Equal}},
{types::Hotkey::MapZoomOut, QKeySequence {Qt::Key::Key_Minus}},
{types::Hotkey::ProductTiltDecrease,
QKeySequence {Qt::Key::Key_BracketLeft}},
{types::Hotkey::ProductTiltIncrease,
QKeySequence {Qt::Key::Key_BracketRight}},
{types::Hotkey::TimelineStepBegin,
QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier,
Qt::Key::Key_Left}}},
{types::Hotkey::TimelineStepBack, QKeySequence {Qt::Key::Key_Left}},
{types::Hotkey::TimelinePlay, QKeySequence {Qt::Key::Key_Space}},
{types::Hotkey::TimelineStepNext, QKeySequence {Qt::Key::Key_Right}},
{types::Hotkey::TimelineStepEnd,
QKeySequence {QKeyCombination {Qt::KeyboardModifier::ControlModifier,
Qt::Key::Key_Right}}},
{types::Hotkey::Unknown, QKeySequence {}}};
static bool IsHotkeyValid(const std::string& value);
class HotkeySettings::Impl
{
public:
explicit Impl()
{
for (const auto& hotkey : types::HotkeyIterator())
{
const std::string& name = types::GetHotkeyShortName(hotkey);
const std::string defaultValue =
kDefaultHotkeys_.at(hotkey).toString().toStdString();
auto result =
hotkey_.emplace(hotkey, SettingsVariable<std::string> {name});
SettingsVariable<std::string>& settingsVariable = result.first->second;
settingsVariable.SetDefault(defaultValue);
settingsVariable.SetValidator(&IsHotkeyValid);
variables_.push_back(&settingsVariable);
}
// Add an empty hotkey (not part of registered variables) for error
// handling
hotkey_.emplace(types::Hotkey::Unknown,
SettingsVariable<std::string> {"?"});
}
~Impl() {}
std::unordered_map<types::Hotkey, SettingsVariable<std::string>> hotkey_ {};
std::vector<SettingsVariableBase*> variables_ {};
};
HotkeySettings::HotkeySettings() :
SettingsCategory("hotkeys"), p(std::make_unique<Impl>())
{
RegisterVariables(p->variables_);
SetDefaults();
p->variables_.clear();
}
HotkeySettings::~HotkeySettings() = default;
HotkeySettings::HotkeySettings(HotkeySettings&&) noexcept = default;
HotkeySettings& HotkeySettings::operator=(HotkeySettings&&) noexcept = default;
SettingsVariable<std::string>&
HotkeySettings::hotkey(scwx::qt::types::Hotkey hotkey) const
{
auto hotkeyVariable = p->hotkey_.find(hotkey);
if (hotkeyVariable == p->hotkey_.cend())
{
hotkeyVariable = p->hotkey_.find(types::Hotkey::Unknown);
}
return hotkeyVariable->second;
}
HotkeySettings& HotkeySettings::Instance()
{
static HotkeySettings hotkeySettings_;
return hotkeySettings_;
}
bool operator==(const HotkeySettings& lhs, const HotkeySettings& rhs)
{
return (lhs.p->hotkey_ == rhs.p->hotkey_);
}
static bool IsHotkeyValid(const std::string& value)
{
return QKeySequence::fromString(QString::fromStdString(value))
.toString()
.toStdString() == value;
}
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,42 @@
#pragma once
#include <scwx/qt/settings/settings_category.hpp>
#include <scwx/qt/settings/settings_variable.hpp>
#include <scwx/qt/types/hotkey_types.hpp>
#include <memory>
#include <string>
namespace scwx
{
namespace qt
{
namespace settings
{
class HotkeySettings : public SettingsCategory
{
public:
explicit HotkeySettings();
~HotkeySettings();
HotkeySettings(const HotkeySettings&) = delete;
HotkeySettings& operator=(const HotkeySettings&) = delete;
HotkeySettings(HotkeySettings&&) noexcept;
HotkeySettings& operator=(HotkeySettings&&) noexcept;
SettingsVariable<std::string>& hotkey(scwx::qt::types::Hotkey hotkey) const;
static HotkeySettings& Instance();
friend bool operator==(const HotkeySettings& lhs, const HotkeySettings& rhs);
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -2,6 +2,7 @@
#include <scwx/qt/settings/settings_interface.hpp>
#include <scwx/qt/settings/settings_variable.hpp>
#include <scwx/qt/ui/hotkey_edit.hpp>
#include <boost/tokenizer.hpp>
#include <fmt/ranges.h>
@ -155,7 +156,27 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
return;
}
if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget))
if (ui::HotkeyEdit* hotkeyEdit = dynamic_cast<ui::HotkeyEdit*>(widget))
{
if constexpr (std::is_same_v<T, std::string>)
{
QObject::connect(hotkeyEdit,
&ui::HotkeyEdit::KeySequenceChanged,
p->context_.get(),
[this](const QKeySequence& sequence)
{
std::string value {
sequence.toString().toStdString()};
// Attempt to stage the value
p->stagedValid_ = p->variable_->StageValue(value);
p->UpdateResetButton();
// TODO: Display invalid status
});
}
}
else if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget))
{
if constexpr (std::is_same_v<T, std::string>)
{
@ -487,7 +508,16 @@ void SettingsInterface<T>::Impl::UpdateEditWidget()
const T value = variable_->GetValue();
const T& currentValue = staged.has_value() ? *staged : value;
if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_))
if (ui::HotkeyEdit* hotkeyEdit = dynamic_cast<ui::HotkeyEdit*>(editWidget_))
{
if constexpr (std::is_same_v<T, std::string>)
{
QKeySequence keySequence =
QKeySequence::fromString(QString::fromStdString(currentValue));
hotkeyEdit->set_key_sequence(keySequence);
}
}
else if (QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editWidget_))
{
SetWidgetText(lineEdit, currentValue);
}

View file

@ -0,0 +1,72 @@
#include <scwx/qt/types/hotkey_types.hpp>
#include <scwx/util/enum.hpp>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
namespace scwx
{
namespace qt
{
namespace types
{
static const std::unordered_map<Hotkey, std::string> hotkeyShortName_ {
{Hotkey::ChangeMapStyle, "change_map_style"},
{Hotkey::CopyCursorCoordinates, "copy_cursor_coordinates"},
{Hotkey::CopyMapCoordinates, "copy_map_coordinates"},
{Hotkey::MapPanUp, "map_pan_up"},
{Hotkey::MapPanDown, "map_pan_down"},
{Hotkey::MapPanLeft, "map_pan_left"},
{Hotkey::MapPanRight, "map_pan_right"},
{Hotkey::MapRotateClockwise, "map_rotate_clockwise"},
{Hotkey::MapRotateCounterclockwise, "map_rotate_counterclockwise"},
{Hotkey::MapZoomIn, "map_zoom_in"},
{Hotkey::MapZoomOut, "map_zoom_out"},
{Hotkey::ProductTiltDecrease, "product_tilt_decrease"},
{Hotkey::ProductTiltIncrease, "product_tilt_increase"},
{Hotkey::TimelineStepBegin, "timeline_step_begin"},
{Hotkey::TimelineStepBack, "timeline_step_back"},
{Hotkey::TimelinePlay, "timeline_play"},
{Hotkey::TimelineStepNext, "timeline_step_next"},
{Hotkey::TimelineStepEnd, "timeline_step_end"},
{Hotkey::Unknown, "?"}};
static const std::unordered_map<Hotkey, std::string> hotkeyLongName_ {
{Hotkey::ChangeMapStyle, "Change Map Style"},
{Hotkey::CopyCursorCoordinates, "Copy Cursor Coordinates"},
{Hotkey::CopyMapCoordinates, "Copy Map Coordinates"},
{Hotkey::MapPanUp, "Map Pan Up"},
{Hotkey::MapPanDown, "Map Pan Down"},
{Hotkey::MapPanLeft, "Map Pan Left"},
{Hotkey::MapPanRight, "Map Pan Right"},
{Hotkey::MapRotateClockwise, "Map Rotate Clockwise"},
{Hotkey::MapRotateCounterclockwise, "Map Rotate Counterclockwise"},
{Hotkey::MapZoomIn, "Map Zoom In"},
{Hotkey::MapZoomOut, "Map Zoom Out"},
{Hotkey::ProductTiltDecrease, "Product Tilt Decrease"},
{Hotkey::ProductTiltIncrease, "Product Tilt Increase"},
{Hotkey::TimelineStepBegin, "Timeline Step Begin"},
{Hotkey::TimelineStepBack, "Timeline Step Back"},
{Hotkey::TimelinePlay, "Timeline Play/Pause"},
{Hotkey::TimelineStepNext, "Timeline Step Next"},
{Hotkey::TimelineStepEnd, "Timeline Step End"},
{Hotkey::Unknown, "?"}};
SCWX_GET_ENUM(Hotkey, GetHotkeyFromShortName, hotkeyShortName_)
SCWX_GET_ENUM(Hotkey, GetHotkeyFromLongName, hotkeyLongName_)
const std::string& GetHotkeyShortName(Hotkey hotkey)
{
return hotkeyShortName_.at(hotkey);
}
const std::string& GetHotkeyLongName(Hotkey hotkey)
{
return hotkeyLongName_.at(hotkey);
}
} // namespace types
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,47 @@
#pragma once
#include <scwx/util/iterator.hpp>
#include <string>
namespace scwx
{
namespace qt
{
namespace types
{
enum class Hotkey
{
ChangeMapStyle,
CopyCursorCoordinates,
CopyMapCoordinates,
MapPanUp,
MapPanDown,
MapPanLeft,
MapPanRight,
MapRotateClockwise,
MapRotateCounterclockwise,
MapZoomIn,
MapZoomOut,
ProductTiltDecrease,
ProductTiltIncrease,
TimelineStepBegin,
TimelineStepBack,
TimelinePlay,
TimelineStepNext,
TimelineStepEnd,
Unknown
};
typedef scwx::util::
Iterator<Hotkey, Hotkey::ChangeMapStyle, Hotkey::TimelineStepEnd>
HotkeyIterator;
Hotkey GetHotkeyFromShortName(const std::string& name);
Hotkey GetHotkeyFromLongName(const std::string& name);
const std::string& GetHotkeyShortName(Hotkey hotkey);
const std::string& GetHotkeyLongName(Hotkey hotkey);
} // namespace types
} // namespace qt
} // namespace scwx

View file

@ -1,6 +1,7 @@
#include "animation_dock_widget.hpp"
#include "ui_animation_dock_widget.h"
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/util/time.hpp>
#include <scwx/util/logger.hpp>
@ -39,6 +40,9 @@ public:
AnimationDockWidget* self_;
std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
manager::HotkeyManager::Instance()};
types::AnimationState animationState_ {types::AnimationState::Pause};
types::MapTime viewType_ {types::MapTime::Live};
bool isLive_ {true};
@ -220,6 +224,39 @@ void AnimationDockWidgetImpl::ConnectSignals()
&QAbstractButton::clicked,
self_,
[this]() { Q_EMIT self_->AnimationStepEndSelected(); });
// Shortcuts
QObject::connect(hotkeyManager_.get(),
&manager::HotkeyManager::HotkeyPressed,
self_,
[this](types::Hotkey hotkey, bool /* isAutoRepeat */)
{
switch (hotkey)
{
case types::Hotkey::TimelineStepBegin:
Q_EMIT self_->AnimationStepBeginSelected();
break;
case types::Hotkey::TimelineStepBack:
Q_EMIT self_->AnimationStepBackSelected();
break;
case types::Hotkey::TimelinePlay:
Q_EMIT self_->AnimationPlaySelected();
break;
case types::Hotkey::TimelineStepNext:
Q_EMIT self_->AnimationStepNextSelected();
break;
case types::Hotkey::TimelineStepEnd:
Q_EMIT self_->AnimationStepEndSelected();
break;
default:
break;
}
});
}
void AnimationDockWidget::UpdateAnimationState(types::AnimationState state)

View file

@ -0,0 +1,137 @@
#include <scwx/qt/ui/hotkey_edit.hpp>
#include <scwx/util/logger.hpp>
#include <qevent.h>
namespace scwx
{
namespace qt
{
namespace ui
{
static const std::string logPrefix_ = "scwx::qt::ui::hotkey_edit";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
class HotkeyEdit::Impl
{
public:
explicit Impl() {};
~Impl() = default;
QKeySequence sequence_ {};
};
HotkeyEdit::HotkeyEdit(QWidget* parent) :
QLineEdit(parent), p {std::make_unique<Impl>()}
{
setReadOnly(true);
setClearButtonEnabled(true);
QAction* clearAction = findChild<QAction*>();
if (clearAction != nullptr)
{
clearAction->setEnabled(true);
connect(clearAction,
&QAction::triggered,
this,
[this](bool /* checked */)
{
logger_->trace("clearAction");
if (!p->sequence_.isEmpty())
{
// Clear saved sequence
p->sequence_ = QKeySequence {};
setText(p->sequence_.toString());
Q_EMIT KeySequenceChanged({});
}
});
}
}
HotkeyEdit::~HotkeyEdit() {}
QKeySequence HotkeyEdit::key_sequence() const
{
return p->sequence_;
}
void HotkeyEdit::set_key_sequence(const QKeySequence& sequence)
{
if (sequence != p->sequence_)
{
p->sequence_ = sequence;
setText(sequence.toString());
Q_EMIT KeySequenceChanged(sequence);
}
}
void HotkeyEdit::focusInEvent(QFocusEvent* e)
{
logger_->trace("focusInEvent");
// Replace text with placeholder prompting for input
setPlaceholderText("Press any key");
setText({});
QLineEdit::focusInEvent(e);
}
void HotkeyEdit::focusOutEvent(QFocusEvent* e)
{
logger_->trace("focusOutEvent");
// Replace text with saved sequence
setPlaceholderText({});
setText(p->sequence_.toString());
QLineEdit::focusOutEvent(e);
}
void HotkeyEdit::keyPressEvent(QKeyEvent* e)
{
logger_->trace("keyPressEvent");
QKeySequence sequence {};
switch (e->key())
{
case Qt::Key::Key_Shift:
case Qt::Key::Key_Control:
case Qt::Key::Key_Alt:
case Qt::Key::Key_Meta:
case Qt::Key::Key_Mode_switch:
// Record modifiers only in sequence
sequence = e->modifiers().toInt();
break;
default:
// Record modifiers and keys in sequence, and save sequence
sequence = e->modifiers().toInt() | e->key();
if (sequence != p->sequence_)
{
p->sequence_ = sequence;
Q_EMIT KeySequenceChanged(sequence);
}
clearFocus();
break;
}
setText(sequence.toString());
}
void HotkeyEdit::keyReleaseEvent(QKeyEvent*)
{
logger_->trace("keyReleaseEvent");
// Modifiers were released prior to pressing a non-modifier key
setText({});
}
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,41 @@
#pragma once
#include <QLineEdit>
namespace scwx
{
namespace qt
{
namespace ui
{
class HotkeyEdit : public QLineEdit
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(HotkeyEdit)
public:
explicit HotkeyEdit(QWidget* parent = nullptr);
~HotkeyEdit();
QKeySequence key_sequence() const;
void set_key_sequence(const QKeySequence& sequence);
protected:
void focusInEvent(QFocusEvent* e) override;
void focusOutEvent(QFocusEvent* e) override;
void keyPressEvent(QKeyEvent* e) override;
void keyReleaseEvent(QKeyEvent* e) override;
signals:
void KeySequenceChanged(const QKeySequence& sequence);
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -1,6 +1,8 @@
#include <scwx/qt/ui/level2_settings_widget.hpp>
#include <scwx/qt/ui/flow_layout.hpp>
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/common/characters.hpp>
#include <scwx/util/logger.hpp>
#include <execution>
@ -16,6 +18,9 @@ namespace qt
namespace ui
{
static const std::string logPrefix_ = "scwx::qt::ui::level2_settings_widget";
static const auto logger_ = util::Logger::Create(logPrefix_);
class Level2SettingsWidgetImpl : public QObject
{
Q_OBJECT
@ -46,9 +51,15 @@ public:
settingsLayout->addWidget(declutterCheckBox_);
settingsGroupBox_->setVisible(false);
QObject::connect(hotkeyManager_.get(),
&manager::HotkeyManager::HotkeyPressed,
this,
&Level2SettingsWidgetImpl::HandleHotkeyPressed);
}
~Level2SettingsWidgetImpl() = default;
void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat);
void NormalizeElevationButtons();
void SelectElevation(float elevation);
@ -63,6 +74,12 @@ public:
QGroupBox* settingsGroupBox_;
QCheckBox* declutterCheckBox_;
float currentElevation_ {};
QToolButton* currentElevationButton_ {nullptr};
std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
manager::HotkeyManager::Instance()};
};
Level2SettingsWidget::Level2SettingsWidget(QWidget* parent) :
@ -96,6 +113,71 @@ void Level2SettingsWidget::showEvent(QShowEvent* event)
p->NormalizeElevationButtons();
}
void Level2SettingsWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey,
bool isAutoRepeat)
{
if (hotkey != types::Hotkey::ProductTiltDecrease &&
hotkey != types::Hotkey::ProductTiltIncrease)
{
// Not handling this hotkey
return;
}
logger_->trace("Handling hotkey: {}, repeat: {}",
types::GetHotkeyShortName(hotkey),
isAutoRepeat);
if (!self_->isVisible() || currentElevationButton_ == nullptr)
{
// Level 2 product is not selected
return;
}
// Find the current elevation tilt
auto tiltIt = std::find(elevationButtons_.cbegin(),
elevationButtons_.cend(),
currentElevationButton_);
if (tiltIt == elevationButtons_.cend())
{
logger_->error("Could not locate level 2 tilt: {}", currentElevation_);
return;
}
if (hotkey == types::Hotkey::ProductTiltDecrease)
{
// Validate the current elevation tilt
if (tiltIt != elevationButtons_.cbegin())
{
// Get the previous elevation tilt
--tiltIt;
// Select the new elevation tilt
(*tiltIt)->click();
}
else
{
logger_->info("Level 2 tilt at lower limit");
}
}
else if (hotkey == types::Hotkey::ProductTiltIncrease)
{
// Get the next elevation tilt
++tiltIt;
// Validate the next elevation tilt
if (tiltIt != elevationButtons_.cend())
{
// Select the new elevation tilt
(*tiltIt)->click();
}
else
{
logger_->info("Level 2 tilt at upper limit");
}
}
}
void Level2SettingsWidgetImpl::NormalizeElevationButtons()
{
// Set each elevation cut's tool button to the same size
@ -135,12 +217,15 @@ void Level2SettingsWidget::UpdateElevationSelection(float elevation)
QString buttonText {QString::number(elevation, 'f', 1) +
common::Characters::DEGREE};
QToolButton* newElevationButton = nullptr;
std::for_each(p->elevationButtons_.cbegin(),
p->elevationButtons_.cend(),
[&](auto& toolButton)
{
if (toolButton->text() == buttonText)
{
newElevationButton = toolButton;
toolButton->setCheckable(true);
toolButton->setChecked(true);
}
@ -150,6 +235,9 @@ void Level2SettingsWidget::UpdateElevationSelection(float elevation)
toolButton->setCheckable(false);
}
});
p->currentElevation_ = elevation;
p->currentElevationButton_ = newElevationButton;
}
void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap)

View file

@ -1,5 +1,6 @@
#include <scwx/qt/ui/level3_products_widget.hpp>
#include <scwx/qt/ui/flow_layout.hpp>
#include <scwx/qt/manager/hotkey_manager.hpp>
#include <scwx/qt/settings/product_settings.hpp>
#include <scwx/qt/settings/settings_interface.hpp>
#include <scwx/util/logger.hpp>
@ -127,9 +128,15 @@ public:
stiPastEnabled_.SetEditWidget(stiPastEnableCheckBox);
stiForecastEnabled_.SetEditWidget(stiForecastEnableCheckBox);
QObject::connect(hotkeyManager_.get(),
&manager::HotkeyManager::HotkeyPressed,
this,
&Level3ProductsWidgetImpl::HandleHotkeyPressed);
}
~Level3ProductsWidgetImpl() = default;
void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat);
void NormalizeProductButtons();
void SelectProductCategory(common::Level3ProductCategory category);
void UpdateCategorySelection(common::Level3ProductCategory category);
@ -144,11 +151,17 @@ public:
std::unordered_map<std::string, QMenu*>>
categoryMenuMap_;
std::shared_ptr<manager::HotkeyManager> hotkeyManager_ {
manager::HotkeyManager::Instance()};
std::unordered_map<std::string, std::vector<QAction*>> productTiltMap_;
std::unordered_map<QAction*, std::string> awipsProductMap_;
std::shared_mutex awipsProductMutex_;
std::string currentAwipsId_ {};
QAction* currentProductTiltAction_ {nullptr};
settings::SettingsInterface<bool> stiPastEnabled_ {};
settings::SettingsInterface<bool> stiForecastEnabled_ {};
};
@ -167,6 +180,84 @@ void Level3ProductsWidget::showEvent(QShowEvent* event)
p->NormalizeProductButtons();
}
void Level3ProductsWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey,
bool isAutoRepeat)
{
if (hotkey != types::Hotkey::ProductTiltDecrease &&
hotkey != types::Hotkey::ProductTiltIncrease)
{
// Not handling this hotkey
return;
}
logger_->trace("Handling hotkey: {}, repeat: {}",
types::GetHotkeyShortName(hotkey),
isAutoRepeat);
std::string currentAwipsId = currentAwipsId_;
QAction* currentProductTiltAction = currentProductTiltAction_;
if (currentProductTiltAction == nullptr || currentAwipsId.empty() ||
currentAwipsId == "?")
{
// Level 3 product is not selected
return;
}
// Get product
std::string product = common::GetLevel3ProductByAwipsId(currentAwipsId);
if (product == "?")
{
logger_->error("Invalid AWIPS ID: {}", currentAwipsId);
return;
}
std::shared_lock lock {awipsProductMutex_};
// Find the current product tilt
auto productTiltsIt = productTiltMap_.find(product);
if (productTiltsIt == productTiltMap_.cend())
{
logger_->error("Could not find product tilt map: {}",
common::GetLevel3ProductDescription(product));
return;
}
auto& productTilts = productTiltsIt->second;
auto productTiltIt = std::find(
productTilts.cbegin(), productTilts.cend(), currentProductTiltAction);
if (productTiltIt == productTilts.cend())
{
logger_->error("Could not locate product tilt: {}", currentAwipsId);
return;
}
std::ptrdiff_t productTiltIndex =
std::distance(productTilts.cbegin(), productTiltIt);
// Determine the new product tilt index
std::ptrdiff_t newProductTiltIndex =
(hotkey == types::Hotkey::ProductTiltDecrease) ? productTiltIndex - 1 :
productTiltIndex + 1;
// Validate the new product tilt index
if (newProductTiltIndex < 0 ||
newProductTiltIndex >=
static_cast<std::ptrdiff_t>(productTilts.size()) ||
!productTilts.at(newProductTiltIndex)->isVisible())
{
const std::string direction =
(hotkey == types::Hotkey::ProductTiltDecrease) ? "lower" : "upper";
logger_->info("Product tilt at {} limit", direction);
return;
}
// Select the new tilt
productTilts.at(newProductTiltIndex)->trigger();
}
void Level3ProductsWidgetImpl::NormalizeProductButtons()
{
int level3MaxWidth = 0;
@ -283,6 +374,8 @@ void Level3ProductsWidget::UpdateProductSelection(
else
{
p->UpdateCategorySelection(common::Level3ProductCategory::Unknown);
p->currentAwipsId_.erase();
p->currentProductTiltAction_ = nullptr;
}
}
@ -293,7 +386,7 @@ void Level3ProductsWidgetImpl::UpdateCategorySelection(
std::for_each(categoryButtons_.cbegin(),
categoryButtons_.cend(),
[&](auto& toolButton)
[&, this](auto& toolButton)
{
if (toolButton->text().toStdString() == categoryName)
{
@ -313,10 +406,25 @@ void Level3ProductsWidgetImpl::UpdateProductSelection(
{
std::shared_lock lock {awipsProductMutex_};
QAction* newProductTilt = nullptr;
std::for_each(awipsProductMap_.cbegin(),
awipsProductMap_.cend(),
[=](const auto& pair)
{ pair.first->setChecked(pair.second == awipsId); });
[&, this](const auto& pair)
{
if (pair.second == awipsId)
{
newProductTilt = pair.first;
pair.first->setChecked(true);
}
else
{
pair.first->setChecked(false);
}
});
currentAwipsId_ = awipsId;
currentProductTiltAction_ = newProductTilt;
}
} // namespace ui

View file

@ -0,0 +1,107 @@
#include <scwx/qt/ui/settings/hotkey_settings_widget.hpp>
#include <scwx/qt/ui/hotkey_edit.hpp>
#include <scwx/qt/settings/hotkey_settings.hpp>
#include <scwx/qt/settings/settings_interface.hpp>
#include <scwx/qt/types/hotkey_types.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QToolButton>
#include <QVBoxLayout>
namespace scwx
{
namespace qt
{
namespace ui
{
static const std::string logPrefix_ =
"scwx::qt::ui::settings::hotkey_settings_widget";
class HotkeySettingsWidget::Impl
{
public:
explicit Impl(HotkeySettingsWidget* self)
{
auto& hotkeySettings = settings::HotkeySettings::Instance();
gridLayout_ = new QGridLayout(self);
contents_ = new QWidget(self);
contents_->setLayout(gridLayout_);
scrollArea_ = new QScrollArea(self);
scrollArea_->setHorizontalScrollBarPolicy(
Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
scrollArea_->setWidgetResizable(true);
scrollArea_->setWidget(contents_);
layout_ = new QVBoxLayout(self);
layout_->setContentsMargins(0, 0, 0, 0);
layout_->addWidget(scrollArea_);
self->setLayout(layout_);
int row = 0;
for (types::Hotkey hotkey : types::HotkeyIterator())
{
const std::string& labelText = types::GetHotkeyLongName(hotkey);
QLabel* label = new QLabel(QObject::tr(labelText.c_str()), self);
HotkeyEdit* hotkeyEdit = new HotkeyEdit(self);
QToolButton* resetButton = new QToolButton(self);
resetButton->setIcon(
QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"});
resetButton->setVisible(false);
gridLayout_->addWidget(label, row, 0);
gridLayout_->addWidget(hotkeyEdit, row, 1);
gridLayout_->addWidget(resetButton, row, 2);
// Create settings interface
auto result = hotkeys_.emplace(
hotkey, settings::SettingsInterface<std::string> {});
auto& pair = *result.first;
auto& interface = pair.second;
// Add to settings list
self->AddSettingsInterface(&interface);
auto& hotkeyVariable = hotkeySettings.hotkey(hotkey);
interface.SetSettingsVariable(hotkeyVariable);
interface.SetEditWidget(hotkeyEdit);
interface.SetResetButton(resetButton);
++row;
}
QSpacerItem* spacer =
new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout_->addItem(spacer, row, 0);
}
~Impl() = default;
QWidget* contents_;
QLayout* layout_;
QScrollArea* scrollArea_ {};
QGridLayout* gridLayout_ {};
boost::unordered_flat_map<types::Hotkey,
settings::SettingsInterface<std::string>>
hotkeys_ {};
};
HotkeySettingsWidget::HotkeySettingsWidget(QWidget* parent) :
SettingsPageWidget(parent), p {std::make_shared<Impl>(this)}
{
}
HotkeySettingsWidget::~HotkeySettingsWidget() = default;
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,29 @@
#pragma once
#include <scwx/qt/ui/settings/settings_page_widget.hpp>
#include <QWidget>
namespace scwx
{
namespace qt
{
namespace ui
{
class HotkeySettingsWidget : public SettingsPageWidget
{
Q_OBJECT
public:
explicit HotkeySettingsWidget(QWidget* parent = nullptr);
~HotkeySettingsWidget();
private:
class Impl;
std::shared_ptr<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,68 @@
#include <scwx/qt/ui/settings/settings_page_widget.hpp>
#include <scwx/qt/settings/settings_interface_base.hpp>
#include <vector>
namespace scwx
{
namespace qt
{
namespace ui
{
static const std::string logPrefix_ =
"scwx::qt::ui::settings::settings_page_widget";
class SettingsPageWidget::Impl
{
public:
explicit Impl() {}
~Impl() = default;
std::vector<settings::SettingsInterfaceBase*> settings_;
};
SettingsPageWidget::SettingsPageWidget(QWidget* parent) :
QWidget(parent), p {std::make_shared<Impl>()}
{
}
SettingsPageWidget::~SettingsPageWidget() = default;
void SettingsPageWidget::AddSettingsInterface(
settings::SettingsInterfaceBase* setting)
{
p->settings_.push_back(setting);
}
bool SettingsPageWidget::CommitChanges()
{
bool committed = false;
for (auto& setting : p->settings_)
{
committed |= setting->Commit();
}
return committed;
}
void SettingsPageWidget::DiscardChanges()
{
for (auto& setting : p->settings_)
{
setting->Reset();
}
}
void SettingsPageWidget::ResetToDefault()
{
for (auto& setting : p->settings_)
{
setting->StageDefault();
}
}
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,36 @@
#pragma once
#include <scwx/qt/settings/settings_interface_base.hpp>
#include <QWidget>
namespace scwx
{
namespace qt
{
namespace ui
{
class SettingsPageWidget : public QWidget
{
Q_OBJECT
public:
explicit SettingsPageWidget(QWidget* parent = nullptr);
~SettingsPageWidget();
bool CommitChanges();
void DiscardChanges();
void ResetToDefault();
protected:
void AddSettingsInterface(settings::SettingsInterfaceBase* setting);
private:
class Impl;
std::shared_ptr<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

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/settings/hotkey_settings_widget.hpp>
#include <scwx/qt/util/color.hpp>
#include <scwx/qt/util/file.hpp>
#include <scwx/util/logger.hpp>
@ -177,6 +178,7 @@ public:
void SetupPalettesAlertsTab();
void SetupAudioTab();
void SetupTextTab();
void SetupHotkeysTab();
void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr);
void UpdateRadarDialogLocation(const std::string& id);
@ -216,6 +218,9 @@ public:
std::shared_ptr<manager::PositionManager> positionManager_ {
manager::PositionManager::Instance()};
std::vector<SettingsPageWidget*> settingsPages_ {};
HotkeySettingsWidget* hotkeySettingsWidget_ {};
settings::SettingsInterface<std::string> defaultRadarSite_ {};
settings::SettingsInterface<std::int64_t> gridWidth_ {};
settings::SettingsInterface<std::int64_t> gridHeight_ {};
@ -289,6 +294,9 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
// Text
p->SetupTextTab();
// Hotkeys
p->SetupHotkeysTab();
p->ConnectSignals();
}
@ -1171,6 +1179,16 @@ void SettingsDialogImpl::SetupTextTab()
self_->ui->radarSiteHoverTextCheckBox);
}
void SettingsDialogImpl::SetupHotkeysTab()
{
QVBoxLayout* layout = new QVBoxLayout(self_->ui->hotkeys);
hotkeySettingsWidget_ = new HotkeySettingsWidget(self_->ui->hotkeys);
layout->addWidget(hotkeySettingsWidget_);
settingsPages_.push_back(hotkeySettingsWidget_);
}
QImage SettingsDialogImpl::GenerateColorTableImage(
std::shared_ptr<common::ColorTable> colorTable,
std::uint16_t min,
@ -1343,6 +1361,11 @@ void SettingsDialogImpl::ApplyChanges()
committed |= setting->Commit();
}
for (auto& page : settingsPages_)
{
committed |= page->CommitChanges();
}
if (committed)
{
manager::SettingsManager::Instance().SaveSettings();
@ -1357,6 +1380,11 @@ void SettingsDialogImpl::DiscardChanges()
{
setting->Reset();
}
for (auto& page : settingsPages_)
{
page->DiscardChanges();
}
}
void SettingsDialogImpl::ResetToDefault()
@ -1367,6 +1395,11 @@ void SettingsDialogImpl::ResetToDefault()
{
setting->StageDefault();
}
for (auto& page : settingsPages_)
{
page->ResetToDefault();
}
}
std::string SettingsDialogImpl::RadarSiteLabel(

View file

@ -13,8 +13,8 @@
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="0">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
@ -95,6 +95,15 @@
<normaloff>:/res/icons/font-awesome-6/font-solid.svg</normaloff>:/res/icons/font-awesome-6/font-solid.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>Hotkeys</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/keyboard-regular.svg</normaloff>:/res/icons/font-awesome-6/keyboard-regular.svg</iconset>
</property>
</item>
</widget>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
@ -209,6 +218,9 @@
<item row="5" column="2">
<widget class="QComboBox" name="mapProviderComboBox"/>
</item>
<item row="10" column="2">
<widget class="QComboBox" name="defaultTimeZoneComboBox"/>
</item>
<item row="7" column="4">
<widget class="QToolButton" name="resetMapTilerApiKeyButton">
<property name="text">
@ -249,6 +261,17 @@
</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="2" column="2">
<widget class="QSpinBox" name="gridWidthSpinBox"/>
</item>
@ -329,20 +352,6 @@
<item row="3" column="2">
<widget class="QSpinBox" name="gridHeightSpinBox"/>
</item>
<item row="10" column="2">
<widget class="QComboBox" name="defaultTimeZoneComboBox"/>
</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="4">
<widget class="QToolButton" name="resetDefaultTimeZoneButton">
<property name="text">
@ -429,8 +438,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>63</width>
<height>18</height>
<width>498</width>
<height>383</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
@ -1007,13 +1016,14 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="hotkeys"/>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>

@ -1 +1 @@
Subproject commit 2ba8740516bbdc58c848bf71755b2f285aa47938
Subproject commit 260b340030487b01ce9aa37135d949008c972f27