mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 16:50:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1021 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1021 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "settings_dialog.hpp"
 | |
| #include "ui_settings_dialog.h"
 | |
| 
 | |
| #include <scwx/awips/phenomenon.hpp>
 | |
| #include <scwx/common/color_table.hpp>
 | |
| #include <scwx/qt/config/radar_site.hpp>
 | |
| #include <scwx/qt/manager/settings_manager.hpp>
 | |
| #include <scwx/qt/map/map_provider.hpp>
 | |
| #include <scwx/qt/settings/general_settings.hpp>
 | |
| #include <scwx/qt/settings/palette_settings.hpp>
 | |
| #include <scwx/qt/settings/settings_interface.hpp>
 | |
| #include <scwx/qt/settings/text_settings.hpp>
 | |
| #include <scwx/qt/types/alert_types.hpp>
 | |
| #include <scwx/qt/types/font_types.hpp>
 | |
| #include <scwx/qt/types/text_types.hpp>
 | |
| #include <scwx/qt/ui/radar_site_dialog.hpp>
 | |
| #include <scwx/qt/util/color.hpp>
 | |
| #include <scwx/qt/util/file.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| #include <scwx/util/threads.hpp>
 | |
| 
 | |
| #include <boost/algorithm/string.hpp>
 | |
| #include <fmt/format.h>
 | |
| #include <QColorDialog>
 | |
| #include <QFileDialog>
 | |
| #include <QFontDatabase>
 | |
| #include <QFontDialog>
 | |
| #include <QStandardItemModel>
 | |
| #include <QToolButton>
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace qt
 | |
