diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 482aaad5..00abbaf4 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -50,9 +50,11 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp + source/scwx/qt/manager/radar_product_manager_notifier.hpp source/scwx/qt/manager/resource_manager.hpp source/scwx/qt/manager/settings_manager.hpp) set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp + source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp source/scwx/qt/manager/settings_manager.cpp) set(HDR_MAP source/scwx/qt/map/color_table_layer.hpp @@ -73,6 +75,12 @@ set(SRC_MAP source/scwx/qt/map/color_table_layer.cpp source/scwx/qt/map/overlay_layer.cpp source/scwx/qt/map/radar_product_layer.cpp source/scwx/qt/map/radar_range_layer.cpp) +set(HDR_MODEL source/scwx/qt/model/radar_product_model.hpp + source/scwx/qt/model/tree_item.hpp + source/scwx/qt/model/tree_model.hpp) +set(SRC_MODEL source/scwx/qt/model/radar_product_model.cpp + source/scwx/qt/model/tree_item.cpp + source/scwx/qt/model/tree_model.cpp) set(HDR_REQUEST source/scwx/qt/request/nexrad_file_request.hpp) set(SRC_REQUEST source/scwx/qt/request/nexrad_file_request.cpp) set(HDR_SETTINGS source/scwx/qt/settings/general_settings.hpp @@ -140,6 +148,8 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${UI_MAIN} ${HDR_MAP} ${SRC_MAP} + ${HDR_MODEL} + ${SRC_MODEL} ${HDR_REQUEST} ${SRC_REQUEST} ${HDR_SETTINGS} @@ -172,6 +182,8 @@ 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}) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index ececc023..f28a9688 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,24 @@ MainWindow::MainWindow(QWidget* parent) : ui->vcpValueLabel->setVisible(false); ui->vcpDescriptionLabel->setVisible(false); + // Configure Menu + ui->menuView->insertAction(ui->actionRadarToolbox, + ui->radarToolboxDock->toggleViewAction()); + ui->radarToolboxDock->toggleViewAction()->setText(tr("Radar &Toolbox")); + ui->actionRadarToolbox->setVisible(false); + + ui->menuView->insertAction(ui->actionResourceExplorer, + ui->resourceExplorerDock->toggleViewAction()); + ui->resourceExplorerDock->toggleViewAction()->setText( + tr("&Resource Explorer")); + ui->actionResourceExplorer->setVisible(false); + + // Configure Docks + ui->resourceExplorerDock->setVisible(false); + + ui->resourceTreeView->setModel(new model::RadarProductModel(this)); + + // Configure Map p->ConfigureMapLayout(); // Add Level 2 Products diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 56ee6e30..f5f1c23e 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -52,13 +52,21 @@ + + + &View + + + + + - Toolbox + Radar Toolbox 1 @@ -217,6 +225,21 @@ + + + Resource Explorer + + + 2 + + + + + + + + + E&xit @@ -235,6 +258,16 @@ Ctrl+O + + + Radar Toolbox + + + + + &Resource Explorer + + diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index 8a626476..117d681d 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -937,15 +938,29 @@ void RadarProductManager::UpdateAvailableProducts() std::shared_ptr RadarProductManager::Instance(const std::string& radarSite) { - std::lock_guard guard(instanceMutex_); + std::shared_ptr instance = nullptr; + bool instanceCreated = false; - if (!instanceMap_.contains(radarSite)) { - instanceMap_[radarSite] = - std::make_shared(radarSite); + std::lock_guard guard(instanceMutex_); + + if (!instanceMap_.contains(radarSite)) + { + instanceMap_[radarSite] = + std::make_shared(radarSite); + instanceCreated = true; + } + + instance = instanceMap_[radarSite]; } - return instanceMap_[radarSite]; + if (instanceCreated) + { + emit RadarProductManagerNotifier::Instance().RadarProductManagerCreated( + radarSite); + } + + return instance; } } // namespace manager diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.cpp new file mode 100644 index 00000000..b5c0a89b --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.cpp @@ -0,0 +1,34 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = + "scwx::qt::manager::radar_product_manager_notifier"; + +class RadarProductManagerNotifierImpl +{ +public: + explicit RadarProductManagerNotifierImpl() {} + ~RadarProductManagerNotifierImpl() {} +}; + +RadarProductManagerNotifier::RadarProductManagerNotifier() : + p(std::make_unique()) +{ +} +RadarProductManagerNotifier::~RadarProductManagerNotifier() = default; + +RadarProductManagerNotifier& RadarProductManagerNotifier::Instance() +{ + static RadarProductManagerNotifier instance_ {}; + return instance_; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.hpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.hpp new file mode 100644 index 00000000..163ba29f --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager_notifier.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class RadarProductManagerNotifierImpl; + +class RadarProductManagerNotifier : public QObject +{ + Q_OBJECT + +public: + explicit RadarProductManagerNotifier(); + ~RadarProductManagerNotifier(); + + static RadarProductManagerNotifier& Instance(); + +signals: + void RadarProductManagerCreated(const std::string& radarSite); + +private: + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/radar_product_model.cpp b/scwx-qt/source/scwx/qt/model/radar_product_model.cpp new file mode 100644 index 00000000..bb4ecebd --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/radar_product_model.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::radar_product_model"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class RadarProductModelImpl : public QObject +{ + Q_OBJECT + +public: + explicit RadarProductModelImpl(RadarProductModel* self); + ~RadarProductModelImpl() = default; + + RadarProductModel* self_; + std::shared_ptr rootItem_; +}; + +RadarProductModel::RadarProductModel(QObject* parent) : + TreeModel(parent), p(std::make_unique(this)) +{ +} +RadarProductModel::~RadarProductModel() = default; + +const std::shared_ptr RadarProductModel::root_item() const +{ + return p->rootItem_; +} + +RadarProductModelImpl::RadarProductModelImpl(RadarProductModel* self) : + self_ {self}, + rootItem_ {std::make_shared( + std::vector {QObject::tr("Product")})} +{ + connect( + &manager::RadarProductManagerNotifier::Instance(), + &manager::RadarProductManagerNotifier::RadarProductManagerCreated, + this, + [=](const std::string& radarSite) + { + logger_->debug("Adding radar site: {}", radarSite); + + const QModelIndex rootIndex = + self_->createIndex(rootItem_->row(), 0, rootItem_.get()); + const int rootChildren = rootItem_->child_count(); + + self_->beginInsertRows(rootIndex, rootChildren, rootChildren); + + TreeItem* radarSiteItem = + new TreeItem({QString::fromStdString(radarSite)}); + rootItem_->AppendChild(radarSiteItem); + + self_->endInsertRows(); + + connect( + manager::RadarProductManager::Instance(radarSite).get(), + &manager::RadarProductManager::NewDataAvailable, + this, + [=](common::RadarProductGroup group, + const std::string& product, + std::chrono::system_clock::time_point latestTime) + { + const QString groupName {QString::fromStdString( + common::GetRadarProductGroupName(group))}; + + // Find existing group item (e.g., Level 2, Level 3) + TreeItem* groupItem = radarSiteItem->FindChild(0, groupName); + + if (groupItem == nullptr) + { + // Existing group item was not found, create it + const QModelIndex radarSiteIndex = + self_->createIndex(radarSiteItem->row(), 0, radarSiteItem); + const int radarSiteChildren = radarSiteItem->child_count(); + + self_->beginInsertRows( + radarSiteIndex, radarSiteChildren, radarSiteChildren); + + groupItem = new TreeItem({groupName}); + radarSiteItem->AppendChild(groupItem); + + self_->endInsertRows(); + } + + TreeItem* productItem = nullptr; + + if (group == common::RadarProductGroup::Level2) + { + // Level 2 items are not separated by product + productItem = groupItem; + } + else + { + // Find existing product item (e.g., N0B, N0Q) + const QString productName {QString::fromStdString(product)}; + productItem = groupItem->FindChild(0, productName); + + if (productItem == nullptr) + { + // Existing product item was not found, create it + const QModelIndex groupItemIndex = + self_->createIndex(groupItem->row(), 0, groupItem); + const int groupItemChildren = groupItem->child_count(); + + self_->beginInsertRows( + groupItemIndex, groupItemChildren, groupItemChildren); + + productItem = new TreeItem({productName}); + groupItem->AppendChild(productItem); + + self_->endInsertRows(); + } + } + + // Create leaf item for product time + const QModelIndex productItemIndex = + self_->createIndex(productItem->row(), 0, productItem); + const int productItemChildren = productItem->child_count(); + + self_->beginInsertRows( + productItemIndex, productItemChildren, productItemChildren); + + productItem->AppendChild(new TreeItem { + QString::fromStdString(util::TimeString(latestTime))}); + + self_->endInsertRows(); + }, + Qt::QueuedConnection); + }); +} + +#include "radar_product_model.moc" + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/radar_product_model.hpp b/scwx-qt/source/scwx/qt/model/radar_product_model.hpp new file mode 100644 index 00000000..9fb45aa2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/radar_product_model.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class RadarProductModelImpl; + +class RadarProductModel : public TreeModel +{ +public: + explicit RadarProductModel(QObject* parent = nullptr); + ~RadarProductModel(); + +protected: + const std::shared_ptr root_item() const override; + +private: + std::unique_ptr p; + + friend class RadarProductModelImpl; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/tree_item.cpp b/scwx-qt/source/scwx/qt/model/tree_item.cpp new file mode 100644 index 00000000..e43ef4c2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/tree_item.cpp @@ -0,0 +1,138 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::tree_item"; + +class TreeItem::Impl +{ +public: + explicit Impl(const std::vector& data, TreeItem* parent) : + childItems_ {}, itemData_ {data}, parentItem_ {parent} + { + } + ~Impl() { qDeleteAll(childItems_); } + + std::vector childItems_; + std::vector itemData_; + TreeItem* parentItem_; +}; + +TreeItem::TreeItem(const std::vector& data, TreeItem* parent) : + p {std::make_unique(data, parent)} +{ +} + +TreeItem::TreeItem(std::initializer_list data, TreeItem* parent) : + TreeItem(std::vector {data}, parent) +{ +} + +TreeItem::~TreeItem() {} + +TreeItem::TreeItem(TreeItem&&) noexcept = default; +TreeItem& TreeItem::operator=(TreeItem&&) noexcept = default; + +void TreeItem::AppendChild(TreeItem* item) +{ + item->p->parentItem_ = this; + p->childItems_.push_back(item); +} + +TreeItem* TreeItem::FindChild(int column, const QVariant& data) +{ + auto it = std::find_if(p->childItems_.begin(), + p->childItems_.end(), + [&](auto& item) { + return (column < item->column_count() && + item->data(column) == data); + }); + + TreeItem* item = nullptr; + if (it != p->childItems_.end()) + { + item = *it; + } + + return item; +} + +const TreeItem* TreeItem::child(int row) const +{ + const TreeItem* item = nullptr; + + if (0 <= row && row < p->childItems_.size()) + { + item = p->childItems_[row]; + } + + return item; +} + +TreeItem* TreeItem::child(int row) +{ + TreeItem* item = nullptr; + + if (0 <= row && row < p->childItems_.size()) + { + item = p->childItems_[row]; + } + + return item; +} + +std::vector TreeItem::children() +{ + return p->childItems_; +} + +int TreeItem::child_count() const +{ + return static_cast(p->childItems_.size()); +} + +int TreeItem::column_count() const +{ + return static_cast(p->itemData_.size()); +} + +QVariant TreeItem::data(int column) const +{ + if (0 <= column && column < p->itemData_.size()) + { + return p->itemData_[column]; + } + else + { + return QVariant(); + } +} + +int TreeItem::row() const +{ + int row = 0; + + if (p->parentItem_ != nullptr) + { + const auto& childItems = p->parentItem_->p->childItems_; + row = + std::distance(childItems.cbegin(), + std::find(childItems.cbegin(), childItems.cend(), this)); + } + + return row; +} + +const TreeItem* TreeItem::parent_item() const +{ + return p->parentItem_; +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/tree_item.hpp b/scwx-qt/source/scwx/qt/model/tree_item.hpp new file mode 100644 index 00000000..de3eb54e --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/tree_item.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class TreeItem +{ +public: + explicit TreeItem(const std::vector& data, + TreeItem* parent = nullptr); + explicit TreeItem(std::initializer_list data, + TreeItem* parent = nullptr); + virtual ~TreeItem(); + + TreeItem(const TreeItem&) = delete; + TreeItem& operator=(const TreeItem&) = delete; + + TreeItem(TreeItem&&) noexcept; + TreeItem& operator=(TreeItem&&) noexcept; + + void AppendChild(TreeItem* child); + TreeItem* FindChild(int column, const QVariant& data); + + const TreeItem* child(int row) const; + TreeItem* child(int row); + std::vector children(); + int child_count() const; + int column_count() const; + QVariant data(int column) const; + int row() const; + const TreeItem* parent_item() const; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/tree_model.cpp b/scwx-qt/source/scwx/qt/model/tree_model.cpp new file mode 100644 index 00000000..e2be6382 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/tree_model.cpp @@ -0,0 +1,145 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::tree_model"; + +class TreeModelImpl +{ +public: + explicit TreeModelImpl() = default; + ~TreeModelImpl() = default; +}; + +TreeModel::TreeModel(QObject* parent) : + QAbstractItemModel(parent), p(std::make_unique()) +{ +} +TreeModel::~TreeModel() = default; + +int TreeModel::rowCount(const QModelIndex& parent) const +{ + const TreeItem* parentItem; + + if (parent.isValid()) + { + parentItem = static_cast(parent.constInternalPointer()); + } + else + { + parentItem = root_item().get(); + } + + return parentItem->child_count(); +} + +int TreeModel::columnCount(const QModelIndex& parent) const +{ + const TreeItem* parentItem; + + if (parent.isValid()) + { + parentItem = static_cast(parent.constInternalPointer()); + } + else + { + parentItem = root_item().get(); + } + + return parentItem->column_count(); +} + +QVariant TreeModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || role != Qt::DisplayRole) + { + return QVariant(); + } + + const TreeItem* item = static_cast(index.internalPointer()); + + return item->data(index.column()); +} + +Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags; + + if (!index.isValid()) + { + flags = Qt::NoItemFlags; + } + else + { + flags = QAbstractItemModel::flags(index); + } + + return flags; +} + +QVariant +TreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return root_item()->data(section); + } + + return QVariant(); +} + +QModelIndex +TreeModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + + const TreeItem* parentItem; + + if (!parent.isValid()) + { + parentItem = root_item().get(); + } + else + { + parentItem = static_cast(parent.constInternalPointer()); + } + + const TreeItem* childItem = parentItem->child(row); + if (childItem) + { + return createIndex(row, column, childItem); + } + + return QModelIndex(); +} + +QModelIndex TreeModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + { + return QModelIndex(); + } + + const TreeItem* childItem = + static_cast(index.constInternalPointer()); + const TreeItem* parentItem = childItem->parent_item(); + + if (parentItem == root_item().get() || parentItem == nullptr) + { + return QModelIndex(); + } + + return createIndex(parentItem->row(), 0, parentItem); +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/tree_model.hpp b/scwx-qt/source/scwx/qt/model/tree_model.hpp new file mode 100644 index 00000000..6c85c299 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/tree_model.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class TreeModelImpl; + +class TreeModel : public QAbstractItemModel +{ +public: + explicit TreeModel(QObject* parent = nullptr); + virtual ~TreeModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index(int row, + int column, + const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + +protected: + virtual const std::shared_ptr root_item() const = 0; + +private: + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx