mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 12:20:05 +00:00 
			
		
		
		
	Merge pull request #108 from dpaulat/feature/radar-presets
Radar Presets
This commit is contained in:
		
						commit
						0bec134096
					
				
					 10 changed files with 479 additions and 110 deletions
				
			
		
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/house-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/house-solid.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#000000" d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"/></svg> | ||||
| After Width: | Height: | Size: 734 B | 
							
								
								
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/star-solid.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scwx-qt/res/icons/font-awesome-6/star-solid.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#000000" d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg> | ||||
| After Width: | Height: | Size: 615 B | 
|  | @ -34,6 +34,7 @@ | |||
|         <file>res/icons/font-awesome-6/forward-step-solid.svg</file> | ||||
|         <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/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> | ||||
|  | @ -44,6 +45,7 @@ | |||
|         <file>res/icons/font-awesome-6/square-caret-right-regular.svg</file> | ||||
|         <file>res/icons/font-awesome-6/square-minus-regular.svg</file> | ||||
|         <file>res/icons/font-awesome-6/square-plus-regular.svg</file> | ||||
|         <file>res/icons/font-awesome-6/star-solid.svg</file> | ||||
|         <file>res/icons/font-awesome-6/stop-solid.svg</file> | ||||
|         <file>res/icons/font-awesome-6/volume-high-solid.svg</file> | ||||
|         <file>res/palettes/wct/CC.pal</file> | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include <scwx/qt/manager/update_manager.hpp> | ||||
| #include <scwx/qt/map/map_provider.hpp> | ||||
| #include <scwx/qt/map/map_widget.hpp> | ||||
| #include <scwx/qt/model/radar_site_model.hpp> | ||||
| #include <scwx/qt/settings/general_settings.hpp> | ||||
| #include <scwx/qt/settings/map_settings.hpp> | ||||
| #include <scwx/qt/settings/ui_settings.hpp> | ||||
|  | @ -89,10 +90,7 @@ public: | |||
|        textEventManager_ {manager::TextEventManager::Instance()}, | ||||
|        timelineManager_ {manager::TimelineManager::Instance()}, | ||||
|        updateManager_ {manager::UpdateManager::Instance()}, | ||||
|        maps_ {}, | ||||
|        elevationCuts_ {}, | ||||
|        elevationButtonsChanged_ {false}, | ||||
|        resizeElevationButtons_ {false} | ||||
|        maps_ {} | ||||
|    { | ||||
|       mapProvider_ = map::GetMapProvider( | ||||
|          settings::GeneralSettings::Instance().map_provider().GetValue()); | ||||
|  | @ -129,6 +127,7 @@ public: | |||
|    } | ||||
|    ~MainWindowImpl() { threadPool_.join(); } | ||||
| 
 | ||||
|    void AddRadarSitePreset(const std::string& id); | ||||
|    void AsyncSetup(); | ||||
|    void ConfigureMapLayout(); | ||||
|    void ConfigureMapStyles(); | ||||
|  | @ -187,14 +186,15 @@ public: | |||
|    std::shared_ptr<manager::TimelineManager>  timelineManager_; | ||||
|    std::shared_ptr<manager::UpdateManager>    updateManager_; | ||||
| 
 | ||||
|    std::shared_ptr<model::RadarSiteModel> radarSiteModel_ { | ||||
|       model::RadarSiteModel::Instance()}; | ||||
|    std::map<std::string, std::shared_ptr<QAction>> radarSitePresetsActions_ {}; | ||||
|    QMenu* radarSitePresetsMenu_ {nullptr}; | ||||
| 
 | ||||
|    std::vector<map::MapWidget*> maps_; | ||||
|    std::vector<float>           elevationCuts_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point volumeTime_ {}; | ||||
| 
 | ||||
|    bool elevationButtonsChanged_; | ||||
|    bool resizeElevationButtons_; | ||||
| 
 | ||||
| public slots: | ||||
|    void UpdateMapParameters(double latitude, | ||||
|                             double longitude, | ||||
|  | @ -213,10 +213,22 @@ MainWindow::MainWindow(QWidget* parent) : | |||
|    // Assign the bottom left corner to the left dock widget
 | ||||
|    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); | ||||
| 
 | ||||
|    // Configure Radar Site Box
 | ||||
|    ui->vcpLabel->setVisible(false); | ||||
|    ui->vcpValueLabel->setVisible(false); | ||||
|    ui->vcpDescriptionLabel->setVisible(false); | ||||
| 
 | ||||
|    p->radarSitePresetsMenu_ = new QMenu(this); | ||||
|    ui->radarSitePresetsButton->setMenu(p->radarSitePresetsMenu_); | ||||
| 
 | ||||
|    auto radarSitePresets = p->radarSiteModel_->presets(); | ||||
|    for (auto preset : radarSitePresets) | ||||
|    { | ||||
|       p->AddRadarSitePreset(preset); | ||||
|    } | ||||
| 
 | ||||
|    ui->radarSitePresetsButton->setVisible(!radarSitePresets.empty()); | ||||
| 
 | ||||
|    // Configure Alert Dock
 | ||||
|    p->alertDockWidget_ = new ui::AlertDockWidget(this); | ||||
|    p->alertDockWidget_->setVisible(false); | ||||
|  | @ -521,6 +533,19 @@ void MainWindow::on_actionAboutSupercellWx_triggered() | |||
|    p->aboutDialog_->show(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_radarSiteHomeButton_clicked() | ||||
| { | ||||
|    std::string homeRadarSite = | ||||
|       settings::GeneralSettings::Instance().default_radar_site().GetValue(); | ||||
| 
 | ||||
|    for (map::MapWidget* map : p->maps_) | ||||
|    { | ||||
|       map->SelectRadarSite(homeRadarSite); | ||||
|    } | ||||
| 
 | ||||
|    p->UpdateRadarSite(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_radarSiteSelectButton_clicked() | ||||
| { | ||||
|    p->radarSiteDialog_->show(); | ||||
|  | @ -931,6 +956,27 @@ void MainWindowImpl::ConnectOtherSignals() | |||
| 
 | ||||
|               UpdateRadarSite(); | ||||
|            }); | ||||
|    connect(radarSiteModel_.get(), | ||||
|            &model::RadarSiteModel::PresetToggled, | ||||
|            [this](const std::string& siteId, bool isPreset) | ||||
|            { | ||||
|               if (isPreset && !radarSitePresetsActions_.contains(siteId)) | ||||
|               { | ||||
|                  AddRadarSitePreset(siteId); | ||||
|               } | ||||
|               else if (!isPreset) | ||||
|               { | ||||
|                  auto entry = radarSitePresetsActions_.find(siteId); | ||||
|                  if (entry != radarSitePresetsActions_.cend()) | ||||
|                  { | ||||
|                     radarSitePresetsMenu_->removeAction(entry->second.get()); | ||||
|                     radarSitePresetsActions_.erase(entry); | ||||
|                  } | ||||
|               } | ||||
| 
 | ||||
|               mainWindow_->ui->radarSitePresetsButton->setVisible( | ||||
|                  !radarSitePresetsActions_.empty()); | ||||
|            }); | ||||
|    connect(updateManager_.get(), | ||||
|            &manager::UpdateManager::UpdateAvailable, | ||||
|            this, | ||||
|  | @ -942,6 +988,40 @@ void MainWindowImpl::ConnectOtherSignals() | |||
|            }); | ||||
| } | ||||
| 
 | ||||
| void MainWindowImpl::AddRadarSitePreset(const std::string& siteId) | ||||
| { | ||||
|    auto        radarSite = config::RadarSite::Get(siteId); | ||||
|    std::string actionText = | ||||
|       fmt::format("{}: {}", siteId, radarSite->location_name()); | ||||
| 
 | ||||
|    auto pair = radarSitePresetsActions_.emplace( | ||||
|       siteId, std::make_shared<QAction>(QString::fromStdString(actionText))); | ||||
|    auto& action = pair.first->second; | ||||
| 
 | ||||
|    QAction* before = nullptr; | ||||
| 
 | ||||
|    // If the radar site is not at the end
 | ||||
|    if (pair.first != std::prev(radarSitePresetsActions_.cend())) | ||||
|    { | ||||
|       // Insert before the next entry in the list
 | ||||
|       before = std::next(pair.first)->second.get(); | ||||
|    } | ||||
| 
 | ||||
|    radarSitePresetsMenu_->insertAction(before, action.get()); | ||||
| 
 | ||||
|    connect(action.get(), | ||||
|            &QAction::triggered, | ||||
|            [this, siteId]() | ||||
|            { | ||||
|               for (map::MapWidget* map : maps_) | ||||
|               { | ||||
|                  map->SelectRadarSite(siteId); | ||||
|               } | ||||
| 
 | ||||
|               UpdateRadarSite(); | ||||
|            }); | ||||
| } | ||||
| 
 | ||||