| {
 | |
| namespace ui
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::qt::ui::settings_dialog";
 | |
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | |
| 
 | |
| struct ColorTableConversions
 | |
| {
 | |
|    uint16_t rangeMin {0u};
 | |
|    uint16_t rangeMax {255u};
 | |
|    float    offset {0.0f};
 | |
|    float    scale {1.0f};
 | |
| };
 | |
| 
 | |
| static const std::array<std::pair<std::string, std::string>, 15>
 | |
|    kColorTableTypes_ {std::pair {"BR", "BR"},
 | |
|                       std::pair {"BV", "BV"},
 | |
|                       std::pair {"SW", "SW"},
 | |
|                       std::pair {"ZDR", "ZDR"},
 | |
|                       std::pair {"PHI2", "KDP (L2)"},
 | |
|                       std::pair {"CC", "CC"},
 | |
|                       std::pair {"DOD", "DOD"},
 | |
|                       std::pair {"DSD", "DSD"},
 | |
|                       std::pair {"ET", "ET"},
 | |
|                       std::pair {"OHP", "OHP"},
 | |
|                       std::pair {"PHI3", "KDP"},
 | |
|                       std::pair {"SRV", "SRV"},
 | |
|                       std::pair {"STP", "STP"},
 | |
|                       std::pair {"VIL", "VIL"},
 | |
|                       std::pair {"???", "Default"}};
 | |
| 
 | |
| // Color table conversions for display, roughly based upon:
 | |
| // - ICD for RDA-RPG: Data Moment Characteristics and Conversion for Data Names
 | |
| // - ICD for the RPG to Class 1 User
 | |
| static const std::unordered_map<std::string, ColorTableConversions>
 | |
|    kColorTableConversions_ {{"BR", {0u, 255u, 66.0f, 2.0f}},
 | |
|                             {"BV", {0u, 255u, 129.0f, 2.0f}},
 | |
|                             {"SW", {0u, 255u, 129.0f, 2.0f}},
 | |
|                             {"ZDR", {0u, 1058u, 418.0f, 32.0f}},
 | |
|                             {"PHI2", {0u, 1023u, 2.0f, 2.8361f}},
 | |
|                             {"CC", {0u, 255u, -60.5f, 300.0f}},
 | |
|                             {"DOD", {0u, 255u, 128.0f, 1.5f}},
 | |
|                             {"DSD", {0u, 255u, 128.0f, 1.5f}},
 | |
|                             {"ET", {0u, 255u, 2.0f, 1.0f}},
 | |
|                             {"OHP", {0u, 255u, 0.0f, 2.5f}},
 | |
|                             {"PHI3", {0u, 255u, 43.0f, 20.0f}},
 | |
|                             {"SRV", {0u, 255u, 128.0f, 2.0f}},
 | |
|                             {"STP", {0u, 255u, 0.0f, 1.25f}},
 | |
|                             {"VIL", {0u, 255u, 1.0f, 2.5f}},
 | |
|                             {"???", {0u, 15u, 0.0f, 1.0f}}};
 | |
| 
 | |
| class SettingsDialogImpl
 | |
| {
 | |
| public:
 | |
|    explicit SettingsDialogImpl(SettingsDialog* self) :
 | |
|        self_ {self},
 | |
|        radarSiteDialog_ {new RadarSiteDialog(self)},
 | |
|        fontDialog_ {new QFontDialog(self)},
 | |
|        fontCategoryModel_ {new QStandardItemModel(self)},
 | |
|        settings_ {std::initializer_list<settings::SettingsInterfaceBase*> {
 | |
|           &defaultRadarSite_,
 | |
|           &gridWidth_,
 | |
|           &gridHeight_,
 | |
|           &mapProvider_,
 | |
|           &mapboxApiKey_,
 | |
|           &mapTilerApiKey_,
 | |
|           &defaultAlertAction_,
 | |
|           &antiAliasingEnabled_,
 | |
|           &updateNotificationsEnabled_,
 | |
|           &debugEnabled_,
 | |
|           &hoverTextWrap_,
 | |
|           &tooltipMethod_,
 | |
|           &placefileTextDropShadowEnabled_}}
 | |
|    {
 | |
|       // Configure default alert phenomena colors
 | |
|       auto& paletteSettings = settings::PaletteSettings::Instance();
 | |
|       int   index           = 0;
 | |
| 
 | |
|       for (auto& phenomenon : settings::PaletteSettings::alert_phenomena())
 | |
|       {
 | |
|          QColorDialog::setCustomColor(
 | |
|             index++,
 | |
|             QColor(QString::fromStdString(
 | |
|                paletteSettings.alert_color(phenomenon, true).GetDefault())));
 | |
|          QColorDialog::setCustomColor(
 | |
|             index++,
 | |
|             QColor(QString::fromStdString(
 | |
|                paletteSettings.alert_color(phenomenon, false).GetDefault())));
 | |
|       }
 | |
| 
 | |
|       // Configure font dialog
 | |
|       fontDialog_->setOptions(
 | |
|          QFontDialog::FontDialogOption::DontUseNativeDialog |
 | |
|          QFontDialog::FontDialogOption::ScalableFonts);
 | |
|       fontDialog_->setWindowModality(Qt::WindowModality::WindowModal);
 | |
|    }
 | |
|    ~SettingsDialogImpl() = default;
 | |
| 
 | |
|    void ConnectSignals();
 | |
|    void SetupGeneralTab();
 | |
|    void SetupPalettesColorTablesTab();
 | |
|    void SetupPalettesAlertsTab();
 | |
|    void SetupTextTab();
 | |
| 
 | |
|    void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr);
 | |
|    void UpdateRadarDialogLocation(const std::string& id);
 | |
| 
 | |
|    QFont GetSelectedFont();
 | |
|    void  SelectFontCategory(types::FontCategory fontCategory);
 | |
|    void  UpdateFontDisplayData();
 | |
| 
 | |
|    void ApplyChanges();
 | |
|    void DiscardChanges();
 | |
|    void ResetToDefault();
 | |
| 
 | |
|    static QImage
 | |
|    GenerateColorTableImage(std::shared_ptr<common::ColorTable> colorTable,
 | |
|                            std::uint16_t                       min,
 | |
|                            std::uint16_t                       max,
 | |
|                            float                               offset,
 | |
|                            float                               scale);
 | |
|    static void LoadColorTablePreview(const std::string& key,
 | |
|                                      const std::string& value,
 | |
|                                      QLabel*            imageLabel);
 | |
|    static std::string
 | |
|                RadarSiteLabel(std::shared_ptr<config::RadarSite>& radarSite);
 | |
|    static void SetBackgroundColor(const std::string& value, QFrame* frame);
 | |
| 
 | |
|    SettingsDialog*  self_;
 | |
|    RadarSiteDialog* radarSiteDialog_;
 | |
|    QFontDialog*     fontDialog_;
 | |
| 
 | |
|    QStandardItemModel* fontCategoryModel_;
 | |
| 
 | |
|    types::FontCategory selectedFontCategory_ {types::FontCategory::Unknown};
 | |
| 
 | |
|    settings::SettingsInterface<std::string>  defaultRadarSite_ {};
 | |
|    settings::SettingsInterface<std::int64_t> gridWidth_ {};
 | |
|    settings::SettingsInterface<std::int64_t> gridHeight_ {};
 | |
|    settings::SettingsInterface<std::string>  mapProvider_ {};
 | |
|    settings::SettingsInterface<std::string>  mapboxApiKey_ {};
 | |
|    settings::SettingsInterface<std::string>  mapTilerApiKey_ {};
 | |
|    settings::SettingsInterface<std::string>  defaultAlertAction_ {};
 | |
|    settings::SettingsInterface<bool>         antiAliasingEnabled_ {};
 | |
|    settings::SettingsInterface<bool>         updateNotificationsEnabled_ {};
 | |
|    settings::SettingsInterface<bool>         debugEnabled_ {};
 | |
| 
 | |
|    std::unordered_map<std::string, settings::SettingsInterface<std::string>>
 | |
|       colorTables_ {};
 | |
|    std::unordered_map<awips::Phenomenon,
 | |
|                       settings::SettingsInterface<std::string>>
 | |
|       activeAlertColors_ {};
 | |
|    std::unordered_map<awips::Phenomenon,
 | |
|                       settings::SettingsInterface<std::string>>
 | |
|       inactiveAlertColors_ {};
 | |
| 
 | |
|    std::unordered_map<types::FontCategory,
 | |
|                       settings::SettingsInterface<std::string>>
 | |
|       fontFamilies_ {};
 | |
|    std::unordered_map<types::FontCategory,
 | |
|                       settings::SettingsInterface<std::string>>
 | |
|       fontStyles_ {};
 | |
|    std::unordered_map<types::FontCategory, settings::SettingsInterface<double>>
 | |
|       fontPointSizes_ {};
 | |
| 
 | |
|    settings::SettingsInterface<std::int64_t> hoverTextWrap_ {};
 | |
|    settings::SettingsInterface<std::string>  tooltipMethod_ {};
 | |
|    settings::SettingsInterface<bool>         placefileTextDropShadowEnabled_ {};
 | |
| 
 | |
|    std::vector<settings::SettingsInterfaceBase*> settings_;
 | |
| };
 | |
| 
 | |
| SettingsDialog::SettingsDialog(QWidget* parent) :
 | |
|     QDialog(parent),
 | |
|     p {std::make_unique<SettingsDialogImpl>(this)},
 | |
