From f0822205a43b47177a00259d6f103424daa55a53 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 12 Oct 2023 07:26:25 -0500 Subject: [PATCH] Initial layer model for layer manager --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/model/layer_model.cpp | 393 +++++++++++++++++++ scwx-qt/source/scwx/qt/model/layer_model.hpp | 72 ++++ scwx-qt/source/scwx/qt/ui/layer_dialog.cpp | 30 +- scwx-qt/source/scwx/qt/ui/layer_dialog.hpp | 1 + scwx-qt/source/scwx/qt/ui/layer_dialog.ui | 9 +- 6 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/model/layer_model.cpp create mode 100644 scwx-qt/source/scwx/qt/model/layer_model.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 0e0f2091..bd03dde8 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -120,6 +120,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp set(HDR_MODEL source/scwx/qt/model/alert_model.hpp source/scwx/qt/model/alert_proxy_model.hpp source/scwx/qt/model/imgui_context_model.hpp + source/scwx/qt/model/layer_model.hpp source/scwx/qt/model/placefile_model.hpp source/scwx/qt/model/radar_product_model.hpp source/scwx/qt/model/radar_site_model.hpp @@ -128,6 +129,7 @@ set(HDR_MODEL source/scwx/qt/model/alert_model.hpp set(SRC_MODEL source/scwx/qt/model/alert_model.cpp source/scwx/qt/model/alert_proxy_model.cpp source/scwx/qt/model/imgui_context_model.cpp + source/scwx/qt/model/layer_model.cpp source/scwx/qt/model/placefile_model.cpp source/scwx/qt/model/radar_product_model.cpp source/scwx/qt/model/radar_site_model.cpp diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp new file mode 100644 index 00000000..36847512 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -0,0 +1,393 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +static const std::string logPrefix_ = "scwx::qt::model::layer_model"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr int kFirstColumn = static_cast(LayerModel::Column::Order); +static constexpr int kLastColumn = + static_cast(LayerModel::Column::Description); +static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1; + +static const std::unordered_map + layerTypeNames_ {{LayerModel::LayerType::Map, "Map"}, + {LayerModel::LayerType::Radar, "Radar"}, + {LayerModel::LayerType::Alert, "Alert"}, + {LayerModel::LayerType::Placefile, "Placefile"}}; + +class LayerModelImpl +{ +public: + explicit LayerModelImpl() {} + ~LayerModelImpl() = default; + + std::shared_ptr placefileManager_ { + manager::PlacefileManager::Instance()}; + + std::vector>> + layers_ {}; +}; + +LayerModel::LayerModel(QObject* parent) : + QAbstractTableModel(parent), p(std::make_unique()) +{ + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileEnabled, + this, + &LayerModel::HandlePlacefileUpdate); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRemoved, + this, + &LayerModel::HandlePlacefileRemoved); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileRenamed, + this, + &LayerModel::HandlePlacefileRenamed); + + connect(p->placefileManager_.get(), + &manager::PlacefileManager::PlacefileUpdated, + this, + &LayerModel::HandlePlacefileUpdate); +} +LayerModel::~LayerModel() = default; + +int LayerModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : static_cast(p->layers_.size()); +} + +int LayerModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : kNumColumns; +} + +Qt::ItemFlags LayerModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + switch (index.column()) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable; + break; + + default: + break; + } + + return flags; +} + +QVariant LayerModel::data(const QModelIndex& index, int role) const +{ + static const QString enabledString = QObject::tr("Enabled"); + static const QString disabledString = QObject::tr("Disabled"); + + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->layers_.size()) + { + return QVariant(); + } + + const auto& layer = p->layers_.at(index.row()); + bool enabled = true; // TODO + + switch (index.column()) + { + case static_cast(Column::Order): + if (role == Qt::ItemDataRole::DisplayRole) + { + return index.row(); + } + break; + + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + // TODO + if (role == Qt::ItemDataRole::ToolTipRole) + { + return enabled ? enabledString : disabledString; + } + else if (role == Qt::ItemDataRole::CheckStateRole) + { + return static_cast(enabled ? Qt::CheckState::Checked : + Qt::CheckState::Unchecked); + } + break; + + case static_cast(Column::Type): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + return QString::fromStdString(layerTypeNames_.at(layer.first)); + } + break; + + case static_cast(Column::Description): + if (role == Qt::ItemDataRole::DisplayRole || + role == Qt::ItemDataRole::ToolTipRole) + { + if (layer.first == LayerType::Placefile) + { + std::string placefileName = std::get(layer.second); + std::string description = placefileName; + std::string title = + p->placefileManager_->placefile_title(placefileName); + if (!title.empty()) + { + description = title + '\n' + description; + } + + return QString::fromStdString(description); + } + else + { + if (std::holds_alternative(layer.second)) + { + return QString::fromStdString( + std::get(layer.second)); + } + } + } + break; + + default: + break; + } + + return QVariant(); +} + +QVariant +LayerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::ItemDataRole::DisplayRole) + { + if (orientation == Qt::Horizontal) + { + switch (section) + { + case static_cast(Column::EnabledMap1): + return tr("1"); + + case static_cast(Column::EnabledMap2): + return tr("2"); + + case static_cast(Column::EnabledMap3): + return tr("3"); + + case static_cast(Column::EnabledMap4): + return tr("4"); + + case static_cast(Column::Type): + return tr("Type"); + + case static_cast(Column::Description): + return tr("Description"); + + default: + break; + } + } + } + else if (role == Qt::ItemDataRole::ToolTipRole) + { + switch (section) + { + case static_cast(Column::Order): + return tr("Order"); + + case static_cast(Column::EnabledMap1): + return tr("Enabled on Map 1"); + + case static_cast(Column::EnabledMap2): + return tr("Enabled on Map 2"); + + case static_cast(Column::EnabledMap3): + return tr("Enabled on Map 3"); + + case static_cast(Column::EnabledMap4): + return tr("Enabled on Map 4"); + + default: + break; + } + } + else if (role == Qt::ItemDataRole::SizeHintRole) + { + switch (section) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + { + static const QCheckBox checkBox {}; + QStyleOptionButton option {}; + option.initFrom(&checkBox); + + // Width values from QCheckBox + return QApplication::style()->sizeFromContents( + QStyle::ContentsType::CT_CheckBox, + &option, + {option.iconSize.width() + 4, 0}); + } + + default: + break; + } + } + + return QVariant(); +} + +bool LayerModel::setData(const QModelIndex& index, + const QVariant& value, + int role) +{ + if (!index.isValid() || index.row() < 0 || + static_cast(index.row()) >= p->layers_.size()) + { + return false; + } + + const auto& layer = p->layers_.at(index.row()); + bool result = false; + + switch (index.column()) + { + case static_cast(Column::EnabledMap1): + case static_cast(Column::EnabledMap2): + case static_cast(Column::EnabledMap3): + case static_cast(Column::EnabledMap4): + if (role == Qt::ItemDataRole::CheckStateRole) + { + // TODO + Q_UNUSED(layer); + Q_UNUSED(value); + } + break; + + default: + break; + } + + if (result) + { + Q_EMIT dataChanged(index, index); + } + + return result; +} + +void LayerModel::HandlePlacefileRemoved(const std::string& name) +{ + auto it = std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == name; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, delete row + const int row = std::distance(p->layers_.begin(), it); + + beginRemoveRows(QModelIndex(), row, row); + p->layers_.erase(it); + endRemoveRows(); + } +} + +void LayerModel::HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName) +{ + auto it = + std::find_if(p->layers_.begin(), + p->layers_.end(), + [&oldName](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == oldName; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->layers_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + // Rename placefile + it->second = newName; + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->layers_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->layers_.push_back({LayerType::Placefile, newName}); + endInsertRows(); + } +} + +void LayerModel::HandlePlacefileUpdate(const std::string& name) +{ + auto it = std::find_if(p->layers_.begin(), + p->layers_.end(), + [&name](const auto& layer) + { + return layer.first == LayerType::Placefile && + std::get(layer.second) == name; + }); + + if (it != p->layers_.end()) + { + // Placefile exists, mark row as updated + const int row = std::distance(p->layers_.begin(), it); + QModelIndex topLeft = createIndex(row, kFirstColumn); + QModelIndex bottomRight = createIndex(row, kLastColumn); + + Q_EMIT dataChanged(topLeft, bottomRight); + } + else + { + // Placefile is new, append row + const int newIndex = static_cast(p->layers_.size()); + beginInsertRows(QModelIndex(), newIndex, newIndex); + p->layers_.push_back({LayerType::Placefile, name}); + endInsertRows(); + } +} + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/layer_model.hpp b/scwx-qt/source/scwx/qt/model/layer_model.hpp new file mode 100644 index 00000000..bb7c4828 --- /dev/null +++ b/scwx-qt/source/scwx/qt/model/layer_model.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace model +{ + +class LayerModelImpl; + +class LayerModel : public QAbstractTableModel +{ +public: + enum class Column : int + { + Order = 0, + EnabledMap1 = 1, + EnabledMap2 = 2, + EnabledMap3 = 3, + EnabledMap4 = 4, + Type = 5, + Description = 6 + }; + + enum class LayerType + { + Map, + Radar, + Alert, + Placefile + }; + + explicit LayerModel(QObject* parent = nullptr); + ~LayerModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, + const QVariant& value, + int role = Qt::EditRole) override; + +public slots: + void HandlePlacefileRemoved(const std::string& name); + void HandlePlacefileRenamed(const std::string& oldName, + const std::string& newName); + void HandlePlacefileUpdate(const std::string& name); + +private: + friend class LayerModelImpl; + std::unique_ptr p; +}; + +} // namespace model +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp index b5d535dc..3df3b06d 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.cpp @@ -1,6 +1,7 @@ #include "layer_dialog.hpp" #include "ui_layer_dialog.h" +#include #include namespace scwx @@ -16,16 +17,41 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class LayerDialogImpl { public: - explicit LayerDialogImpl() {} + explicit LayerDialogImpl(LayerDialog* self) : + layerModel_ {new model::LayerModel(self)} + { + } ~LayerDialogImpl() = default; + + model::LayerModel* layerModel_; }; LayerDialog::LayerDialog(QWidget* parent) : QDialog(parent), - p {std::make_unique()}, + p {std::make_unique(this)}, ui(new Ui::LayerDialog) { ui->setupUi(this); + + ui->layerTreeView->setModel(p->layerModel_); + + auto layerViewHeader = ui->layerTreeView->header(); + + layerViewHeader->setMinimumSectionSize(10); + + // Enabled columns have a fixed size (checkbox) + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap1), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap2), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap3), + QHeaderView::ResizeMode::ResizeToContents); + layerViewHeader->setSectionResizeMode( + static_cast(model::LayerModel::Column::EnabledMap4), + QHeaderView::ResizeMode::ResizeToContents); } LayerDialog::~LayerDialog() diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp index 738befe4..a8101d0d 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.hpp @@ -19,6 +19,7 @@ class LayerDialogImpl; class LayerDialog : public QDialog { Q_OBJECT + Q_DISABLE_COPY_MOVE(LayerDialog) public: explicit LayerDialog(QWidget* parent = nullptr); diff --git a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui index 8d2c4ed4..18b937c8 100644 --- a/scwx-qt/source/scwx/qt/ui/layer_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/layer_dialog.ui @@ -36,7 +36,14 @@ 0 - + + + true + + + QAbstractItemView::ContiguousSelection + +