diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 909cd93cda..9fca6040d4 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -23,7 +24,7 @@ namespace O3DE::ProjectManager m_layout = new QVBoxLayout(); m_layout->setSpacing(0); - m_layout->setMargin(0); + m_layout->setMargin(5); m_layout->setAlignment(Qt::AlignTop); setLayout(m_layout); @@ -41,74 +42,111 @@ namespace O3DE::ProjectManager hLayout->addWidget(closeButton); m_layout->addLayout(hLayout); - // enabled - { - m_enabledWidget = new QWidget(); - m_enabledWidget->setFixedWidth(s_width); - m_layout->addWidget(m_enabledWidget); - - QVBoxLayout* layout = new QVBoxLayout(); - layout->setAlignment(Qt::AlignTop); - m_enabledWidget->setLayout(layout); - - m_enabledLabel = new QLabel(); - m_enabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel"); - layout->addWidget(m_enabledLabel); - m_enabledTagContainer = new TagContainerWidget(); - layout->addWidget(m_enabledTagContainer); - } + // added + CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=] + { + QVector gems; + const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/false); - // disabled - { - m_disabledWidget = new QWidget(); - m_disabledWidget->setFixedWidth(s_width); - m_layout->addWidget(m_disabledWidget); - - QVBoxLayout* layout = new QVBoxLayout(); - layout->setAlignment(Qt::AlignTop); - m_disabledWidget->setLayout(layout); - - m_disabledLabel = new QLabel(); - m_disabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel"); - layout->addWidget(m_disabledLabel); - m_disabledTagContainer = new TagContainerWidget(); - layout->addWidget(m_disabledTagContainer); - } + // don't include gems that were already active because they were dependencies + for (const QModelIndex& modelIndex : toBeAdded) + { + if (!GemModel::WasPreviouslyAddedDependency(modelIndex)) + { + gems.push_back(modelIndex); + } + } + return gems; + }); - setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); + // removed + CreateGemSection( tr("Gem to be deactivated"), tr("Gems to be deactivated"), [=] + { + QVector gems; + const QVector toBeAdded = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/false); + + // don't include gems that are still active because they are dependencies + for (const QModelIndex& modelIndex : toBeAdded) + { + if (!GemModel::IsAddedDependency(modelIndex)) + { + gems.push_back(modelIndex); + } + } + return gems; + }); + + // added dependencies + CreateGemSection( tr("Dependency to be activated"), tr("Dependencies to be activated"), [=] + { + QVector dependencies; + const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true); + + // only include gems that are dependencies and not explicitly added + for (const QModelIndex& modelIndex : toBeAdded) + { + if (GemModel::IsAddedDependency(modelIndex) && !GemModel::IsAdded(modelIndex)) + { + dependencies.push_back(modelIndex); + } + } + return dependencies; + }); - Update(); - connect(gemModel, &GemModel::dataChanged, this, [=] + // removed dependencies + CreateGemSection( tr("Dependency to be deactivated"), tr("Dependencies to be deactivated"), [=] { - Update(); + QVector dependencies; + const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true); + + // don't include gems that were explicitly removed - those are listed in a different section + for (const QModelIndex& modelIndex : toBeRemoved) + { + if (!GemModel::WasPreviouslyAdded(modelIndex)) + { + dependencies.push_back(modelIndex); + } + } + return dependencies; }); + + setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); } - void CartOverlayWidget::Update() + void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices) { - const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); - if (toBeAdded.isEmpty()) - { - m_enabledWidget->hide(); - } - else - { - m_enabledTagContainer->Update(ConvertFromModelIndices(toBeAdded)); - m_enabledLabel->setText(QString("%1 %2").arg(QString::number(toBeAdded.size()), tr("Gems to be enabled"))); - m_enabledWidget->show(); - } + QWidget* widget = new QWidget(); + widget->setFixedWidth(s_width); + m_layout->addWidget(widget); - const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); - if (toBeRemoved.isEmpty()) - { - m_disabledWidget->hide(); - } - else + QVBoxLayout* layout = new QVBoxLayout(); + layout->setAlignment(Qt::AlignTop); + widget->setLayout(layout); + + QLabel* label = new QLabel(); + label->setObjectName("GemCatalogCartOverlaySectionLabel"); + layout->addWidget(label); + + TagContainerWidget* tagContainer = new TagContainerWidget(); + layout->addWidget(tagContainer); + + auto update = [=]() { - m_disabledTagContainer->Update(ConvertFromModelIndices(toBeRemoved)); - m_disabledLabel->setText(QString("%1 %2").arg(QString::number(toBeRemoved.size()), tr("Gems to be disabled"))); - m_disabledWidget->show(); - } + const QVector tagIndices = getTagIndices(); + if (tagIndices.isEmpty()) + { + widget->hide(); + } + else + { + tagContainer->Update(ConvertFromModelIndices(tagIndices)); + label->setText(QString("%1 %2").arg(tagIndices.size()).arg(tagIndices.size() == 1 ? singularTitle : pluralTitle)); + widget->show(); + } + }; + + connect(m_gemModel, &GemModel::dataChanged, this, update); + update(); } QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector& gems) const @@ -154,15 +192,15 @@ namespace O3DE::ProjectManager // Adjust the label text whenever the model gets updated. connect(gemModel, &GemModel::dataChanged, [=] { - const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); - const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); + const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true); + const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true); const int count = toBeAdded.size() + toBeRemoved.size(); m_countLabel->setText(QString::number(count)); m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty()); - // Automatically close the overlay window in case there are no gems to be enabled or disabled anymore. + // Automatically close the overlay window in case there are no gems to be activated or deactivated anymore. if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty()) { m_cartOverlay->deleteLater(); @@ -186,8 +224,8 @@ namespace O3DE::ProjectManager void CartButton::ShowOverlay() { - const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); - const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); + const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true); + const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true); if (toBeAdded.isEmpty() && toBeRemoved.isEmpty()) { return; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 4c21fbbbe3..2cfda4c790 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -8,6 +8,8 @@ #pragma once +#include + #if !defined(Q_MOC_RUN) #include #include @@ -30,22 +32,16 @@ namespace O3DE::ProjectManager public: CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr); - void Update(); private: QStringList ConvertFromModelIndices(const QVector& gems) const; + using GetTagIndicesCallback = AZStd::function()>; + void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); + QVBoxLayout* m_layout = nullptr; GemModel* m_gemModel = nullptr; - QWidget* m_enabledWidget = nullptr; - QLabel* m_enabledLabel = nullptr; - TagContainerWidget* m_enabledTagContainer = nullptr; - - QWidget* m_disabledWidget = nullptr; - QLabel* m_disabledLabel = nullptr; - TagContainerWidget* m_disabledTagContainer = nullptr; - inline constexpr static int s_width = 240; }; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 04d4d6999b..a41a81b448 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -100,6 +100,8 @@ namespace O3DE::ProjectManager m_gemModel->AddGem(gemInfo); } + m_gemModel->UpdateGemDependencies(); + // Gather enabled gems for the given project. auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); if (enabledGemNamesResult.IsSuccess()) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index c9ea138b55..b425c15dee 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -226,13 +226,20 @@ namespace O3DE::ProjectManager QVector elementCounts; const int totalGems = m_gemModel->rowCount(); const int selectedGemTotal = m_gemModel->TotalAddedGems(); + const int enabledGemTotal = m_gemModel->TotalAddedGems(/*includeDependencies=*/true); - elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Unselected)); + elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected)); elementCounts.push_back(totalGems - selectedGemTotal); - elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Selected)); + elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Selected)); elementCounts.push_back(selectedGemTotal); + elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive)); + elementCounts.push_back(totalGems - enabledGemTotal); + + elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Active)); + elementCounts.push_back(enabledGemTotal); + bool wasCollapsed = false; if (m_statusFilter) { @@ -253,48 +260,53 @@ namespace O3DE::ProjectManager m_statusFilter->deleteLater(); m_statusFilter = filterWidget; - const GemSortFilterProxyModel::GemStatus currentFilterState = m_filterProxyModel->GetGemStatus(); const QList buttons = m_statusFilter->GetButtonGroup()->buttons(); - for (int statusFilterIndex = 0; statusFilterIndex < buttons.size(); ++statusFilterIndex) - { - const GemSortFilterProxyModel::GemStatus gemStatus = static_cast(statusFilterIndex); - QAbstractButton* button = buttons[statusFilterIndex]; - if (static_cast(statusFilterIndex) == currentFilterState) + QAbstractButton* unselectedButton = buttons[0]; + QAbstractButton* selectedButton = buttons[1]; + unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected); + selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected); + + auto updateGemSelection = [=]([[maybe_unused]] bool checked) + { + if (unselectedButton->isChecked() && !selectedButton->isChecked()) + { + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected); + } + else if (!unselectedButton->isChecked() && selectedButton->isChecked()) { - button->setChecked(true); + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Selected); } + else + { + m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter); + } + }; + connect(unselectedButton, &QAbstractButton::toggled, this, updateGemSelection); + connect(selectedButton, &QAbstractButton::toggled, this, updateGemSelection); - connect( - button, &QAbstractButton::toggled, this, - [=](bool checked) - { - GemSortFilterProxyModel::GemStatus filterStatus = m_filterProxyModel->GetGemStatus(); - if (checked) - { - if (filterStatus == GemSortFilterProxyModel::GemStatus::NoFilter) - { - filterStatus = gemStatus; - } - else - { - filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter; - } - } - else - { - if (filterStatus != gemStatus) - { - filterStatus = static_cast(!gemStatus); - } - else - { - filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter; - } - } - m_filterProxyModel->SetGemStatus(filterStatus); - }); - } + QAbstractButton* inactiveButton = buttons[2]; + QAbstractButton* activeButton = buttons[3]; + inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive); + activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active); + + auto updateGemActive = [=]([[maybe_unused]] bool checked) + { + if (inactiveButton->isChecked() && !activeButton->isChecked()) + { + m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive); + } + else if (!inactiveButton->isChecked() && activeButton->isChecked()) + { + m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active); + } + else + { + m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::NoFilter); + } + }; + connect(inactiveButton, &QAbstractButton::toggled, this, updateGemActive); + connect(activeButton, &QAbstractButton::toggled, this, updateGemActive); } void GemFilterWidget::AddGemOriginFilter() @@ -487,7 +499,7 @@ namespace O3DE::ProjectManager const QString& feature = elementNames[i]; QAbstractButton* button = buttons[i]; - // Adjust the proxy model and enable or disable the clicked feature used for filtering. + // Adjust the proxy model and enable the clicked feature used for filtering. connect(button, &QAbstractButton::toggled, this, [=](bool checked) { QSet features = m_filterProxyModel->GetFeatures(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 4312b08998..311eeb93f6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -75,8 +75,7 @@ namespace O3DE::ProjectManager QString m_version = "Unknown Version"; QString m_lastUpdatedDate = "Unknown Date"; int m_binarySizeInKB = 0; - QStringList m_dependingGemUuids; - QStringList m_conflictingGemUuids; + QStringList m_dependencies; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 6dd6c52612..282aa2193e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -83,9 +83,8 @@ namespace O3DE::ProjectManager m_reqirementsTextLabel->hide(); } - // Depending and conflicting gems + // Depending gems m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex)); - m_conflictingGems->Update("Conflicting Gems", "The following Gems will be automatically disabled with this Gem.", m_model->GetConflictingGemNames(modelIndex)); // Additional information m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex))); @@ -173,15 +172,11 @@ namespace O3DE::ProjectManager m_mainLayout->addSpacing(20); - // Depending and conflicting gems + // Depending gems m_dependingGems = new GemsSubWidget(); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); - m_conflictingGems = new GemsSubWidget(); - m_mainLayout->addWidget(m_conflictingGems); - m_mainLayout->addSpacing(20); - // Additional information QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor); additionalInfoLabel->setText("Additional Information"); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index 97c23f7df2..0c41d1033c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -78,7 +78,6 @@ namespace O3DE::ProjectManager // Depending and conflicting gems GemsSubWidget* m_dependingGems = nullptr; - GemsSubWidget* m_conflictingGems = nullptr; // Additional information QLabel* m_versionLabel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index 99a2cd8db7..dc24c13009 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -8,9 +8,14 @@ #include #include +#include #include +#include #include #include +#include +#include +#include namespace O3DE::ProjectManager { @@ -149,8 +154,7 @@ namespace O3DE::ProjectManager return true; } } - - if (event->type() == QEvent::MouseButtonPress) + else if (event->type() == QEvent::MouseButtonPress ) { QMouseEvent* mouseEvent = static_cast(event); @@ -169,6 +173,69 @@ namespace O3DE::ProjectManager return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); } + QString GetGemNameList(const QVector modelIndices) + { + QString gemNameList; + for (int i = 0; i < modelIndices.size(); ++i) + { + if (!gemNameList.isEmpty()) + { + if (i == modelIndices.size() - 1) + { + gemNameList.append(" and "); + } + else + { + gemNameList.append(", "); + } + } + + gemNameList.append(GemModel::GetDisplayName(modelIndices[i])); + } + + return gemNameList; + } + + bool GemItemDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) + { + if (event->type() == QEvent::ToolTip) + { + QRect fullRect, itemRect, contentRect; + CalcRects(option, fullRect, itemRect, contentRect); + const QRect buttonRect = CalcButtonRect(contentRect); + if (buttonRect.contains(event->pos())) + { + if (!QToolTip::isVisible()) + { + if(GemModel::IsAddedDependency(index) && !GemModel::IsAdded(index)) + { + const GemModel* gemModel = GemModel::GetSourceModel(index.model()); + AZ_Assert(gemModel, "Failed to obtain GemModel"); + + // we only want to display the gems that must be de-selected to automatically + // disable this dependency, so don't include any that haven't been selected (added) + constexpr bool addedOnly = true; + QVector dependents = gemModel->GatherDependentGems(index, addedOnly); + QString nameList = GetGemNameList(dependents); + if (!nameList.isEmpty()) + { + QToolTip::showText(event->globalPos(), tr("This gem is a dependency of %1.\nTo disable this gem, first disable %1.").arg(nameList)); + } + } + } + return true; + } + else if (QToolTip::isVisible()) + { + QToolTip::hideText(); + event->ignore(); + return true; + } + } + + return QStyledItemDelegate::helpEvent(event, view, option, index); + } + void GemItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const { outFullRect = QRect(option.rect); @@ -260,14 +327,20 @@ namespace O3DE::ProjectManager const QRect buttonRect = CalcButtonRect(contentRect); QPoint circleCenter; - const bool isAdded = GemModel::IsAdded(modelIndex); - if (isAdded) + if (GemModel::IsAdded(modelIndex)) { painter->setBrush(m_buttonEnabledColor); painter->setPen(m_buttonEnabledColor); circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); } + else if (GemModel::IsAddedDependency(modelIndex)) + { + painter->setBrush(m_buttonImplicitlyEnabledColor); + painter->setPen(m_buttonImplicitlyEnabledColor); + + circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); + } else { circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index a0a3dbb36a..d842f63ae7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -29,7 +29,6 @@ namespace O3DE::ProjectManager ~GemItemDelegate() = default; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; - bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; // Colors @@ -39,6 +38,7 @@ namespace O3DE::ProjectManager const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the gem item const QColor m_borderColor = QColor("#1E70EB"); const QColor m_buttonEnabledColor = QColor("#00B931"); + const QColor m_buttonImplicitlyEnabledColor = QColor("#BCBCBE"); // Item inline constexpr static int s_height = 105; // Gem item total height @@ -65,6 +65,9 @@ namespace O3DE::ProjectManager inline constexpr static int s_featureTagSpacing = 7; protected: + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override; + bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) override; + void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp index 16234f4900..ab51c7511c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp @@ -60,11 +60,14 @@ namespace O3DE::ProjectManager QLabel* showCountLabel = new QLabel(); showCountLabel->setObjectName("GemCatalogHeaderShowCountLabel"); topLayout->addWidget(showCountLabel); - connect(proxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=] - { + + auto refreshGemCountUI = [=]() { const int numGemsShown = proxyModel->rowCount(); showCountLabel->setText(QString(tr("showing %1 Gems")).arg(numGemsShown)); - }); + }; + + connect(proxyModel, &GemSortFilterProxyModel::OnInvalidated, this, refreshGemCountUI); + connect(proxyModel->GetSourceModel(), &GemModel::dataChanged, this, refreshGemCountUI); topLayout->addSpacing(GemItemDelegate::s_contentMargins.right() + GemItemDelegate::s_borderWidth); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp index d393a30ed9..afdb9697c9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp @@ -9,9 +9,26 @@ #include #include #include +#include namespace O3DE::ProjectManager { + class GemListViewProxyStyle : public QProxyStyle + { + public: + using QProxyStyle::QProxyStyle; + int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override + { + if (hint == QStyle::SH_ToolTip_WakeUpDelay || hint == QStyle::SH_ToolTip_FallAsleepDelay) + { + // no delay + return 0; + } + + return QProxyStyle::styleHint(hint, option, widget, returnData); + } + }; + GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent) : QListView(parent) { @@ -21,5 +38,8 @@ namespace O3DE::ProjectManager setModel(model); setSelectionModel(selectionModel); setItemDelegate(new GemItemDelegate(model, this)); + + // use a custom proxy style so we get immediate tooltips for gem radio buttons + setStyle(new GemListViewProxyStyle(this->style())); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 7daea174e7..0941541793 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace O3DE::ProjectManager @@ -40,8 +41,7 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_documentationLink, RoleDocLink); - item->setData(gemInfo.m_dependingGemUuids, RoleDependingGems); - item->setData(gemInfo.m_conflictingGemUuids, RoleConflictingGems); + item->setData(gemInfo.m_dependencies, RoleDependingGems); item->setData(gemInfo.m_version, RoleVersion); item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); @@ -60,6 +60,39 @@ namespace O3DE::ProjectManager clear(); } + void GemModel::UpdateGemDependencies() + { + m_gemDependencyMap.clear(); + m_gemReverseDependencyMap.clear(); + + for (auto iter = m_nameToIndexMap.begin(); iter != m_nameToIndexMap.end(); ++iter) + { + const QString& key = iter.key(); + const QModelIndex modelIndex = iter.value(); + QSet dependencies; + GetAllDependingGems(modelIndex, dependencies); + if (!dependencies.isEmpty()) + { + m_gemDependencyMap.insert(key, dependencies); + } + } + + for (auto iter = m_gemDependencyMap.begin(); iter != m_gemDependencyMap.end(); ++iter) + { + const QString& dependant = iter.key(); + for (const QModelIndex& dependency : iter.value()) + { + const QString& dependencyName = dependency.data(RoleName).toString(); + if (!m_gemReverseDependencyMap.contains(dependencyName)) + { + m_gemReverseDependencyMap.insert(dependencyName, QSet()); + } + + m_gemReverseDependencyMap[dependencyName].insert(m_nameToIndexMap[dependant]); + } + } + } + QString GemModel::GetName(const QModelIndex& modelIndex) { return modelIndex.data(RoleName).toString(); @@ -125,49 +158,46 @@ namespace O3DE::ProjectManager return {}; } - void GemModel::FindGemNamesByNameStrings(QStringList& inOutGemNames) + void GemModel::FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames) { - for (QString& dependingGemString : inOutGemNames) + for (QString& name : inOutGemNames) { - QModelIndex modelIndex = FindIndexByNameString(dependingGemString); + QModelIndex modelIndex = FindIndexByNameString(name); if (modelIndex.isValid()) { - dependingGemString = GetDisplayName(modelIndex); + name = GetDisplayName(modelIndex); } } } - QStringList GemModel::GetDependingGemUuids(const QModelIndex& modelIndex) + QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex) { return modelIndex.data(RoleDependingGems).toStringList(); } - QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex) + void GemModel::GetAllDependingGems(const QModelIndex& modelIndex, QSet& inOutGems) { - QStringList result = GetDependingGemUuids(modelIndex); - if (result.isEmpty()) + QStringList dependencies = GetDependingGems(modelIndex); + for (const QString& dependency : dependencies) { - return {}; + QModelIndex dependencyIndex = FindIndexByNameString(dependency); + if (!inOutGems.contains(dependencyIndex)) + { + inOutGems.insert(dependencyIndex); + GetAllDependingGems(dependencyIndex, inOutGems); + } } - - FindGemNamesByNameStrings(result); - return result; } - QStringList GemModel::GetConflictingGemUuids(const QModelIndex& modelIndex) - { - return modelIndex.data(RoleConflictingGems).toStringList(); - } - - QStringList GemModel::GetConflictingGemNames(const QModelIndex& modelIndex) + QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex) { - QStringList result = GetConflictingGemUuids(modelIndex); + QStringList result = GetDependingGems(modelIndex); if (result.isEmpty()) { return {}; } - FindGemNamesByNameStrings(result); + FindGemDisplayNamesByNameStrings(result); return result; } @@ -201,29 +231,146 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleRequirement).toString(); } + GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) + { + GemSortFilterProxyModel* proxyModel = qobject_cast(model); + if (proxyModel) + { + return proxyModel->GetSourceModel(); + } + else + { + return qobject_cast(model); + } + } + + const GemModel* GemModel::GetSourceModel(const QAbstractItemModel* model) + { + const GemSortFilterProxyModel* proxyModel = qobject_cast(model); + if (proxyModel) + { + return proxyModel->GetSourceModel(); + } + else + { + return qobject_cast(model); + } + } + bool GemModel::IsAdded(const QModelIndex& modelIndex) { return modelIndex.data(RoleIsAdded).toBool(); } + bool GemModel::IsAddedDependency(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleIsAddedDependency).toBool(); + } + void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { model.setData(modelIndex, isAdded, RoleIsAdded); + + UpdateDependencies(model, modelIndex); + } + + bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const + { + QVector dependentGems = GatherDependentGems(modelIndex); + for (const QModelIndex& dependency : dependentGems) + { + if (IsAdded(dependency)) + { + return true; + } + } + return false; + } + + void GemModel::UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex) + { + GemModel* gemModel = GetSourceModel(&model); + AZ_Assert(gemModel, "Failed to obtain GemModel"); + + QVector dependencies = gemModel->GatherGemDependencies(modelIndex); + if (IsAdded(modelIndex)) + { + for (const QModelIndex& dependency : dependencies) + { + SetIsAddedDependency(*gemModel, dependency, true); + } + } + else + { + // still a dependency if some added gem depends on this one + SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex)); + + for (const QModelIndex& dependency : dependencies) + { + SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency)); + } + } + } + + void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) + { + model.setData(modelIndex, isAdded, RoleIsAddedDependency); } void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) { model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded); + + if (wasAdded) + { + // update all dependencies + GemModel* gemModel = GetSourceModel(&model); + AZ_Assert(gemModel, "Failed to obtain GemModel"); + QVector dependencies = gemModel->GatherGemDependencies(modelIndex); + for (const QModelIndex& dependency : dependencies) + { + SetWasPreviouslyAddedDependency(*gemModel, dependency, true); + } + } + } + + void GemModel::SetWasPreviouslyAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) + { + model.setData(modelIndex, wasAdded, RoleWasPreviouslyAddedDependency); } - bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex) + bool GemModel::WasPreviouslyAdded(const QModelIndex& modelIndex) { - return (!modelIndex.data(RoleWasPreviouslyAdded).toBool() && modelIndex.data(RoleIsAdded).toBool()); + return modelIndex.data(RoleWasPreviouslyAdded).toBool(); } - bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex) + bool GemModel::WasPreviouslyAddedDependency(const QModelIndex& modelIndex) { - return (modelIndex.data(RoleWasPreviouslyAdded).toBool() && !modelIndex.data(RoleIsAdded).toBool()); + return modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); + } + + bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies) + { + bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool(); + bool added = modelIndex.data(RoleIsAdded).toBool(); + if (includeDependencies) + { + previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); + added |= modelIndex.data(RoleIsAddedDependency).toBool(); + } + return !previouslyAdded && added; + } + + bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies) + { + bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool(); + bool added = modelIndex.data(RoleIsAdded).toBool(); + if (includeDependencies) + { + previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); + added |= modelIndex.data(RoleIsAddedDependency).toBool(); + } + return previouslyAdded && !added; } bool GemModel::HasRequirement(const QModelIndex& modelIndex) @@ -244,13 +391,44 @@ namespace O3DE::ProjectManager return false; } - QVector GemModel::GatherGemsToBeAdded() const + QVector GemModel::GatherGemDependencies(const QModelIndex& modelIndex) const + { + QVector result; + const QString& gemName = modelIndex.data(RoleName).toString(); + if (m_gemDependencyMap.contains(gemName)) + { + for (const QModelIndex& dependency : m_gemDependencyMap[gemName]) + { + result.push_back(dependency); + } + } + return result; + } + + QVector GemModel::GatherDependentGems(const QModelIndex& modelIndex, bool addedOnly) const + { + QVector result; + const QString& gemName = modelIndex.data(RoleName).toString(); + if (m_gemReverseDependencyMap.contains(gemName)) + { + for (const QModelIndex& dependency : m_gemReverseDependencyMap[gemName]) + { + if (!addedOnly || GemModel::IsAdded(dependency)) + { + result.push_back(dependency); + } + } + } + return result; + } + + QVector GemModel::GatherGemsToBeAdded(bool includeDependencies) const { QVector result; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); - if (NeedsToBeAdded(modelIndex)) + if (NeedsToBeAdded(modelIndex, includeDependencies)) { result.push_back(modelIndex); } @@ -258,13 +436,13 @@ namespace O3DE::ProjectManager return result; } - QVector GemModel::GatherGemsToBeRemoved() const + QVector GemModel::GatherGemsToBeRemoved(bool includeDependencies) const { QVector result; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); - if (NeedsToBeRemoved(modelIndex)) + if (NeedsToBeRemoved(modelIndex, includeDependencies)) { result.push_back(modelIndex); } @@ -272,13 +450,13 @@ namespace O3DE::ProjectManager return result; } - int GemModel::TotalAddedGems() const + int GemModel::TotalAddedGems(bool includeDependencies) const { int result = 0; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); - if (IsAdded(modelIndex)) + if (IsAdded(modelIndex) || (includeDependencies && IsAddedDependency(modelIndex))) { ++result; } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index ce004ee875..0591094c11 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -28,13 +28,11 @@ namespace O3DE::ProjectManager void AddGem(const GemInfo& gemInfo); void Clear(); + void UpdateGemDependencies(); QModelIndex FindIndexByNameString(const QString& nameString) const; - void FindGemNamesByNameStrings(QStringList& inOutGemNames); - QStringList GetDependingGemUuids(const QModelIndex& modelIndex); QStringList GetDependingGemNames(const QModelIndex& modelIndex); - QStringList GetConflictingGemUuids(const QModelIndex& modelIndex); - QStringList GetConflictingGemNames(const QModelIndex& modelIndex); + bool HasDependentGems(const QModelIndex& modelIndex) const; static QString GetName(const QModelIndex& modelIndex); static QString GetDisplayName(const QModelIndex& modelIndex); @@ -51,22 +49,36 @@ namespace O3DE::ProjectManager static QStringList GetFeatures(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex); static QString GetRequirement(const QModelIndex& modelIndex); + static GemModel* GetSourceModel(QAbstractItemModel* model); + static const GemModel* GetSourceModel(const QAbstractItemModel* model); static bool IsAdded(const QModelIndex& modelIndex); + static bool IsAddedDependency(const QModelIndex& modelIndex); static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); + static void SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); static void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded); - static bool NeedsToBeAdded(const QModelIndex& modelIndex); - static bool NeedsToBeRemoved(const QModelIndex& modelIndex); + static bool WasPreviouslyAdded(const QModelIndex& modelIndex); + static void SetWasPreviouslyAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded); + static bool WasPreviouslyAddedDependency(const QModelIndex& modelIndex); + static bool NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies = false); + static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false); static bool HasRequirement(const QModelIndex& modelIndex); + static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex); bool DoGemsToBeAddedHaveRequirements() const; - QVector GatherGemsToBeAdded() const; - QVector GatherGemsToBeRemoved() const; + QVector GatherGemDependencies(const QModelIndex& modelIndex) const; + QVector GatherDependentGems(const QModelIndex& modelIndex, bool addedOnly = false) const; + QVector GatherGemsToBeAdded(bool includeDependencies = false) const; + QVector GatherGemsToBeRemoved(bool includeDependencies = false) const; - int TotalAddedGems() const; + int TotalAddedGems(bool includeDependencies = false) const; private: + void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); + void GetAllDependingGems(const QModelIndex& modelIndex, QSet& inOutGems); + QStringList GetDependingGems(const QModelIndex& modelIndex); + enum UserRole { RoleName = Qt::UserRole, @@ -76,11 +88,12 @@ namespace O3DE::ProjectManager RolePlatforms, RoleSummary, RoleWasPreviouslyAdded, + RoleWasPreviouslyAddedDependency, RoleIsAdded, + RoleIsAddedDependency, RoleDirectoryLink, RoleDocLink, RoleDependingGems, - RoleConflictingGems, RoleVersion, RoleLastUpdated, RoleBinarySize, @@ -92,5 +105,7 @@ namespace O3DE::ProjectManager QHash m_nameToIndexMap; QItemSelectionModel* m_selectionModel = nullptr; + QHash> m_gemDependencyMap; + QHash> m_gemReverseDependencyMap; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp index 6edfced6e5..199692f200 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp @@ -50,11 +50,21 @@ namespace O3DE::ProjectManager } } - // Gem status - if (m_gemStatusFilter != GemStatus::NoFilter) + // Gem selected + if (m_gemSelectedFilter != GemSelected::NoFilter) { - const GemStatus sourceGemStatus = static_cast(GemModel::IsAdded(sourceIndex)); - if (m_gemStatusFilter != sourceGemStatus) + const GemSelected sourceGemStatus = static_cast(GemModel::IsAdded(sourceIndex)); + if (m_gemSelectedFilter != sourceGemStatus) + { + return false; + } + } + + // Gem enabled + if (m_gemActiveFilter != GemActive::NoFilter) + { + const GemActive sourceGemStatus = static_cast(GemModel::IsAdded(sourceIndex) || GemModel::IsAddedDependency(sourceIndex)); + if (m_gemActiveFilter != sourceGemStatus) { return false; } @@ -148,19 +158,31 @@ namespace O3DE::ProjectManager return true; } - QString GemSortFilterProxyModel::GetGemStatusString(GemStatus status) + QString GemSortFilterProxyModel::GetGemSelectedString(GemSelected status) { switch (status) { - case Unselected: + case GemSelected::Unselected: return "Unselected"; - case Selected: + case GemSelected::Selected: return "Selected"; default: - return ""; + return ""; } } + QString GemSortFilterProxyModel::GetGemActiveString(GemActive status) + { + switch (status) + { + case GemActive::Inactive: + return "Inactive"; + case GemActive::Active: + return "Active"; + default: + return ""; + } + } void GemSortFilterProxyModel::InvalidateFilter() { invalidate(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h index 4ec170aef2..74b1e915eb 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h @@ -25,16 +25,23 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - enum GemStatus + enum class GemSelected { NoFilter = -1, Unselected, Selected }; + enum class GemActive + { + NoFilter = -1, + Inactive, + Active + }; GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr); - static QString GetGemStatusString(GemStatus status); + static QString GetGemSelectedString(GemSelected status); + static QString GetGemActiveString(GemActive status); bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; @@ -43,8 +50,11 @@ namespace O3DE::ProjectManager void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); } - GemStatus GetGemStatus() const { return m_gemStatusFilter; } - void SetGemStatus(GemStatus gemStatus) { m_gemStatusFilter = gemStatus; InvalidateFilter(); } + GemSelected GetGemSelected() const { return m_gemSelectedFilter; } + void SetGemSelected(GemSelected selected) { m_gemSelectedFilter = selected; InvalidateFilter(); } + + GemActive GetGemActive() const { return m_gemActiveFilter; } + void SetGemActive(GemActive enabled) { m_gemActiveFilter = enabled; InvalidateFilter(); } GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; } void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); } @@ -69,7 +79,8 @@ namespace O3DE::ProjectManager AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr; QString m_searchString; - GemStatus m_gemStatusFilter = GemStatus::NoFilter; + GemSelected m_gemSelectedFilter = GemSelected::NoFilter; + GemActive m_gemActiveFilter = GemActive::NoFilter; GemInfo::GemOrigins m_gemOriginFilter = {}; GemInfo::Platforms m_platformFilter = {}; GemInfo::Types m_typeFilter = {}; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index c92ad53cd4..9f0c6a51ba 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -675,6 +675,14 @@ namespace O3DE::ProjectManager } } + if (data.contains("dependencies")) + { + for (auto dependency : data["dependencies"]) + { + gemInfo.m_dependencies.push_back(Py_To_String(dependency)); + } + } + QString gemType = Py_To_String_Optional(data, "type", ""); if (gemType == "Asset") { diff --git a/Code/Tools/ProjectManager/project_manager_tests_files.cmake b/Code/Tools/ProjectManager/project_manager_tests_files.cmake index 2b22ced910..2bfe343038 100644 --- a/Code/Tools/ProjectManager/project_manager_tests_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_tests_files.cmake @@ -11,6 +11,7 @@ set(FILES Resources/ProjectManager.qss tests/ApplicationTests.cpp tests/PythonBindingsTests.cpp + tests/GemCatalogTests.cpp tests/main.cpp tests/UtilsTests.cpp ) diff --git a/Code/Tools/ProjectManager/tests/GemCatalogTests.cpp b/Code/Tools/ProjectManager/tests/GemCatalogTests.cpp new file mode 100644 index 0000000000..f5c6d5196a --- /dev/null +++ b/Code/Tools/ProjectManager/tests/GemCatalogTests.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + + +namespace O3DE::ProjectManager +{ + class GemCatalogTests + : public ::UnitTest::ScopedAllocatorSetupFixture + { + public: + + GemCatalogTests() = default; + }; + + TEST_F(GemCatalogTests, GemCatalog_Displays_But_Does_Not_Add_Dependencies) + { + GemModel* gemModel = new GemModel(); + + // given 3 gems a,b,c where a depends on b which depends on c + GemInfo gemA, gemB, gemC; + QModelIndex indexA, indexB, indexC; + gemA.m_name = "a"; + gemB.m_name = "b"; + gemC.m_name = "c"; + + gemA.m_dependencies = QStringList({ "b" }); + gemB.m_dependencies = QStringList({ "c" }); + + gemModel->AddGem(gemA); + indexA = gemModel->FindIndexByNameString(gemA.m_name); + + gemModel->AddGem(gemB); + indexB = gemModel->FindIndexByNameString(gemB.m_name); + + gemModel->AddGem(gemC); + indexC = gemModel->FindIndexByNameString(gemC.m_name); + + gemModel->UpdateGemDependencies(); + + EXPECT_FALSE(GemModel::IsAdded(indexA)); + EXPECT_FALSE(GemModel::IsAddedDependency(indexB) || GemModel::IsAddedDependency(indexC)); + + // when a is added + GemModel::SetIsAdded(*gemModel, indexA, true); + + // expect b and c are now dependencies of an added gem but not themselves added + // cmake will handle dependencies + EXPECT_TRUE(GemModel::IsAddedDependency(indexB) && GemModel::IsAddedDependency(indexC)); + EXPECT_TRUE(!GemModel::IsAdded(indexB) && !GemModel::IsAdded(indexC)); + + QVector gemsToAdd = gemModel->GatherGemsToBeAdded(); + EXPECT_TRUE(gemsToAdd.size() == 1); + EXPECT_EQ(GemModel::GetName(gemsToAdd.at(0)), gemA.m_name); + } +}