|     ui(new Ui::SettingsDialog)
 | |
| {
 | |
|    ui->setupUi(this);
 | |
| 
 | |
|    // General
 | |
|    p->SetupGeneralTab();
 | |
| 
 | |
|    // Palettes > Color Tables
 | |
|    p->SetupPalettesColorTablesTab();
 | |
| 
 | |
|    // Palettes > Alerts
 | |
|    p->SetupPalettesAlertsTab();
 | |
| 
 | |
|    // Text
 | |
|    p->SetupTextTab();
 | |
| 
 | |
|    p->ConnectSignals();
 | |
| }
 | |
| 
 | |
| SettingsDialog::~SettingsDialog()
 | |
| {
 | |
|    delete ui;
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::ConnectSignals()
 | |
| {
 | |
|    QObject::connect(self_->ui->listWidget,
 | |
|                     &QListWidget::currentRowChanged,
 | |
|                     self_->ui->stackedWidget,
 | |
|                     &QStackedWidget::setCurrentIndex);
 | |
| 
 | |
|    QObject::connect(self_->ui->radarSiteSelectButton,
 | |
|                     &QAbstractButton::clicked,
 | |
|                     self_,
 | |
|                     [this]() { radarSiteDialog_->show(); });
 | |
| 
 | |
|    QObject::connect(radarSiteDialog_,
 | |
|                     &RadarSiteDialog::accepted,
 | |
|                     self_,
 | |
|                     [this]()
 | |
|                     {
 | |
|                        std::string id = radarSiteDialog_->radar_site();
 | |
| 
 | |
|                        std::shared_ptr<config::RadarSite> radarSite =
 | |
|                           config::RadarSite::Get(id);
 | |
| 
 | |
|                        if (radarSite != nullptr)
 | |
|                        {
 | |
|                           self_->ui->radarSiteComboBox->setCurrentText(
 | |
|                              QString::fromStdString(RadarSiteLabel(radarSite)));
 | |
|                        }
 | |
|                     });
 | |
| 
 | |
|    // Update the Radar Site dialog "map" location with the currently selected
 | |
|    // radar site
 | |
|    auto& defaultRadarSite = *defaultRadarSite_.GetSettingsVariable();
 | |
|    defaultRadarSite.RegisterValueStagedCallback(
 | |
|       [this](const std::string& newValue)
 | |
|       { UpdateRadarDialogLocation(newValue); });
 | |
| 
 | |
|    QObject::connect(
 | |
|       self_->ui->fontListView->selectionModel(),
 | |
|       &QItemSelectionModel::selectionChanged,
 | |
|       self_,
 | |
|       [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;
 | |
|          }
 | |
| 
 | |
|          if (selected.size() > 0)
 | |
|          {
 | |
|             QModelIndex selectedIndex = selected[0].indexes()[0];
 | |
|             QVariant    variantData =
 | |
|                self_->ui->fontListView->model()->data(selectedIndex);
 | |
|             if (variantData.typeId() == QMetaType::QString)
 | |
|             {
 | |
|                types::FontCategory fontCategory =
 | |
|                   types::GetFontCategory(variantData.toString().toStdString());
 | |
|                SelectFontCategory(fontCategory);
 | |
|                UpdateFontDisplayData();
 | |
|             }
 | |
|          }
 | |
|       });
 | |
| 
 | |
|    QObject::connect(self_->ui->fontSelectButton,
 | |
|                     &QAbstractButton::clicked,
 | |
|                     self_,
 | |
|                     [this]()
 | |
|                     {
 | |
|                        fontDialog_->setCurrentFont(GetSelectedFont());
 | |
|                        fontDialog_->show();
 | |
|                     });
 | |
| 
 | |
|    QObject::connect(fontDialog_,
 | |
|                     &QFontDialog::fontSelected,
 | |
|                     self_,
 | |
|                     [this](const QFont& font)
 | |
|                     {
 | |
|                        fontFamilies_.at(selectedFontCategory_)
 | |
|                           .StageValue(font.family().toStdString());
 | |
|                        fontStyles_.at(selectedFontCategory_)
 | |
|                           .StageValue(font.styleName().toStdString());
 | |
|                        fontPointSizes_.at(selectedFontCategory_)
 | |
|                           .StageValue(font.pointSizeF());
 | |
| 
 | |
|                        UpdateFontDisplayData();
 | |
|                     });
 | |
| 
 | |
|    QObject::connect(self_->ui->resetFontButton,
 | |
|                     &QAbstractButton::clicked,
 | |
|                     self_,
 | |
|                     [this]()
 | |
|                     {
 | |
|                        fontFamilies_.at(selectedFontCategory_).StageDefault();
 | |
|                        fontStyles_.at(selectedFontCategory_).StageDefault();
 | |
|                        fontPointSizes_.at(selectedFontCategory_).StageDefault();
 | |
| 
 | |
|                        UpdateFontDisplayData();
 | |
|                     });
 | |
| 
 | |
|    QObject::connect(
 | |
|       self_->ui->buttonBox,
 | |
|       &QDialogButtonBox::clicked,
 | |
|       self_,
 | |
|       [this](QAbstractButton* button)
 | |
|       {
 | |
|          QDialogButtonBox::ButtonRole role =
 | |
|             self_->ui->buttonBox->buttonRole(button);
 | |
| 
 | |
|          switch (role)
 | |
|          {
 | |
|          case QDialogButtonBox::ButtonRole::AcceptRole: // OK
 | |
|          case QDialogButtonBox::ButtonRole::ApplyRole:  // Apply
 | |
|             ApplyChanges();
 | |
|             break;
 | |
| 
 | |
|          case QDialogButtonBox::ButtonRole::DestructiveRole: // Discard
 | |
|          case QDialogButtonBox::ButtonRole::RejectRole:      // Cancel
 | |
|             DiscardChanges();
 | |
|             break;
 | |
| 
 | |
|          case QDialogButtonBox::ButtonRole::ResetRole: // Restore Defaults
 | |
|             ResetToDefault();
 | |
|             break;
 | |
| 
 | |
|          default:
 | |
|             break;
 | |
|          }
 | |
|       });
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SetupGeneralTab()
 | |
| {
 | |
|    auto radarSites = config::RadarSite::GetAll();
 | |
| 
 | |
|    // Sort radar sites by ID
 | |
|    std::sort(radarSites.begin(),
 | |
|              radarSites.end(),
 | |
|              [](const std::shared_ptr<config::RadarSite>& a,
 | |
|                 const std::shared_ptr<config::RadarSite>& b)
 | |
|              { return a->id() < b->id(); });
 | |
| 
 | |
|    // Add sorted radar sites
 | |
|    for (std::shared_ptr<config::RadarSite>& radarSite : radarSites)
 | |
|    {
 | |
|       QString text = QString::fromStdString(RadarSiteLabel(radarSite));
 | |
|       self_->ui->radarSiteComboBox->addItem(text);
 | |
|    }
 | |
| 
 | |
|    settings::GeneralSettings& generalSettings =
 | |
|       settings::GeneralSettings::Instance();
 | |
| 
 | |
|    defaultRadarSite_.SetSettingsVariable(generalSettings.default_radar_site());
 | |
|    defaultRadarSite_.SetMapFromValueFunction(
 | |
|       [](const std::string& id) -> std::string
 | |
|       {
 | |
|          // Get the radar site associated with the ID
 | |
|          std::shared_ptr<config::RadarSite> radarSite =
 | |
|             config::RadarSite::Get(id);
 | |
| 
 | |
|          if (radarSite == nullptr)
 | |
|          {
 | |
|             // No radar site found, just return the ID
 | |
|             return id;
 | |
|          }
 | |
| 
 | |
|          // Add location details to the radar site
 | |
|          return RadarSiteLabel(radarSite);
 | |
|       });
 | |
|    defaultRadarSite_.SetMapToValueFunction(
 | |
|       [](const std::string& text) -> std::string
 | |
|       {
 | |
|          // Find the position of location details
 | |
|          size_t pos = text.rfind(" (");
 | |
| 
 | |
|          if (pos == std::string::npos)
 | |
|          {
 | |
|             // No location details found, just return the text
 | |
|             return text;
 | |
|          }
 | |
| 
 | |
|          // Remove location details from the radar site
 | |
|          return text.substr(0, pos);
 | |
|       });
 | |
|    defaultRadarSite_.SetEditWidget(self_->ui->radarSiteComboBox);
 | |
|    defaultRadarSite_.SetResetButton(self_->ui->resetRadarSiteButton);
 | |
|    UpdateRadarDialogLocation(generalSettings.default_radar_site().GetValue());
 | |
| 
 | |
|    gridWidth_.SetSettingsVariable(generalSettings.grid_width());
 | |
|    gridWidth_.SetEditWidget(self_->ui->gridWidthSpinBox);
 | |
|    gridWidth_.SetResetButton(self_->ui->resetGridWidthButton);
 | |
| 
 | |
|    gridHeight_.SetSettingsVariable(generalSettings.grid_height());
 | |
|    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);
 | |
|    mapProvider_.SetResetButton(self_->ui->resetMapProviderButton);
 | |
| 
 | |
|    mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key());
 | |
|    mapboxApiKey_.SetEditWidget(self_->ui->mapboxApiKeyLineEdit);
 | |
|    mapboxApiKey_.SetResetButton(self_->ui->resetMapboxApiKeyButton);
 | |
| 
 | |
|    mapTilerApiKey_.SetSettingsVariable(generalSettings.maptiler_api_key());
 | |
|    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(
 | |
|       [](const std::string& text) -> std::string
 | |
|       {
 | |
|          for (types::AlertAction alertAction : types::AlertActionIterator())
 | |
|          {
 | |
|             const std::string alertActionName =
 | |
|                types::GetAlertActionName(alertAction);
 | |
| 
 | |
|             if (boost::iequals(text, alertActionName))
 | |
|             {
 | |
|                // Return alert action label
 | |
|                return alertActionName;
 | |
|             }
 | |
|          }
 | |
| 
 | |
|          // Alert action label not found, return unknown
 | |
|          return "?";
 | |
|       });
 | |
|    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);
 | |
|    defaultAlertAction_.SetResetButton(self_->ui->resetDefaultAlertActionButton);
 | |
| 
 | |
|    antiAliasingEnabled_.SetSettingsVariable(
 | |
|       generalSettings.anti_aliasing_enabled());
 | |
|    antiAliasingEnabled_.SetEditWidget(self_->ui->antiAliasingEnabledCheckBox);
 | |
| 
 | |
|    updateNotificationsEnabled_.SetSettingsVariable(
 | |
|       generalSettings.update_notifications_enabled());
 | |
|    updateNotificationsEnabled_.SetEditWidget(
 | |
|       self_->ui->enableUpdateNotificationsCheckBox);
 | |
| 
 | |
|    debugEnabled_.SetSettingsVariable(generalSettings.debug_enabled());
 | |