| void MainWindowImpl::HandleFocusChange(QWidget* focused) | ||||
| { | ||||
|    map::MapWidget* mapWidget = dynamic_cast<map::MapWidget*>(focused); | ||||
|  |  | |||
|  | @ -46,6 +46,7 @@ private slots: | |||
|    void on_actionGitHubRepository_triggered(); | ||||
|    void on_actionCheckForUpdates_triggered(); | ||||
|    void on_actionAboutSupercellWx_triggered(); | ||||
|    void on_radarSiteHomeButton_clicked(); | ||||
|    void on_radarSiteSelectButton_clicked(); | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ | |||
|      <x>0</x> | ||||
|      <y>0</y> | ||||
|      <width>1024</width> | ||||
|      <height>22</height> | ||||
|      <height>21</height> | ||||
|     </rect> | ||||
|    </property> | ||||
|    <widget class="QMenu" name="menuFile"> | ||||
|  | @ -141,8 +141,8 @@ | |||
|          <rect> | ||||
|           <x>0</x> | ||||
|           <y>0</y> | ||||
|           <width>157</width> | ||||
|           <height>697</height> | ||||
|           <width>193</width> | ||||
|           <height>688</height> | ||||
|          </rect> | ||||
|         </property> | ||||
|         <layout class="QVBoxLayout" name="verticalLayout_6"> | ||||
|  | @ -166,8 +166,126 @@ | |||
|            <property name="frameShadow"> | ||||
|             <enum>QFrame::Raised</enum> | ||||
|            </property> | ||||
|            <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0"> | ||||
|            <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0,0"> | ||||
|             <item row="0" column="2"> | ||||
|              <widget class="QLabel" name="radarSiteValueLabel"> | ||||
|               <property name="sizePolicy"> | ||||
|                <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> | ||||
|                 <horstretch>0</horstretch> | ||||
|                 <verstretch>0</verstretch> | ||||
|                </sizepolicy> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string notr="true">KLSX</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="3" column="0" colspan="2"> | ||||
|              <widget class="QLabel" name="vcpLabel"> | ||||
|               <property name="sizePolicy"> | ||||
|                <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> | ||||
|                 <horstretch>0</horstretch> | ||||
|                 <verstretch>0</verstretch> | ||||
|                </sizepolicy> | ||||
|               </property> | ||||
|               <property name="toolTip"> | ||||
|                <string>Volume Coverage Pattern</string> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string>VCP</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="4"> | ||||
|              <widget class="QFrame" name="frame"> | ||||
|               <property name="frameShape"> | ||||
|                <enum>QFrame::NoFrame</enum> | ||||
|               </property> | ||||
|               <property name="frameShadow"> | ||||
|                <enum>QFrame::Raised</enum> | ||||
|               </property> | ||||
|               <layout class="QHBoxLayout" name="horizontalLayout"> | ||||
|                <property name="leftMargin"> | ||||
|                 <number>0</number> | ||||
|                </property> | ||||
|                <property name="topMargin"> | ||||
|                 <number>0</number> | ||||
|                </property> | ||||
|                <property name="rightMargin"> | ||||
|                 <number>0</number> | ||||
|                </property> | ||||
|                <property name="bottomMargin"> | ||||
|                 <number>0</number> | ||||
|                </property> | ||||
|                <item> | ||||
|                 <widget class="QToolButton" name="radarSiteHomeButton"> | ||||
|                  <property name="maximumSize"> | ||||
|                   <size> | ||||
|                    <width>16777215</width> | ||||
|                    <height>13</height> | ||||
|                   </size> | ||||
|                  </property> | ||||
|                  <property name="text"> | ||||
|                   <string>...</string> | ||||
|                  </property> | ||||
|                  <property name="icon"> | ||||
|                   <iconset resource="../../../../scwx-qt.qrc"> | ||||
|                    <normaloff>:/res/icons/font-awesome-6/house-solid.svg</normaloff>:/res/icons/font-awesome-6/house-solid.svg</iconset> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QToolButton" name="radarSitePresetsButton"> | ||||
|                  <property name="maximumSize"> | ||||
|                   <size> | ||||
|                    <width>16777215</width> | ||||
|                    <height>13</height> | ||||
|                   </size> | ||||
|                  </property> | ||||
|                  <property name="text"> | ||||
|                   <string>...</string> | ||||
|                  </property> | ||||
|                  <property name="icon"> | ||||
|                   <iconset resource="../../../../scwx-qt.qrc"> | ||||
|                    <normaloff>:/res/icons/font-awesome-6/star-solid.svg</normaloff>:/res/icons/font-awesome-6/star-solid.svg</iconset> | ||||
|                  </property> | ||||
|                  <property name="popupMode"> | ||||
|                   <enum>QToolButton::InstantPopup</enum> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|               </layout> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="0" colspan="2"> | ||||
|              <widget class="QLabel" name="radarSiteLabel"> | ||||
|               <property name="text"> | ||||
|                <string>Radar Site</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="1" column="2" colspan="3"> | ||||
|              <widget class="QLabel" name="radarLocationLabel"> | ||||
|               <property name="text"> | ||||
|                <string notr="true">St. Louis, MO</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="3" column="2" colspan="3"> | ||||
|              <widget class="QLabel" name="vcpValueLabel"> | ||||
|               <property name="text"> | ||||
|                <string notr="true">35</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="4" column="2" colspan="3"> | ||||
|              <widget class="QLabel" name="vcpDescriptionLabel"> | ||||
|               <property name="text"> | ||||
|                <string>Clear Air Mode</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="3"> | ||||
|              <widget class="QToolButton" name="radarSiteSelectButton"> | ||||
|               <property name="maximumSize"> | ||||
|                <size> | ||||
|  | @ -180,51 +298,6 @@ | |||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="1" column="1" colspan="2"> | ||||
|              <widget class="QLabel" name="radarLocationLabel"> | ||||
|               <property name="text"> | ||||
|                <string notr="true">St. Louis, MO</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="2" column="0"> | ||||
|              <widget class="QLabel" name="vcpLabel"> | ||||
|               <property name="toolTip"> | ||||
|                <string>Volume Coverage Pattern</string> | ||||
|               </property> | ||||
|               <property name="text"> | ||||
|                <string>VCP</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="1"> | ||||
|              <widget class="QLabel" name="radarSiteValueLabel"> | ||||
|               <property name="text"> | ||||
|                <string notr="true">KLSX</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="0" column="0"> | ||||
|              <widget class="QLabel" name="radarSiteLabel"> | ||||
|               <property name="text"> | ||||
|                <string>Radar Site</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="3" column="1" colspan="2"> | ||||
|              <widget class="QLabel" name="vcpDescriptionLabel"> | ||||
|               <property name="text"> | ||||
|                <string>Clear Air Mode</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item row="2" column="1" colspan="2"> | ||||
|              <widget class="QLabel" name="vcpValueLabel"> | ||||
|               <property name="text"> | ||||
|                <string notr="true">35</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|            </layout> | ||||
|           </widget> | ||||
|          </item> | ||||
|  |  | |||
|  | @ -2,9 +2,17 @@ | |||
| #include <scwx/qt/config/radar_site.hpp> | ||||
| #include <scwx/qt/types/qt_types.hpp> | ||||
| #include <scwx/qt/util/geographic_lib.hpp> | ||||
| #include <scwx/qt/util/json.hpp> | ||||
| #include <scwx/common/geographic.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <filesystem> | ||||
| 
 | ||||
| #include <boost/json.hpp> | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <QIcon> | ||||
| #include <QStandardPaths> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
|  | @ -15,36 +23,138 @@ namespace model | |||
| static const std::string logPrefix_ = "scwx::qt::model::radar_site_model"; | ||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||
| 
 | ||||
| static constexpr size_t kColumnSiteId    = 0u; | ||||
| static constexpr size_t kColumnPlace     = 1u; | ||||
| static constexpr size_t kColumnState     = 2u; | ||||
| static constexpr size_t kColumnCountry   = 3u; | ||||
| static constexpr size_t kColumnLatitude  = 4u; | ||||
| static constexpr size_t kColumnLongitude = 5u; | ||||
| static constexpr size_t kColumnType      = 6u; | ||||
| static constexpr size_t kColumnDistance  = 7u; | ||||
| static constexpr size_t kNumColumns      = 8u; | ||||
| static constexpr int kFirstColumn = | ||||
|    static_cast<int>(RadarSiteModel::Column::SiteId); | ||||
| static constexpr int kLastColumn = | ||||
|    static_cast<int>(RadarSiteModel::Column::Preset); | ||||
| static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; | ||||
| 
 | ||||
| class RadarSiteModelImpl | ||||
| { | ||||
| public: | ||||
|    explicit RadarSiteModelImpl(); | ||||
|    explicit RadarSiteModelImpl() : | ||||
|        radarSites_ {}, | ||||
|        geodesic_(util::GeographicLib::DefaultGeodesic()), | ||||
|        distanceMap_ {}, | ||||
|        distanceDisplay_ {scwx::common::DistanceType::Miles}, | ||||
|        previousPosition_ {} | ||||
|    { | ||||
|       // Get all loaded radar sites
 | ||||
|       std::vector<std::shared_ptr<config::RadarSite>> radarSites = | ||||
|          config::RadarSite::GetAll(); | ||||
| 
 | ||||
|       // Setup radar site list
 | ||||
|       for (auto& site : radarSites) | ||||
|       { | ||||
|          distanceMap_[site->id()] = 0.0; | ||||
|          radarSites_.emplace_back(std::move(site)); | ||||
|       } | ||||
|    } | ||||
|    ~RadarSiteModelImpl() = default; | ||||
| 
 | ||||
|    void InitializePresets(); | ||||
|    void ReadPresets(); | ||||
|    void WritePresets(); | ||||
| 
 | ||||
|    QList<std::shared_ptr<config::RadarSite>> radarSites_; | ||||
|    std::unordered_set<std::string>           presets_ {}; | ||||
| 
 | ||||
|    std::string presetsPath_ {}; | ||||
| 
 | ||||
|    const GeographicLib::Geodesic& geodesic_; | ||||
| 
 | ||||
|    std::unordered_map<std::string, double> distanceMap_; | ||||
|    scwx::common::DistanceType              distanceDisplay_; | ||||
|    scwx::common::Coordinate                previousPosition_; | ||||
| 
 | ||||
|    QIcon starIcon_ {":/res/icons/font-awesome-6/star-solid.svg"}; | ||||
| }; | ||||
| 
 | ||||
| RadarSiteModel::RadarSiteModel(QObject* parent) : | ||||
|     QAbstractTableModel(parent), p(std::make_unique<RadarSiteModelImpl>()) | ||||
| { | ||||
|    p->InitializePresets(); | ||||
|    p->ReadPresets(); | ||||
| } | ||||
| 
 | ||||
| RadarSiteModel::~RadarSiteModel() | ||||
| { | ||||
|    // Write presets on shutdown
 | ||||
|    p->WritePresets(); | ||||
| }; | ||||
| 
 | ||||
| std::unordered_set<std::string> RadarSiteModel::presets() const | ||||
| { | ||||
|    return p->presets_; | ||||
| } | ||||
| 
 | ||||
| void RadarSiteModelImpl::InitializePresets() | ||||
| { | ||||
|    std::string appDataPath { | ||||
|       QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) | ||||
|          .toStdString()}; | ||||
| 
 | ||||
|    if (!std::filesystem::exists(appDataPath)) | ||||
|    { | ||||
|       if (!std::filesystem::create_directories(appDataPath)) | ||||
|       { | ||||
|          logger_->error("Unable to create application data directory: \"{}\"", | ||||
|                         appDataPath); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    presetsPath_ = appDataPath + "/radar-presets.json"; | ||||
| } | ||||
| 
 | ||||
| void RadarSiteModelImpl::ReadPresets() | ||||
| { | ||||
|    logger_->info("Reading presets"); | ||||
| 
 | ||||
|    boost::json::value presetsJson = nullptr; | ||||
| 
 | ||||
|    // Determine if presets exists
 | ||||
|    if (std::filesystem::exists(presetsPath_)) | ||||
|    { | ||||
|       presetsJson = util::json::ReadJsonFile(presetsPath_); | ||||
|    } | ||||
| 
 | ||||
|    // If presets was successfully read
 | ||||
|    if (presetsJson != nullptr && presetsJson.is_array()) | ||||
|    { | ||||
|       auto& presetsArray = presetsJson.as_array(); | ||||
|       for (auto& presetsEntry : presetsArray) | ||||
|       { | ||||
|          if (presetsEntry.is_string()) | ||||
|          { | ||||
|             // Get radar site ID from JSON value
 | ||||
|             std::string preset = | ||||
|                boost::json::value_to<std::string>(presetsEntry); | ||||
|             boost::to_upper(preset); | ||||
| 
 | ||||
|             // Find the preset in the list of radar sites
 | ||||
|             auto it = std::find_if( | ||||
|                radarSites_.cbegin(), | ||||
|                radarSites_.cend(), | ||||
|                [&preset](const std::shared_ptr<config::RadarSite>& radarSite) | ||||
|                { return (radarSite->id() == preset); }); | ||||
| 
 | ||||
|             // If a match, add to the presets
 | ||||
|             if (it != radarSites_.cend()) | ||||
|             { | ||||
|                presets_.insert(preset); | ||||
|             } | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void RadarSiteModelImpl::WritePresets() | ||||
| { | ||||
|    logger_->info("Saving presets"); | ||||
| 
 | ||||
|    auto presetsJson = boost::json::value_from(presets_); | ||||
|    util::json::WriteJsonFile(presetsPath_, presetsJson); | ||||
| } | ||||
| RadarSiteModel::~RadarSiteModel() = default; | ||||
| 
 | ||||
| int RadarSiteModel::rowCount(const QModelIndex& parent) const | ||||
| { | ||||
|  | @ -53,28 +163,32 @@ int RadarSiteModel::rowCount(const QModelIndex& parent) const | |||
| 
 | ||||
| int RadarSiteModel::columnCount(const QModelIndex& parent) const | ||||
| { | ||||
|    return parent.isValid() ? 0 : static_cast<int>(kNumColumns); | ||||
|    return parent.isValid() ? 0 : kNumColumns; | ||||
| } | ||||
| 
 | ||||
| QVariant RadarSiteModel::data(const QModelIndex& index, int role) const | ||||
| { | ||||
|    if (index.isValid() && index.row() >= 0 && | ||||
|        index.row() < p->radarSites_.size() && | ||||
|        (role == Qt::DisplayRole || role == types::SortRole)) | ||||
|    if (!index.isValid() || index.row() < 0 || | ||||
|        index.row() >= p->radarSites_.size()) | ||||
|    { | ||||
|       const auto& site = p->radarSites_.at(index.row()); | ||||
|       return QVariant(); | ||||
|    } | ||||
| 
 | ||||
|    const auto& site = p->radarSites_.at(index.row()); | ||||
| 
 | ||||
|    if (role == Qt::DisplayRole || role == types::SortRole) | ||||
|    { | ||||
|       switch (index.column()) | ||||
|       { | ||||
|       case kColumnSiteId: | ||||
|       case static_cast<int>(Column::SiteId): | ||||
|          return QString::fromStdString(site->id()); | ||||
|       case kColumnPlace: | ||||
|       case static_cast<int>(Column::Place): | ||||
|          return QString::fromStdString(site->place()); | ||||
|       case kColumnState: | ||||
|       case static_cast<int>(Column::State): | ||||
|          return QString::fromStdString(site->state()); | ||||
|       case kColumnCountry: | ||||
|       case static_cast<int>(Column::Country): | ||||
|          return QString::fromStdString(site->country()); | ||||
|       case kColumnLatitude: | ||||
|       case static_cast<int>(Column::Latitude): | ||||
|          if (role == Qt::DisplayRole) | ||||
|          { | ||||
|             return QString::fromStdString( | ||||
|  | @ -84,7 +198,7 @@ QVariant RadarSiteModel::data(const QModelIndex& index, int role) const | |||
|          { | ||||
|             return site->latitude(); | ||||
|          } | ||||
|       case kColumnLongitude: | ||||
|       case static_cast<int>(Column::Longitude): | ||||
|          if (role == Qt::DisplayRole) | ||||
|          { | ||||
|             return QString::fromStdString( | ||||
|  | @ -94,9 +208,9 @@ QVariant RadarSiteModel::data(const QModelIndex& index, int role) const | |||
|          { | ||||
|             return site->longitude(); | ||||
|          } | ||||
|       case kColumnType: | ||||
|       case static_cast<int>(Column::Type): | ||||
|          return QString::fromStdString(site->type_name()); | ||||
|       case kColumnDistance: | ||||
|       case static_cast<int>(Column::Distance): | ||||
|          if (role == Qt::DisplayRole) | ||||
|          { | ||||
|             if (p->distanceDisplay_ == scwx::common::DistanceType::Miles) | ||||
|  | @ -116,6 +230,26 @@ QVariant RadarSiteModel::data(const QModelIndex& index, int role) const | |||
|          { | ||||
|             return p->distanceMap_.at(site->id()); | ||||
|          } | ||||
|       case static_cast<int>(Column::Preset): | ||||
|          if (role == types::SortRole) | ||||
|          { | ||||
|             return QVariant(p->presets_.contains(site->id())); | ||||
|          } | ||||
|          break; | ||||
|       default: | ||||
|          break; | ||||
|       } | ||||
|    } | ||||
|    else if (role == Qt::DecorationRole) | ||||
|    { | ||||
|       switch (index.column()) | ||||
|       { | ||||
|       case static_cast<int>(Column::Preset): | ||||
|          if (p->presets_.contains(site->id())) | ||||
|          { | ||||
|             return p->starIcon_; | ||||
|          } | ||||
|          break; | ||||
|       default: | ||||
|          break; | ||||
|       } | ||||
|  | @ -134,27 +268,40 @@ QVariant RadarSiteModel::headerData(int             section, | |||
|       { | ||||
|          switch (section) | ||||
|          { | ||||
|          case kColumnSiteId: | ||||
|          case static_cast<int>(Column::SiteId): | ||||
|             return tr("Site ID"); | ||||
|          case kColumnPlace: | ||||
|          case static_cast<int>(Column::Place): | ||||
|             return tr("Place"); | ||||
|          case kColumnState: | ||||
|          case static_cast<int>(Column::State): | ||||
|             return tr("State"); | ||||
|          case kColumnCountry: | ||||
|          case static_cast<int>(Column::Country): | ||||
|             return tr("Country"); | ||||
|          case kColumnLatitude: | ||||
|          case static_cast<int>(Column::Latitude): | ||||
|             return tr("Latitude"); | ||||
|          case kColumnLongitude: | ||||
|          case static_cast<int>(Column::Longitude): | ||||
|             return tr("Longitude"); | ||||
|          case kColumnType: | ||||
|          case static_cast<int>(Column::Type): | ||||
|             return tr("Type"); | ||||
|          case kColumnDistance: | ||||
|          case static_cast<int>(Column::Distance): | ||||
|             return tr("Distance"); | ||||
|          default: | ||||
|             break; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|    else if (role == Qt::DecorationRole) | ||||
|    { | ||||
|       if (orientation == Qt::Horizontal) | ||||
|       { | ||||
|          switch (section) | ||||
|          { | ||||
|          case static_cast<int>(Column::Preset): | ||||
|             return p->starIcon_; | ||||
|          default: | ||||
|             break; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    return QVariant(); | ||||
| } | ||||
|  | @ -175,31 +322,54 @@ void RadarSiteModel::HandleMapUpdate(double latitude, double longitude) | |||
|       p->distanceMap_[site->id()] = distanceInMeters; | ||||
|    } | ||||
| 
 | ||||
|    QModelIndex topLeft     = createIndex(0, kColumnDistance); | ||||
|    QModelIndex bottomRight = createIndex(rowCount() - 1, kColumnDistance); | ||||
|    QModelIndex topLeft = createIndex(0, static_cast<int>(Column::Distance)); | ||||
|    QModelIndex bottomRight = | ||||
|       createIndex(rowCount() - 1, static_cast<int>(Column::Distance)); | ||||
| 
 | ||||
|    Q_EMIT dataChanged(topLeft, bottomRight); | ||||
| } | ||||
| 
 | ||||
| RadarSiteModelImpl::RadarSiteModelImpl() : | ||||
|     radarSites_ {}, | ||||
|     geodesic_(util::GeographicLib::DefaultGeodesic()), | ||||
|     distanceMap_ {}, | ||||
|     distanceDisplay_ {scwx::common::DistanceType::Miles}, | ||||
|     previousPosition_ {} | ||||
| void RadarSiteModel::TogglePreset(int row) | ||||
| { | ||||
|    // Get all loaded radar sites
 | ||||
|    std::vector<std::shared_ptr<config::RadarSite>> radarSites = | ||||
|       config::RadarSite::GetAll(); | ||||
| 
 | ||||
|    // Setup radar site list
 | ||||
|    for (auto& site : radarSites) | ||||
|    if (row >= 0 && row < p->radarSites_.size()) | ||||
|    { | ||||
|       distanceMap_[site->id()] = 0.0; | ||||
|       radarSites_.emplace_back(std::move(site)); | ||||
|       std::string siteId   = p->radarSites_.at(row)->id(); | ||||
|       bool        isPreset = false; | ||||
| 
 | ||||
|       // Attempt to erase the radar site from presets
 | ||||
|       if (p->presets_.erase(siteId) == 0) | ||||
|       { | ||||
|          // If the radar site did not exist, add it
 | ||||
|          p->presets_.insert(siteId); | ||||
|          isPreset = true; | ||||
|       } | ||||
| 
 | ||||
|       QModelIndex index = createIndex(row, static_cast<int>(Column::Preset)); | ||||
|       Q_EMIT dataChanged(index, index); | ||||
| 
 | ||||
|       Q_EMIT PresetToggled(siteId, isPreset); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<RadarSiteModel> RadarSiteModel::Instance() | ||||
| { | ||||
|    static std::weak_ptr<RadarSiteModel> radarSiteModelReference_ {}; | ||||
|    static std::mutex                    instanceMutex_ {}; | ||||
| 
 | ||||
|    std::unique_lock lock(instanceMutex_); | ||||
| 
 | ||||
|    std::shared_ptr<RadarSiteModel> radarSiteModel = | ||||
|       radarSiteModelReference_.lock(); | ||||
| 
 | ||||
|    if (radarSiteModel == nullptr) | ||||
|    { | ||||
|       radarSiteModel           = std::make_shared<RadarSiteModel>(); | ||||
|       radarSiteModelReference_ = radarSiteModel; | ||||
|    } | ||||
| 
 | ||||
|    return radarSiteModel; | ||||
| } | ||||
| 
 | ||||
| } // namespace model
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| #include <QAbstractTableModel> | ||||
| 
 | ||||
|  | @ -15,10 +16,27 @@ class RadarSiteModelImpl; | |||
| 
 | ||||
| class RadarSiteModel : public QAbstractTableModel | ||||
| { | ||||
|    Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|    enum class Column : int | ||||
|    { | ||||
|       SiteId    = 0, | ||||
|       Place     = 1, | ||||
|       State     = 2, | ||||
|       Country   = 3, | ||||
|       Latitude  = 4, | ||||
|       Longitude = 5, | ||||
|       Type      = 6, | ||||
|       Distance  = 7, | ||||
|       Preset    = 8 | ||||
|    }; | ||||
| 
 | ||||
|    explicit RadarSiteModel(QObject* parent = nullptr); | ||||
|    ~RadarSiteModel(); | ||||
| 
 | ||||
|    std::unordered_set<std::string> presets() const; | ||||
| 
 | ||||
|    int rowCount(const QModelIndex& parent = QModelIndex()) const override; | ||||
|    int columnCount(const QModelIndex& parent = QModelIndex()) const override; | ||||
| 
 | ||||
|  | @ -29,6 +47,12 @@ public: | |||
|                        int             role = Qt::DisplayRole) const override; | ||||
| 
 | ||||
|    void HandleMapUpdate(double latitude, double longitude); | ||||
|    void TogglePreset(int row); | ||||
| 
 | ||||
|    static std::shared_ptr<RadarSiteModel> Instance(); | ||||
| 
 | ||||
| signals: | ||||
|    void PresetToggled(const std::string& siteId, bool isPreset); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<RadarSiteModelImpl> p; | ||||
|  |  | |||
|  | @ -24,13 +24,13 @@ class RadarSiteDialogImpl | |||
| public: | ||||
|    explicit RadarSiteDialogImpl(RadarSiteDialog* self) : | ||||
|        self_ {self}, | ||||
|        radarSiteModel_ {new model::RadarSiteModel(self_)}, | ||||
|        proxyModel_ {new QSortFilterProxyModel(self_)}, | ||||
|        radarSiteModel_ {model::RadarSiteModel::Instance()}, | ||||
|        mapPosition_ {}, | ||||
|        mapUpdateDeferred_ {false}, | ||||
|        selectedRadarSite_ {"?"} | ||||
|    { | ||||
|       proxyModel_->setSourceModel(radarSiteModel_); | ||||
|       proxyModel_->setSourceModel(radarSiteModel_.get()); | ||||
|       proxyModel_->setSortRole(types::SortRole); | ||||
|       proxyModel_->setFilterCaseSensitivity(Qt::CaseInsensitive); | ||||
|       proxyModel_->setFilterKeyColumn(-1); | ||||
|  | @ -38,9 +38,10 @@ public: | |||
|    ~RadarSiteDialogImpl() = default; | ||||
| 
 | ||||
|    RadarSiteDialog*       self_; | ||||
|    model::RadarSiteModel* radarSiteModel_; | ||||
|    QSortFilterProxyModel* proxyModel_; | ||||
| 
 | ||||
|    std::shared_ptr<model::RadarSiteModel> radarSiteModel_; | ||||
| 
 | ||||
|    scwx::common::Coordinate mapPosition_; | ||||
|    bool                     mapUpdateDeferred_; | ||||
| 
 | ||||
|  | @ -70,9 +71,22 @@ RadarSiteDialog::RadarSiteDialog(QWidget* parent) : | |||
|            p->proxyModel_, | ||||
|            &QSortFilterProxyModel::setFilterWildcard); | ||||
|    connect(ui->radarSiteView, | ||||
|            &QTreeView::doubleClicked, | ||||
|            &QAbstractItemView::doubleClicked, | ||||
|            this, | ||||
|            [this]() { Q_EMIT accept(); }); | ||||
|    connect(ui->radarSiteView, | ||||
|            &QAbstractItemView::pressed, | ||||
|            this, | ||||
|            [this](const QModelIndex& index) | ||||
|            { | ||||
|               QModelIndex selectedIndex = p->proxyModel_->mapToSource(index); | ||||
| 
 | ||||
|               if (selectedIndex.column() == | ||||
|                   static_cast<int>(model::RadarSiteModel::Column::Preset)) | ||||
|               { | ||||
|                  p->radarSiteModel_->TogglePreset(selectedIndex.row()); | ||||
|               } | ||||
|            }); | ||||
|    connect( | ||||
|       ui->radarSiteView->selectionModel(), | ||||
|       &QItemSelectionModel::selectionChanged, | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>576</width> | ||||
|     <width>627</width> | ||||
|     <height>550</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|  | @ -16,6 +16,9 @@ | |||
|   <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|    <item> | ||||
|     <widget class="QTreeView" name="radarSiteView"> | ||||
|      <property name="editTriggers"> | ||||
|       <set>QAbstractItemView::CurrentChanged|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set> | ||||
|      </property> | ||||
|      <property name="alternatingRowColors"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat