Project Manager Gem Dependencies (#4132)

* Fix engine API change and add gem dependencies

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Add GemCatalog dependency test

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Clarify display name and fix missing const

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Moving a couple helper functions into private scope

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Update gem count when unselecting a gem #4074

This addresses the following issue https://github.com/o3de/o3de/issues/4074

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Active/Inactive filter and dependency tooltips

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Accessors for previously added and dependencies

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Cart displays gem dependency changes

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Shorten titles to fit in summary popup

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Remove QString::number

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Remove extra space

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Consolidate source model accesor helpers

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Addressing minor feedback

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>

* Remove unused local variable

Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com>
monroegm-disable-blank-issue-2
Alex Peterson 4 years ago committed by GitHub
parent 4c0796f11e
commit 43244d30e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,7 @@
*/ */
#include <GemCatalog/GemCatalogHeaderWidget.h> #include <GemCatalog/GemCatalogHeaderWidget.h>
#include <AzCore/std/functional.h>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMouseEvent> #include <QMouseEvent>
#include <QLabel> #include <QLabel>
@ -23,7 +24,7 @@ namespace O3DE::ProjectManager
m_layout = new QVBoxLayout(); m_layout = new QVBoxLayout();
m_layout->setSpacing(0); m_layout->setSpacing(0);
m_layout->setMargin(0); m_layout->setMargin(5);
m_layout->setAlignment(Qt::AlignTop); m_layout->setAlignment(Qt::AlignTop);
setLayout(m_layout); setLayout(m_layout);
@ -41,74 +42,111 @@ namespace O3DE::ProjectManager
hLayout->addWidget(closeButton); hLayout->addWidget(closeButton);
m_layout->addLayout(hLayout); m_layout->addLayout(hLayout);
// enabled // added
{ CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=]
m_enabledWidget = new QWidget(); {
m_enabledWidget->setFixedWidth(s_width); QVector<QModelIndex> gems;
m_layout->addWidget(m_enabledWidget); const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/false);
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);
}
// disabled // don't include gems that were already active because they were dependencies
{ for (const QModelIndex& modelIndex : toBeAdded)
m_disabledWidget = new QWidget(); {
m_disabledWidget->setFixedWidth(s_width); if (!GemModel::WasPreviouslyAddedDependency(modelIndex))
m_layout->addWidget(m_disabledWidget); {
gems.push_back(modelIndex);
QVBoxLayout* layout = new QVBoxLayout(); }
layout->setAlignment(Qt::AlignTop); }
m_disabledWidget->setLayout(layout); return gems;
});
m_disabledLabel = new QLabel();
m_disabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
layout->addWidget(m_disabledLabel);
m_disabledTagContainer = new TagContainerWidget();
layout->addWidget(m_disabledTagContainer);
}
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); // removed
CreateGemSection( tr("Gem to be deactivated"), tr("Gems to be deactivated"), [=]
{
QVector<QModelIndex> gems;
const QVector<QModelIndex> 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<QModelIndex> dependencies;
const QVector<QModelIndex> 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(); // removed dependencies
connect(gemModel, &GemModel::dataChanged, this, [=] CreateGemSection( tr("Dependency to be deactivated"), tr("Dependencies to be deactivated"), [=]
{ {
Update(); QVector<QModelIndex> dependencies;
const QVector<QModelIndex> 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<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(); QWidget* widget = new QWidget();
if (toBeAdded.isEmpty()) widget->setFixedWidth(s_width);
{ m_layout->addWidget(widget);
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();
}
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); QVBoxLayout* layout = new QVBoxLayout();
if (toBeRemoved.isEmpty()) layout->setAlignment(Qt::AlignTop);
{ widget->setLayout(layout);
m_disabledWidget->hide();
} QLabel* label = new QLabel();
else label->setObjectName("GemCatalogCartOverlaySectionLabel");
layout->addWidget(label);
TagContainerWidget* tagContainer = new TagContainerWidget();
layout->addWidget(tagContainer);
auto update = [=]()
{ {
m_disabledTagContainer->Update(ConvertFromModelIndices(toBeRemoved)); const QVector<QModelIndex> tagIndices = getTagIndices();
m_disabledLabel->setText(QString("%1 %2").arg(QString::number(toBeRemoved.size()), tr("Gems to be disabled"))); if (tagIndices.isEmpty())
m_disabledWidget->show(); {
} 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<QModelIndex>& gems) const QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector<QModelIndex>& gems) const
@ -154,15 +192,15 @@ namespace O3DE::ProjectManager
// Adjust the label text whenever the model gets updated. // Adjust the label text whenever the model gets updated.
connect(gemModel, &GemModel::dataChanged, [=] connect(gemModel, &GemModel::dataChanged, [=]
{ {
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(); const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
const int count = toBeAdded.size() + toBeRemoved.size(); const int count = toBeAdded.size() + toBeRemoved.size();
m_countLabel->setText(QString::number(count)); m_countLabel->setText(QString::number(count));
m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty()); 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()) if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
{ {
m_cartOverlay->deleteLater(); m_cartOverlay->deleteLater();
@ -186,8 +224,8 @@ namespace O3DE::ProjectManager
void CartButton::ShowOverlay() void CartButton::ShowOverlay()
{ {
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(); const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty()) if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
{ {
return; return;

@ -8,6 +8,8 @@
#pragma once #pragma once
#include <AzCore/std/function/function_fwd.h>
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <AzQtComponents/Components/SearchLineEdit.h> #include <AzQtComponents/Components/SearchLineEdit.h>
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
@ -30,22 +32,16 @@ namespace O3DE::ProjectManager
public: public:
CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr); CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr);
void Update();
private: private:
QStringList ConvertFromModelIndices(const QVector<QModelIndex>& gems) const; QStringList ConvertFromModelIndices(const QVector<QModelIndex>& gems) const;
using GetTagIndicesCallback = AZStd::function<QVector<QModelIndex>()>;
void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices);
QVBoxLayout* m_layout = nullptr; QVBoxLayout* m_layout = nullptr;
GemModel* m_gemModel = 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; inline constexpr static int s_width = 240;
}; };

@ -100,6 +100,8 @@ namespace O3DE::ProjectManager
m_gemModel->AddGem(gemInfo); m_gemModel->AddGem(gemInfo);
} }
m_gemModel->UpdateGemDependencies();
// Gather enabled gems for the given project. // Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
if (enabledGemNamesResult.IsSuccess()) if (enabledGemNamesResult.IsSuccess())

@ -226,13 +226,20 @@ namespace O3DE::ProjectManager
QVector<int> elementCounts; QVector<int> elementCounts;
const int totalGems = m_gemModel->rowCount(); const int totalGems = m_gemModel->rowCount();
const int selectedGemTotal = m_gemModel->TotalAddedGems(); 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); 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); 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; bool wasCollapsed = false;
if (m_statusFilter) if (m_statusFilter)
{ {
@ -253,48 +260,53 @@ namespace O3DE::ProjectManager
m_statusFilter->deleteLater(); m_statusFilter->deleteLater();
m_statusFilter = filterWidget; m_statusFilter = filterWidget;
const GemSortFilterProxyModel::GemStatus currentFilterState = m_filterProxyModel->GetGemStatus();
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons(); const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
for (int statusFilterIndex = 0; statusFilterIndex < buttons.size(); ++statusFilterIndex)
{
const GemSortFilterProxyModel::GemStatus gemStatus = static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex);
QAbstractButton* button = buttons[statusFilterIndex];
if (static_cast<GemSortFilterProxyModel::GemStatus>(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( QAbstractButton* inactiveButton = buttons[2];
button, &QAbstractButton::toggled, this, QAbstractButton* activeButton = buttons[3];
[=](bool checked) inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive);
{ activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active);
GemSortFilterProxyModel::GemStatus filterStatus = m_filterProxyModel->GetGemStatus();
if (checked) auto updateGemActive = [=]([[maybe_unused]] bool checked)
{ {
if (filterStatus == GemSortFilterProxyModel::GemStatus::NoFilter) if (inactiveButton->isChecked() && !activeButton->isChecked())
{ {
filterStatus = gemStatus; m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive);
} }
else else if (!inactiveButton->isChecked() && activeButton->isChecked())
{ {
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter; m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active);
} }
} else
else {
{ m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::NoFilter);
if (filterStatus != gemStatus) }
{ };
filterStatus = static_cast<GemSortFilterProxyModel::GemStatus>(!gemStatus); connect(inactiveButton, &QAbstractButton::toggled, this, updateGemActive);
} connect(activeButton, &QAbstractButton::toggled, this, updateGemActive);
else
{
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter;
}
}
m_filterProxyModel->SetGemStatus(filterStatus);
});
}
} }
void GemFilterWidget::AddGemOriginFilter() void GemFilterWidget::AddGemOriginFilter()
@ -487,7 +499,7 @@ namespace O3DE::ProjectManager
const QString& feature = elementNames[i]; const QString& feature = elementNames[i];
QAbstractButton* button = buttons[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) connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{ {
QSet<QString> features = m_filterProxyModel->GetFeatures(); QSet<QString> features = m_filterProxyModel->GetFeatures();

@ -75,8 +75,7 @@ namespace O3DE::ProjectManager
QString m_version = "Unknown Version"; QString m_version = "Unknown Version";
QString m_lastUpdatedDate = "Unknown Date"; QString m_lastUpdatedDate = "Unknown Date";
int m_binarySizeInKB = 0; int m_binarySizeInKB = 0;
QStringList m_dependingGemUuids; QStringList m_dependencies;
QStringList m_conflictingGemUuids;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -83,9 +83,8 @@ namespace O3DE::ProjectManager
m_reqirementsTextLabel->hide(); 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_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 // Additional information
m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex))); m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
@ -173,15 +172,11 @@ namespace O3DE::ProjectManager
m_mainLayout->addSpacing(20); m_mainLayout->addSpacing(20);
// Depending and conflicting gems // Depending gems
m_dependingGems = new GemsSubWidget(); m_dependingGems = new GemsSubWidget();
m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addWidget(m_dependingGems);
m_mainLayout->addSpacing(20); m_mainLayout->addSpacing(20);
m_conflictingGems = new GemsSubWidget();
m_mainLayout->addWidget(m_conflictingGems);
m_mainLayout->addSpacing(20);
// Additional information // Additional information
QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor); QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor);
additionalInfoLabel->setText("Additional Information"); additionalInfoLabel->setText("Additional Information");

@ -78,7 +78,6 @@ namespace O3DE::ProjectManager
// Depending and conflicting gems // Depending and conflicting gems
GemsSubWidget* m_dependingGems = nullptr; GemsSubWidget* m_dependingGems = nullptr;
GemsSubWidget* m_conflictingGems = nullptr;
// Additional information // Additional information
QLabel* m_versionLabel = nullptr; QLabel* m_versionLabel = nullptr;

@ -8,9 +8,14 @@
#include <GemCatalog/GemItemDelegate.h> #include <GemCatalog/GemItemDelegate.h>
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <QEvent> #include <QEvent>
#include <QAbstractItemView>
#include <QPainter> #include <QPainter>
#include <QMouseEvent> #include <QMouseEvent>
#include <QHelpEvent>
#include <QToolTip>
#include <QHoverEvent>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
@ -149,8 +154,7 @@ namespace O3DE::ProjectManager
return true; return true;
} }
} }
else if (event->type() == QEvent::MouseButtonPress )
if (event->type() == QEvent::MouseButtonPress)
{ {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event); QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
@ -169,6 +173,69 @@ namespace O3DE::ProjectManager
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
} }
QString GetGemNameList(const QVector<QModelIndex> 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<QModelIndex> 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 void GemItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const
{ {
outFullRect = QRect(option.rect); outFullRect = QRect(option.rect);
@ -260,14 +327,20 @@ namespace O3DE::ProjectManager
const QRect buttonRect = CalcButtonRect(contentRect); const QRect buttonRect = CalcButtonRect(contentRect);
QPoint circleCenter; QPoint circleCenter;
const bool isAdded = GemModel::IsAdded(modelIndex); if (GemModel::IsAdded(modelIndex))
if (isAdded)
{ {
painter->setBrush(m_buttonEnabledColor); painter->setBrush(m_buttonEnabledColor);
painter->setPen(m_buttonEnabledColor); painter->setPen(m_buttonEnabledColor);
circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); 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 else
{ {
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1); circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1);

@ -29,7 +29,6 @@ namespace O3DE::ProjectManager
~GemItemDelegate() = default; ~GemItemDelegate() = default;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; 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; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
// Colors // Colors
@ -39,6 +38,7 @@ namespace O3DE::ProjectManager
const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the gem item const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the gem item
const QColor m_borderColor = QColor("#1E70EB"); const QColor m_borderColor = QColor("#1E70EB");
const QColor m_buttonEnabledColor = QColor("#00B931"); const QColor m_buttonEnabledColor = QColor("#00B931");
const QColor m_buttonImplicitlyEnabledColor = QColor("#BCBCBE");
// Item // Item
inline constexpr static int s_height = 105; // Gem item total height 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; inline constexpr static int s_featureTagSpacing = 7;
protected: 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; void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcButtonRect(const QRect& contentRect) const;

@ -60,11 +60,14 @@ namespace O3DE::ProjectManager
QLabel* showCountLabel = new QLabel(); QLabel* showCountLabel = new QLabel();
showCountLabel->setObjectName("GemCatalogHeaderShowCountLabel"); showCountLabel->setObjectName("GemCatalogHeaderShowCountLabel");
topLayout->addWidget(showCountLabel); topLayout->addWidget(showCountLabel);
connect(proxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=]
{ auto refreshGemCountUI = [=]() {
const int numGemsShown = proxyModel->rowCount(); const int numGemsShown = proxyModel->rowCount();
showCountLabel->setText(QString(tr("showing %1 Gems")).arg(numGemsShown)); 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); topLayout->addSpacing(GemItemDelegate::s_contentMargins.right() + GemItemDelegate::s_borderWidth);

@ -9,9 +9,26 @@
#include <GemCatalog/GemListView.h> #include <GemCatalog/GemListView.h>
#include <GemCatalog/GemItemDelegate.h> #include <GemCatalog/GemItemDelegate.h>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QProxyStyle>
namespace O3DE::ProjectManager 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) GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
: QListView(parent) : QListView(parent)
{ {
@ -21,5 +38,8 @@ namespace O3DE::ProjectManager
setModel(model); setModel(model);
setSelectionModel(selectionModel); setSelectionModel(selectionModel);
setItemDelegate(new GemItemDelegate(model, this)); 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 } // namespace O3DE::ProjectManager

@ -8,6 +8,7 @@
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h> #include <AzCore/Casting/numeric_cast.h>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
@ -40,8 +41,7 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_isAdded, RoleIsAdded);
item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink);
item->setData(gemInfo.m_documentationLink, RoleDocLink); item->setData(gemInfo.m_documentationLink, RoleDocLink);
item->setData(gemInfo.m_dependingGemUuids, RoleDependingGems); item->setData(gemInfo.m_dependencies, RoleDependingGems);
item->setData(gemInfo.m_conflictingGemUuids, RoleConflictingGems);
item->setData(gemInfo.m_version, RoleVersion); item->setData(gemInfo.m_version, RoleVersion);
item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated);
item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize);
@ -60,6 +60,39 @@ namespace O3DE::ProjectManager
clear(); 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<QModelIndex> 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<QModelIndex>());
}
m_gemReverseDependencyMap[dependencyName].insert(m_nameToIndexMap[dependant]);
}
}
}
QString GemModel::GetName(const QModelIndex& modelIndex) QString GemModel::GetName(const QModelIndex& modelIndex)
{ {
return modelIndex.data(RoleName).toString(); return modelIndex.data(RoleName).toString();
@ -125,49 +158,46 @@ namespace O3DE::ProjectManager
return {}; 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()) 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(); return modelIndex.data(RoleDependingGems).toStringList();
} }
QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex) void GemModel::GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems)
{ {
QStringList result = GetDependingGemUuids(modelIndex); QStringList dependencies = GetDependingGems(modelIndex);
if (result.isEmpty()) 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) QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleConflictingGems).toStringList();
}
QStringList GemModel::GetConflictingGemNames(const QModelIndex& modelIndex)
{ {
QStringList result = GetConflictingGemUuids(modelIndex); QStringList result = GetDependingGems(modelIndex);
if (result.isEmpty()) if (result.isEmpty())
{ {
return {}; return {};
} }
FindGemNamesByNameStrings(result); FindGemDisplayNamesByNameStrings(result);
return result; return result;
} }
@ -201,29 +231,146 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleRequirement).toString(); return modelIndex.data(RoleRequirement).toString();
} }
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
{
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
if (proxyModel)
{
return proxyModel->GetSourceModel();
}
else
{
return qobject_cast<GemModel*>(model);
}
}
const GemModel* GemModel::GetSourceModel(const QAbstractItemModel* model)
{
const GemSortFilterProxyModel* proxyModel = qobject_cast<const GemSortFilterProxyModel*>(model);
if (proxyModel)
{
return proxyModel->GetSourceModel();
}
else
{
return qobject_cast<const GemModel*>(model);
}
}
bool GemModel::IsAdded(const QModelIndex& modelIndex) bool GemModel::IsAdded(const QModelIndex& modelIndex)
{ {
return modelIndex.data(RoleIsAdded).toBool(); 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) void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
{ {
model.setData(modelIndex, isAdded, RoleIsAdded); model.setData(modelIndex, isAdded, RoleIsAdded);
UpdateDependencies(model, modelIndex);
}
bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const
{
QVector<QModelIndex> 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<QModelIndex> 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) void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded)
{ {
model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded); model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded);
if (wasAdded)
{
// update all dependencies
GemModel* gemModel = GetSourceModel(&model);
AZ_Assert(gemModel, "Failed to obtain GemModel");
QVector<QModelIndex> 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) bool GemModel::HasRequirement(const QModelIndex& modelIndex)
@ -244,13 +391,44 @@ namespace O3DE::ProjectManager
return false; return false;
} }
QVector<QModelIndex> GemModel::GatherGemsToBeAdded() const QVector<QModelIndex> GemModel::GatherGemDependencies(const QModelIndex& modelIndex) const
{
QVector<QModelIndex> 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<QModelIndex> GemModel::GatherDependentGems(const QModelIndex& modelIndex, bool addedOnly) const
{
QVector<QModelIndex> 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<QModelIndex> GemModel::GatherGemsToBeAdded(bool includeDependencies) const
{ {
QVector<QModelIndex> result; QVector<QModelIndex> result;
for (int row = 0; row < rowCount(); ++row) for (int row = 0; row < rowCount(); ++row)
{ {
const QModelIndex modelIndex = index(row, 0); const QModelIndex modelIndex = index(row, 0);
if (NeedsToBeAdded(modelIndex)) if (NeedsToBeAdded(modelIndex, includeDependencies))
{ {
result.push_back(modelIndex); result.push_back(modelIndex);
} }
@ -258,13 +436,13 @@ namespace O3DE::ProjectManager
return result; return result;
} }
QVector<QModelIndex> GemModel::GatherGemsToBeRemoved() const QVector<QModelIndex> GemModel::GatherGemsToBeRemoved(bool includeDependencies) const
{ {
QVector<QModelIndex> result; QVector<QModelIndex> result;
for (int row = 0; row < rowCount(); ++row) for (int row = 0; row < rowCount(); ++row)
{ {
const QModelIndex modelIndex = index(row, 0); const QModelIndex modelIndex = index(row, 0);
if (NeedsToBeRemoved(modelIndex)) if (NeedsToBeRemoved(modelIndex, includeDependencies))
{ {
result.push_back(modelIndex); result.push_back(modelIndex);
} }
@ -272,13 +450,13 @@ namespace O3DE::ProjectManager
return result; return result;
} }
int GemModel::TotalAddedGems() const int GemModel::TotalAddedGems(bool includeDependencies) const
{ {
int result = 0; int result = 0;
for (int row = 0; row < rowCount(); ++row) for (int row = 0; row < rowCount(); ++row)
{ {
const QModelIndex modelIndex = index(row, 0); const QModelIndex modelIndex = index(row, 0);
if (IsAdded(modelIndex)) if (IsAdded(modelIndex) || (includeDependencies && IsAddedDependency(modelIndex)))
{ {
++result; ++result;
} }

@ -28,13 +28,11 @@ namespace O3DE::ProjectManager
void AddGem(const GemInfo& gemInfo); void AddGem(const GemInfo& gemInfo);
void Clear(); void Clear();
void UpdateGemDependencies();
QModelIndex FindIndexByNameString(const QString& nameString) const; QModelIndex FindIndexByNameString(const QString& nameString) const;
void FindGemNamesByNameStrings(QStringList& inOutGemNames);
QStringList GetDependingGemUuids(const QModelIndex& modelIndex);
QStringList GetDependingGemNames(const QModelIndex& modelIndex); QStringList GetDependingGemNames(const QModelIndex& modelIndex);
QStringList GetConflictingGemUuids(const QModelIndex& modelIndex); bool HasDependentGems(const QModelIndex& modelIndex) const;
QStringList GetConflictingGemNames(const QModelIndex& modelIndex);
static QString GetName(const QModelIndex& modelIndex); static QString GetName(const QModelIndex& modelIndex);
static QString GetDisplayName(const QModelIndex& modelIndex); static QString GetDisplayName(const QModelIndex& modelIndex);
@ -51,22 +49,36 @@ namespace O3DE::ProjectManager
static QStringList GetFeatures(const QModelIndex& modelIndex); static QStringList GetFeatures(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex);
static QString GetRequirement(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 IsAdded(const QModelIndex& modelIndex);
static bool IsAddedDependency(const QModelIndex& modelIndex);
static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); 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 void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded);
static bool NeedsToBeAdded(const QModelIndex& modelIndex); static bool WasPreviouslyAdded(const QModelIndex& modelIndex);
static bool NeedsToBeRemoved(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 bool HasRequirement(const QModelIndex& modelIndex);
static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex);
bool DoGemsToBeAddedHaveRequirements() const; bool DoGemsToBeAddedHaveRequirements() const;
QVector<QModelIndex> GatherGemsToBeAdded() const; QVector<QModelIndex> GatherGemDependencies(const QModelIndex& modelIndex) const;
QVector<QModelIndex> GatherGemsToBeRemoved() const; QVector<QModelIndex> GatherDependentGems(const QModelIndex& modelIndex, bool addedOnly = false) const;
QVector<QModelIndex> GatherGemsToBeAdded(bool includeDependencies = false) const;
QVector<QModelIndex> GatherGemsToBeRemoved(bool includeDependencies = false) const;
int TotalAddedGems() const; int TotalAddedGems(bool includeDependencies = false) const;
private: private:
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);
QStringList GetDependingGems(const QModelIndex& modelIndex);
enum UserRole enum UserRole
{ {
RoleName = Qt::UserRole, RoleName = Qt::UserRole,
@ -76,11 +88,12 @@ namespace O3DE::ProjectManager
RolePlatforms, RolePlatforms,
RoleSummary, RoleSummary,
RoleWasPreviouslyAdded, RoleWasPreviouslyAdded,
RoleWasPreviouslyAddedDependency,
RoleIsAdded, RoleIsAdded,
RoleIsAddedDependency,
RoleDirectoryLink, RoleDirectoryLink,
RoleDocLink, RoleDocLink,
RoleDependingGems, RoleDependingGems,
RoleConflictingGems,
RoleVersion, RoleVersion,
RoleLastUpdated, RoleLastUpdated,
RoleBinarySize, RoleBinarySize,
@ -92,5 +105,7 @@ namespace O3DE::ProjectManager
QHash<QString, QModelIndex> m_nameToIndexMap; QHash<QString, QModelIndex> m_nameToIndexMap;
QItemSelectionModel* m_selectionModel = nullptr; QItemSelectionModel* m_selectionModel = nullptr;
QHash<QString, QSet<QModelIndex>> m_gemDependencyMap;
QHash<QString, QSet<QModelIndex>> m_gemReverseDependencyMap;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -50,11 +50,21 @@ namespace O3DE::ProjectManager
} }
} }
// Gem status // Gem selected
if (m_gemStatusFilter != GemStatus::NoFilter) if (m_gemSelectedFilter != GemSelected::NoFilter)
{ {
const GemStatus sourceGemStatus = static_cast<GemStatus>(GemModel::IsAdded(sourceIndex)); const GemSelected sourceGemStatus = static_cast<GemSelected>(GemModel::IsAdded(sourceIndex));
if (m_gemStatusFilter != sourceGemStatus) if (m_gemSelectedFilter != sourceGemStatus)
{
return false;
}
}
// Gem enabled
if (m_gemActiveFilter != GemActive::NoFilter)
{
const GemActive sourceGemStatus = static_cast<GemActive>(GemModel::IsAdded(sourceIndex) || GemModel::IsAddedDependency(sourceIndex));
if (m_gemActiveFilter != sourceGemStatus)
{ {
return false; return false;
} }
@ -148,19 +158,31 @@ namespace O3DE::ProjectManager
return true; return true;
} }
QString GemSortFilterProxyModel::GetGemStatusString(GemStatus status) QString GemSortFilterProxyModel::GetGemSelectedString(GemSelected status)
{ {
switch (status) switch (status)
{ {
case Unselected: case GemSelected::Unselected:
return "Unselected"; return "Unselected";
case Selected: case GemSelected::Selected:
return "Selected"; return "Selected";
default: default:
return "<Unknown Gem Status>"; return "<Unknown Selection Status>";
} }
} }
QString GemSortFilterProxyModel::GetGemActiveString(GemActive status)
{
switch (status)
{
case GemActive::Inactive:
return "Inactive";
case GemActive::Active:
return "Active";
default:
return "<Unknown Active Status>";
}
}
void GemSortFilterProxyModel::InvalidateFilter() void GemSortFilterProxyModel::InvalidateFilter()
{ {
invalidate(); invalidate();

@ -25,16 +25,23 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC Q_OBJECT // AUTOMOC
public: public:
enum GemStatus enum class GemSelected
{ {
NoFilter = -1, NoFilter = -1,
Unselected, Unselected,
Selected Selected
}; };
enum class GemActive
{
NoFilter = -1,
Inactive,
Active
};
GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr); 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; 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(); } void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); }
GemStatus GetGemStatus() const { return m_gemStatusFilter; } GemSelected GetGemSelected() const { return m_gemSelectedFilter; }
void SetGemStatus(GemStatus gemStatus) { m_gemStatusFilter = gemStatus; InvalidateFilter(); } 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; } GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; }
void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); } void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); }
@ -69,7 +79,8 @@ namespace O3DE::ProjectManager
AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr; AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr;
QString m_searchString; QString m_searchString;
GemStatus m_gemStatusFilter = GemStatus::NoFilter; GemSelected m_gemSelectedFilter = GemSelected::NoFilter;
GemActive m_gemActiveFilter = GemActive::NoFilter;
GemInfo::GemOrigins m_gemOriginFilter = {}; GemInfo::GemOrigins m_gemOriginFilter = {};
GemInfo::Platforms m_platformFilter = {}; GemInfo::Platforms m_platformFilter = {};
GemInfo::Types m_typeFilter = {}; GemInfo::Types m_typeFilter = {};

@ -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", ""); QString gemType = Py_To_String_Optional(data, "type", "");
if (gemType == "Asset") if (gemType == "Asset")
{ {

@ -11,6 +11,7 @@ set(FILES
Resources/ProjectManager.qss Resources/ProjectManager.qss
tests/ApplicationTests.cpp tests/ApplicationTests.cpp
tests/PythonBindingsTests.cpp tests/PythonBindingsTests.cpp
tests/GemCatalogTests.cpp
tests/main.cpp tests/main.cpp
tests/UtilsTests.cpp tests/UtilsTests.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 <AzCore/UnitTest/TestTypes.h>
#include <AzTest/Utils.h>
#include <GemCatalog/GemModel.h>
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<QModelIndex> gemsToAdd = gemModel->GatherGemsToBeAdded();
EXPECT_TRUE(gemsToAdd.size() == 1);
EXPECT_EQ(GemModel::GetName(gemsToAdd.at(0)), gemA.m_name);
}
}
Loading…
Cancel
Save