|    debugEnabled_.SetEditWidget(self_->ui->debugEnabledCheckBox);
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SetupPalettesColorTablesTab()
 | |
| {
 | |
|    settings::PaletteSettings& paletteSettings =
 | |
|       settings::PaletteSettings::Instance();
 | |
| 
 | |
|    // Palettes > Color Tables
 | |
|    QGridLayout* colorTableLayout =
 | |
|       reinterpret_cast<QGridLayout*>(self_->ui->colorTableContents->layout());
 | |
| 
 | |
|    int colorTableRow = 0;
 | |
|    for (auto& colorTableType : kColorTableTypes_)
 | |
|    {
 | |
|       QLabel*      imageLabel     = new QLabel(self_);
 | |
|       QLineEdit*   lineEdit       = new QLineEdit(self_);
 | |
|       QToolButton* openFileButton = new QToolButton(self_);
 | |
|       QToolButton* resetButton    = new QToolButton(self_);
 | |
| 
 | |
|       openFileButton->setText(QObject::tr("..."));
 | |
| 
 | |
|       resetButton->setIcon(
 | |
|          QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"});
 | |
|       resetButton->setVisible(false);
 | |
| 
 | |
|       imageLabel->setFrameShape(QFrame::Shape::Box);
 | |
|       imageLabel->setFrameShadow(QFrame::Shadow::Plain);
 | |
|       imageLabel->setVisible(false);
 | |
| 
 | |
|       colorTableLayout->addWidget(
 | |
|          new QLabel(colorTableType.second.c_str(), self_), colorTableRow, 0);
 | |
|       colorTableLayout->addWidget(imageLabel, colorTableRow, 1);
 | |
|       colorTableLayout->addWidget(lineEdit, colorTableRow, 2);
 | |
|       colorTableLayout->addWidget(openFileButton, colorTableRow, 3);
 | |
|       colorTableLayout->addWidget(resetButton, colorTableRow, 4);
 | |
|       ++colorTableRow;
 | |
| 
 | |
|       // Create settings interface
 | |
|       auto result = colorTables_.emplace(
 | |
|          colorTableType.first, settings::SettingsInterface<std::string> {});
 | |
|       auto& pair       = *result.first;
 | |
|       auto& colorTable = pair.second;
 | |
| 
 | |
|       // Add to settings list
 | |
|       settings_.push_back(&colorTable);
 | |
| 
 | |
|       auto& colorTableVariable = paletteSettings.palette(colorTableType.first);
 | |
|       colorTable.SetSettingsVariable(colorTableVariable);
 | |
|       colorTable.SetEditWidget(lineEdit);
 | |
|       colorTable.SetResetButton(resetButton);
 | |
| 
 | |
|       colorTableVariable.RegisterValueStagedCallback(
 | |
|          [colorTableType, imageLabel](const std::string& value)
 | |
|          { LoadColorTablePreview(colorTableType.first, value, imageLabel); });
 | |
| 
 | |
|       LoadColorTablePreview(
 | |
|          colorTableType.first, colorTableVariable.GetValue(), imageLabel);
 | |
| 
 | |
|       QObject::connect(
 | |
|          openFileButton,
 | |
|          &QAbstractButton::clicked,
 | |
|          self_,
 | |
|          [this, lineEdit]()
 | |
|          {
 | |
|             static const std::string paletteFilter = "Color Palettes (*.pal)";
 | |
|             static const std::string allFilter     = "All Files (*)";
 | |
| 
 | |
|             QFileDialog* dialog = new QFileDialog(self_);
 | |
| 
 | |
|             dialog->setFileMode(QFileDialog::ExistingFile);
 | |
|             dialog->setNameFilters({QObject::tr(paletteFilter.c_str()),
 | |
|                                     QObject::tr(allFilter.c_str())});
 | |
|             dialog->setAttribute(Qt::WA_DeleteOnClose);
 | |
| 
 | |
|             QObject::connect(dialog,
 | |
|                              &QFileDialog::fileSelected,
 | |
|                              self_,
 | |
|                              [this, lineEdit](const QString& file)
 | |
|                              {
 | |
|                                 QString path = QDir::toNativeSeparators(file);
 | |
| 
 | |
|                                 logger_->info("Selected palette: {}",
 | |
|                                               path.toStdString());
 | |
|                                 lineEdit->setText(path);
 | |
| 
 | |
|                                 // setText does not emit the textEdited signal
 | |
|                                 Q_EMIT lineEdit->textEdited(path);
 | |
|                              });
 | |
| 
 | |
|             dialog->open();
 | |
|          });
 | |
|    }
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SetupPalettesAlertsTab()
 | |
| {
 | |
|    settings::PaletteSettings& paletteSettings =
 | |
|       settings::PaletteSettings::Instance();
 | |
| 
 | |
|    // Palettes > Alerts
 | |
|    QGridLayout* alertsLayout =
 | |
|       reinterpret_cast<QGridLayout*>(self_->ui->alertsFrame->layout());
 | |
| 
 | |
|    QLabel* phenomenonLabel = new QLabel(QObject::tr("Phenomenon"), self_);
 | |
|    QLabel* activeLabel     = new QLabel(QObject::tr("Active"), self_);
 | |
|    QLabel* inactiveLabel   = new QLabel(QObject::tr("Inactive"), self_);
 | |
| 
 | |
|    QFont boldFont;
 | |
|    boldFont.setBold(true);
 | |
|    phenomenonLabel->setFont(boldFont);
 | |
|    activeLabel->setFont(boldFont);
 | |
|    inactiveLabel->setFont(boldFont);
 | |
| 
 | |
|    alertsLayout->addWidget(phenomenonLabel, 0, 0);
 | |
|    alertsLayout->addWidget(activeLabel, 0, 1, 1, 4);
 | |
|    alertsLayout->addWidget(inactiveLabel, 0, 5, 1, 4);
 | |
| 
 | |
|    int alertsRow = 1;
 | |
|    for (auto& phenomenon : settings::PaletteSettings::alert_phenomena())
 | |
|    {
 | |
|       QFrame* activeFrame   = new QFrame(self_);
 | |
|       QFrame* inactiveFrame = new QFrame(self_);
 | |
| 
 | |
|       QLineEdit* activeEdit   = new QLineEdit(self_);
 | |
|       QLineEdit* inactiveEdit = new QLineEdit(self_);
 | |
| 
 | |
|       QToolButton* activeButton        = new QToolButton(self_);
 | |
|       QToolButton* inactiveButton      = new QToolButton(self_);
 | |
|       QToolButton* activeResetButton   = new QToolButton(self_);
 | |
|       QToolButton* inactiveResetButton = new QToolButton(self_);
 | |
| 
 | |
|       activeFrame->setMinimumHeight(24);
 | |
|       activeFrame->setMinimumWidth(24);
 | |
|       activeFrame->setFrameShape(QFrame::Shape::Box);
 | |
|       activeFrame->setFrameShadow(QFrame::Shadow::Plain);
 | |
|       inactiveFrame->setMinimumHeight(24);
 | |
|       inactiveFrame->setMinimumWidth(24);
 | |
|       inactiveFrame->setFrameShape(QFrame::Shape::Box);
 | |
|       inactiveFrame->setFrameShadow(QFrame::Shadow::Plain);
 | |
| 
 | |
|       activeButton->setIcon(
 | |
|          QIcon {":/res/icons/font-awesome-6/palette-solid.svg"});
 | |
|       inactiveButton->setIcon(
 | |
|          QIcon {":/res/icons/font-awesome-6/palette-solid.svg"});
 | |
|       activeResetButton->setIcon(
 | |
|          QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"});
 | |
|       inactiveResetButton->setIcon(
 | |
|          QIcon {":/res/icons/font-awesome-6/rotate-left-solid.svg"});
 | |
| 
 | |
|       alertsLayout->addWidget(
 | |
|          new QLabel(QObject::tr(awips::GetPhenomenonText(phenomenon).c_str()),
 | |
|                     self_),
 | |
|          alertsRow,
 | |
|          0);
 | |
|       alertsLayout->addWidget(activeFrame, alertsRow, 1);
 | |
|       alertsLayout->addWidget(activeEdit, alertsRow, 2);
 | |
|       alertsLayout->addWidget(activeButton, alertsRow, 3);
 | |
|       alertsLayout->addWidget(activeResetButton, alertsRow, 4);
 | |
|       alertsLayout->addWidget(inactiveFrame, alertsRow, 5);
 | |
|       alertsLayout->addWidget(inactiveEdit, alertsRow, 6);
 | |
|       alertsLayout->addWidget(inactiveButton, alertsRow, 7);
 | |
|       alertsLayout->addWidget(inactiveResetButton, alertsRow, 8);
 | |
|       ++alertsRow;
 | |
| 
 | |
|       // Create settings interface
 | |
|       auto activeResult = activeAlertColors_.emplace(
 | |
|          phenomenon, settings::SettingsInterface<std::string> {});
 | |
|       auto inactiveResult = inactiveAlertColors_.emplace(
 | |
|          phenomenon, settings::SettingsInterface<std::string> {});
 | |
|       auto& activeColor   = activeResult.first->second;
 | |
|       auto& inactiveColor = inactiveResult.first->second;
 | |
| 
 | |
|       // Add to settings list
 | |
|       settings_.push_back(&activeColor);
 | |
|       settings_.push_back(&inactiveColor);
 | |
| 
 | |
|       auto& activeSetting   = paletteSettings.alert_color(phenomenon, true);
 | |
|       auto& inactiveSetting = paletteSettings.alert_color(phenomenon, false);
 | |
| 
 | |
|       activeColor.SetSettingsVariable(activeSetting);
 | |
|       activeColor.SetEditWidget(activeEdit);
 | |
|       activeColor.SetResetButton(activeResetButton);
 | |
| 
 | |
|       inactiveColor.SetSettingsVariable(inactiveSetting);
 | |
|       inactiveColor.SetEditWidget(inactiveEdit);
 | |
|       inactiveColor.SetResetButton(inactiveResetButton);
 | |
| 
 | |
|       SetBackgroundColor(activeSetting.GetValue(), activeFrame);
 | |
|       SetBackgroundColor(inactiveSetting.GetValue(), inactiveFrame);
 | |
| 
 | |
|       activeSetting.RegisterValueStagedCallback(
 | |
|          [activeFrame](const std::string& value)
 | |
|          { SetBackgroundColor(value, activeFrame); });
 | |
|       inactiveSetting.RegisterValueStagedCallback(
 | |
|          [inactiveFrame](const std::string& value)
 | |
|          { SetBackgroundColor(value, inactiveFrame); });
 | |
| 
 | |
|       QObject::connect(activeButton,
 | |
|                        &QAbstractButton::clicked,
 | |
|                        self_,
 | |
|                        [=, this]()
 | |
|                        { ShowColorDialog(activeEdit, activeFrame); });
 | |
|       QObject::connect(inactiveButton,
 | |
|                        &QAbstractButton::clicked,
 | |
|                        self_,
 | |
|                        [=, this]()
 | |
|                        { ShowColorDialog(inactiveEdit, inactiveFrame); });
 | |
|    }
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SetupTextTab()
 | |
| {
 | |
|    settings::TextSettings& textSettings = settings::TextSettings::Instance();
 | |
| 
 | |
|    self_->ui->fontListView->setModel(fontCategoryModel_);
 | |
|    for (const auto& fontCategory : types::FontCategoryIterator())
 | |
|    {
 | |
|       // Add font category to list view
 | |
|       fontCategoryModel_->appendRow(new QStandardItem(
 | |
|          QString::fromStdString(types::GetFontCategoryName(fontCategory))));
 | |
| 
 | |
|       // Create settings interface
 | |
|       auto fontFamilyResult = fontFamilies_.emplace(
 | |
|          fontCategory, settings::SettingsInterface<std::string> {});
 | |
|       auto fontStyleResult = fontStyles_.emplace(
 | |
|          fontCategory, settings::SettingsInterface<std::string> {});
 | |
|       auto fontSizeResult = fontPointSizes_.emplace(
 | |
|          fontCategory, settings::SettingsInterface<double> {});
 | |
| 
 | |
|       auto& fontFamily = (*fontFamilyResult.first).second;
 | |
|       auto& fontStyle  = (*fontStyleResult.first).second;
 | |
|       auto& fontSize   = (*fontSizeResult.first).second;
 | |
| 
 | |
|       // Add to settings list
 | |
|       settings_.push_back(&fontFamily);
 | |
|       settings_.push_back(&fontStyle);
 | |
|       settings_.push_back(&fontSize);
 | |
| 
 | |
|       // Set settings variables
 | |
|       fontFamily.SetSettingsVariable(textSettings.font_family(fontCategory));
 | |
|       fontStyle.SetSettingsVariable(textSettings.font_style(fontCategory));
 | |
|       fontSize.SetSettingsVariable(textSettings.font_point_size(fontCategory));
 | |
|    }
 | |
|    self_->ui->fontListView->setCurrentIndex(fontCategoryModel_->index(0, 0));
 | |
|    SelectFontCategory(*types::FontCategoryIterator().begin());
 | |
|    UpdateFontDisplayData();
 | |
| 
 | |
|    hoverTextWrap_.SetSettingsVariable(textSettings.hover_text_wrap());
 | |
|    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);
 | |
|    tooltipMethod_.SetResetButton(self_->ui->resetTooltipMethodButton);
 | |
| 
 | |
|    placefileTextDropShadowEnabled_.SetSettingsVariable(
 | |
|       textSettings.placefile_text_drop_shadow_enabled());
 | |
|    placefileTextDropShadowEnabled_.SetEditWidget(
 | |
|       self_->ui->placefileTextDropShadowCheckBox);
 | |
| }
 | |
| 
 | |
| QImage SettingsDialogImpl::GenerateColorTableImage(
 | |
|    std::shared_ptr<common::ColorTable> colorTable,
 | |
|    std::uint16_t                       min,
 | |
|    std::uint16_t                       max,
 | |
|    float                               offset,
 | |
|    float                               scale)
 | |
| {
 | |
|    std::size_t width  = max - min + 1u;
 | |
|    std::size_t height = 1u;
 | |
|    QImage      image(static_cast<int>(width),
 | |
|                 static_cast<int>(height),
 | |
|                 QImage::Format::Format_ARGB32);
 | |
| 
 | |
|    for (std::size_t i = min; i <= max; ++i)
 | |
|    {
 | |
|       const float               value = (i - offset) / scale;
 | |
|       boost::gil::rgba8_pixel_t pixel = colorTable->Color(value);
 | |
|       image.setPixel(static_cast<int>(i - min),
 | |
|                      0,
 | |
|                      qRgba(static_cast<int>(pixel[0]),
 | |
|                            static_cast<int>(pixel[1]),
 | |
|                            static_cast<int>(pixel[2]),
 | |
|                            static_cast<int>(pixel[3])));
 | |
|    }
 | |
| 
 | |
|    return image;
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::LoadColorTablePreview(const std::string& key,
 | |
|                                                const std::string& value,
 | |
|                                                QLabel*            imageLabel)
 | |
| {
 | |
|    scwx::util::async(
 | |
|       [key, value, imageLabel]()
 | |
|       {
 | |
|          std::unique_ptr<std::istream>       is = util::OpenFile(value);
 | |
|          std::shared_ptr<common::ColorTable> colorTable =
 | |
|             common::ColorTable::Load(*is);
 | |
| 
 | |
|          if (colorTable->IsValid())
 | |
|          {
 | |
|             auto&   conversions = kColorTableConversions_.at(key);
 | |
|             QPixmap image =
 | |
|                QPixmap::fromImage(GenerateColorTableImage(colorTable,
 | |
|                                                           conversions.rangeMin,
 | |
|                                                           conversions.rangeMax,
 | |
|                                                           conversions.offset,
 | |
|                                                           conversions.scale))
 | |
|                   .scaled(64, 20);
 | |
|             imageLabel->setPixmap(image);
 | |
| 
 | |
|             QMetaObject::invokeMethod(
 | |
|                imageLabel, [imageLabel] { imageLabel->setVisible(true); });
 | |
|          }
 | |
|          else
 | |
|          {
 | |
|             imageLabel->clear();
 | |
| 
 | |
|             QMetaObject::invokeMethod(
 | |
|                imageLabel, [imageLabel] { imageLabel->setVisible(false); });
 | |
|          }
 | |
|       });
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::ShowColorDialog(QLineEdit* lineEdit, QFrame* frame)
 | |
| {
 | |
|    QColorDialog* dialog = new QColorDialog(self_);
 | |
| 
 | |
|    dialog->setAttribute(Qt::WA_DeleteOnClose);
 | |
|    dialog->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel);
 | |
| 
 | |
|    QColor initialColor(lineEdit->text());
 | |
|    if (initialColor.isValid())
 | |
|    {
 | |
|       dialog->setCurrentColor(initialColor);
 | |
|    }
 | |
| 
 | |
|    QObject::connect(
 | |
|       dialog,
 | |
|       &QColorDialog::colorSelected,
 | |
|       self_,
 | |
|       [this, lineEdit, frame](const QColor& color)
 | |
|       {
 | |
|          QString colorName = color.name(QColor::NameFormat::HexArgb);
 | |
| 
 | |
|          logger_->info("Selected color: {}", colorName.toStdString());
 | |
|          lineEdit->setText(colorName);
 | |
| 
 | |
|          // setText does not emit the textEdited signal
 | |
|          Q_EMIT lineEdit->textEdited(colorName);
 | |
|       });
 | |
| 
 | |
|    dialog->open();
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SetBackgroundColor(const std::string& value,
 | |
|                                             QFrame*            frame)
 | |
| {
 | |
|    frame->setStyleSheet(
 | |
|       QString::fromStdString(fmt::format("background-color: {}", value)));
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::UpdateRadarDialogLocation(const std::string& id)
 | |
| {
 | |
|    std::shared_ptr<config::RadarSite> radarSite = config::RadarSite::Get(id);
 | |
| 
 | |
|    if (radarSite != nullptr)
 | |
|    {
 | |
|       radarSiteDialog_->HandleMapUpdate(radarSite->latitude(),
 | |
|                                         radarSite->longitude());
 | |
|    }
 | |
| }
 | |
| 
 | |
| QFont SettingsDialogImpl::GetSelectedFont()
 | |
| {
 | |
|    std::string fontFamily = fontFamilies_.at(selectedFontCategory_)
 | |
|                                .GetSettingsVariable()
 | |
|                                ->GetStagedOrValue();
 | |
|    std::string fontStyle = fontStyles_.at(selectedFontCategory_)
 | |
|                               .GetSettingsVariable()
 | |
|                               ->GetStagedOrValue();
 | |
|    units::font_size::points<double> fontSize {
 | |
|       fontPointSizes_.at(selectedFontCategory_)
 | |
|          .GetSettingsVariable()
 | |
|          ->GetStagedOrValue()};
 | |
| 
 | |
|    QFont font = QFontDatabase::font(QString::fromStdString(fontFamily),
 | |
|                                     QString::fromStdString(fontStyle),
 | |
|                                     static_cast<int>(fontSize.value()));
 | |
|    font.setPointSizeF(fontSize.value());
 | |
| 
 | |
|    return font;
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::SelectFontCategory(types::FontCategory fontCategory)
 | |
| {
 | |
|    selectedFontCategory_ = fontCategory;
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::UpdateFontDisplayData()
 | |
| {
 | |
|    QFont font = GetSelectedFont();
 | |
| 
 | |
|    self_->ui->fontNameLabel->setText(font.family());
 | |
|    self_->ui->fontStyleLabel->setText(font.styleName());
 | |
|    self_->ui->fontSizeLabel->setText(QString::number(font.pointSizeF()));
 | |
| 
 | |
|    self_->ui->fontPreviewLabel->setFont(font);
 | |
| 
 | |
|    if (selectedFontCategory_ != types::FontCategory::Unknown)
 | |
|    {
 | |
|       auto& fontFamily = fontFamilies_.at(selectedFontCategory_);
 | |
|       auto& fontStyle  = fontStyles_.at(selectedFontCategory_);
 | |
|       auto& fontSize   = fontPointSizes_.at(selectedFontCategory_);
 | |
| 
 | |
|       self_->ui->resetFontButton->setVisible(!fontFamily.IsDefault() ||
 | |
|                                              !fontStyle.IsDefault() ||
 | |
|                                              !fontSize.IsDefault());
 | |
|    }
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::ApplyChanges()
 | |
| {
 | |
|    logger_->info("Applying settings changes");
 | |
| 
 | |
|    bool committed = false;
 | |
| 
 | |
|    for (auto& setting : settings_)
 | |
|    {
 | |
|       committed |= setting->Commit();
 | |
|    }
 | |
| 
 | |
|    if (committed)
 | |
|    {
 | |
|       manager::SettingsManager::Instance().SaveSettings();
 | |
|    }
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::DiscardChanges()
 | |
| {
 | |
|    logger_->info("Discarding settings changes");
 | |
| 
 | |
|    for (auto& setting : settings_)
 | |
|    {
 | |
|       setting->Reset();
 | |
|    }
 | |
| }
 | |
| 
 | |
| void SettingsDialogImpl::ResetToDefault()
 | |
| {
 | |
|    logger_->info("Restoring settings to default");
 | |
| 
 | |
|    for (auto& setting : settings_)
 | |
|    {
 | |
|       setting->StageDefault();
 | |
|    }
 | |
| }
 | |
| 
 | |
| std::string SettingsDialogImpl::RadarSiteLabel(
 | |
|    std::shared_ptr<config::RadarSite>& radarSite)
 | |
| {
 | |
|    return fmt::format("{} ({})", radarSite->id(), radarSite->location_name());
 | |
| }
 | |
| 
 | |
| } // namespace ui
 | |
| } // namespace qt
 | |
| } // namespace scwx
 | 
