Merge branch 'development' into Prism/AddRepoDialog

This commit is contained in:
nggieber
2021-09-27 07:24:29 -07:00
503 changed files with 9379 additions and 2541 deletions
@@ -7,6 +7,7 @@
*/
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <AzCore/std/functional.h>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QLabel>
@@ -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);
// added
CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=]
{
QVector<QModelIndex> gems;
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/false);
QVBoxLayout* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
m_enabledWidget->setLayout(layout);
// 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;
});
m_enabledLabel = new QLabel();
m_enabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
layout->addWidget(m_enabledLabel);
m_enabledTagContainer = new TagContainerWidget();
layout->addWidget(m_enabledTagContainer);
}
// removed
CreateGemSection( tr("Gem to be deactivated"), tr("Gems to be deactivated"), [=]
{
QVector<QModelIndex> gems;
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/false);
// disabled
{
m_disabledWidget = new QWidget();
m_disabledWidget->setFixedWidth(s_width);
m_layout->addWidget(m_disabledWidget);
// 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;
});
QVBoxLayout* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
m_disabledWidget->setLayout(layout);
// 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);
m_disabledLabel = new QLabel();
m_disabledLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
layout->addWidget(m_disabledLabel);
m_disabledTagContainer = new TagContainerWidget();
layout->addWidget(m_disabledTagContainer);
}
// 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;
});
// removed dependencies
CreateGemSection( tr("Dependency to be deactivated"), tr("Dependencies to be deactivated"), [=]
{
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);
Update();
connect(gemModel, &GemModel::dataChanged, this, [=]
{
Update();
});
}
void CartOverlayWidget::Update()
void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
{
const QVector<QModelIndex> 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<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
if (toBeRemoved.isEmpty())
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_disabledWidget->hide();
}
else
{
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<QModelIndex> 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<QModelIndex>& gems) const
@@ -154,15 +192,15 @@ namespace O3DE::ProjectManager
// Adjust the label text whenever the model gets updated.
connect(gemModel, &GemModel::dataChanged, [=]
{
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector<QModelIndex> 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<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
{
return;
@@ -8,6 +8,8 @@
#pragma once
#include <AzCore/std/function/function_fwd.h>
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/Components/SearchLineEdit.h>
#include <GemCatalog/GemModel.h>
@@ -30,22 +32,16 @@ namespace O3DE::ProjectManager
public:
CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr);
void Update();
private:
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;
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;
};
@@ -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())
@@ -226,13 +226,20 @@ namespace O3DE::ProjectManager
QVector<int> 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<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
for (int statusFilterIndex = 0; statusFilterIndex < buttons.size(); ++statusFilterIndex)
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)
{
const GemSortFilterProxyModel::GemStatus gemStatus = static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex);
QAbstractButton* button = buttons[statusFilterIndex];
if (static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex) == currentFilterState)
if (unselectedButton->isChecked() && !selectedButton->isChecked())
{
button->setChecked(true);
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected);
}
else if (!unselectedButton->isChecked() && selectedButton->isChecked())
{
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<GemSortFilterProxyModel::GemStatus>(!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<QString> features = m_filterProxyModel->GetFeatures();
@@ -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
@@ -8,6 +8,7 @@
#include <GemCatalog/GemInspector.h>
#include <GemCatalog/GemItemDelegate.h>
#include <QFrame>
#include <QLabel>
#include <QSpacerItem>
@@ -83,14 +84,13 @@ 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)));
m_lastUpdatedLabel->setText(QString("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(QString::number(m_model->GetBinarySizeInKB(modelIndex))));
m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
m_mainWidget->adjustSize();
m_mainWidget->show();
@@ -173,15 +173,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");
@@ -190,27 +186,4 @@ namespace O3DE::ProjectManager
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
}
GemInspector::GemsSubWidget::GemsSubWidget(QWidget* parent)
: QWidget(parent)
{
m_layout = new QVBoxLayout();
m_layout->setAlignment(Qt::AlignTop);
m_layout->setMargin(0);
setLayout(m_layout);
m_titleLabel = GemInspector::CreateStyledLabel(m_layout, 16, s_headerColor);
m_textLabel = GemInspector::CreateStyledLabel(m_layout, 10, s_textColor);
m_textLabel->setWordWrap(true);
m_tagWidget = new TagContainerWidget();
m_layout->addWidget(m_tagWidget);
}
void GemInspector::GemsSubWidget::Update(const QString& title, const QString& text, const QStringList& gemNames)
{
m_titleLabel->setText(title);
m_textLabel->setText(text);
m_tagWidget->Update(gemNames);
}
} // namespace O3DE::ProjectManager
@@ -9,10 +9,11 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <LinkWidget.h>
#include <TagWidget.h>
#include <GemCatalog/GemInfo.h>
#include <GemCatalog/GemModel.h>
#include <GemsSubWidget.h>
#include <LinkWidget.h>
#include <QItemSelection>
#include <QScrollArea>
#include <QWidget>
@@ -43,21 +44,6 @@ namespace O3DE::ProjectManager
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
private:
// Title, description and tag widget container used for the depending and conflicting gems
class GemsSubWidget
: public QWidget
{
public:
GemsSubWidget(QWidget* parent = nullptr);
void Update(const QString& title, const QString& text, const QStringList& gemNames);
private:
QLabel* m_titleLabel = nullptr;
QLabel* m_textLabel = nullptr;
QVBoxLayout* m_layout = nullptr;
TagContainerWidget* m_tagWidget = nullptr;
};
void InitMainWidget();
GemModel* m_model = nullptr;
@@ -78,7 +64,6 @@ namespace O3DE::ProjectManager
// Depending and conflicting gems
GemsSubWidget* m_dependingGems = nullptr;
GemsSubWidget* m_conflictingGems = nullptr;
// Additional information
QLabel* m_versionLabel = nullptr;
@@ -8,9 +8,14 @@
#include <GemCatalog/GemItemDelegate.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <QEvent>
#include <QAbstractItemView>
#include <QPainter>
#include <QMouseEvent>
#include <QHelpEvent>
#include <QToolTip>
#include <QHoverEvent>
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<QMouseEvent*>(event);
@@ -169,6 +173,69 @@ namespace O3DE::ProjectManager
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
{
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);
@@ -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;
@@ -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);
@@ -9,9 +9,26 @@
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemItemDelegate.h>
#include <QStandardItemModel>
#include <QProxyStyle>
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
@@ -8,6 +8,7 @@
#include <AzCore/std/string/string.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h>
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<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)
{
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();
}
void GemModel::GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems)
{
QStringList dependencies = GetDependingGems(modelIndex);
for (const QString& dependency : dependencies)
{
QModelIndex dependencyIndex = FindIndexByNameString(dependency);
if (!inOutGems.contains(dependencyIndex))
{
inOutGems.insert(dependencyIndex);
GetAllDependingGems(dependencyIndex, inOutGems);
}
}
}
QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex)
{
QStringList result = GetDependingGemUuids(modelIndex);
QStringList result = GetDependingGems(modelIndex);
if (result.isEmpty())
{
return {};
}
FindGemNamesByNameStrings(result);
return result;
}
QStringList GemModel::GetConflictingGemUuids(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleConflictingGems).toStringList();
}
QStringList GemModel::GetConflictingGemNames(const QModelIndex& modelIndex)
{
QStringList result = GetConflictingGemUuids(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<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)
{
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<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)
{
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);
}
}
}
bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex)
void GemModel::SetWasPreviouslyAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded)
{
return (!modelIndex.data(RoleWasPreviouslyAdded).toBool() && modelIndex.data(RoleIsAdded).toBool());
model.setData(modelIndex, wasAdded, RoleWasPreviouslyAddedDependency);
}
bool GemModel::NeedsToBeRemoved(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::WasPreviouslyAddedDependency(const QModelIndex& modelIndex)
{
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<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;
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<QModelIndex> GemModel::GatherGemsToBeRemoved() const
QVector<QModelIndex> GemModel::GatherGemsToBeRemoved(bool includeDependencies) const
{
QVector<QModelIndex> 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;
}
@@ -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<QModelIndex> GatherGemsToBeAdded() const;
QVector<QModelIndex> GatherGemsToBeRemoved() const;
QVector<QModelIndex> GatherGemDependencies(const QModelIndex& modelIndex) 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:
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& 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<QString, QModelIndex> m_nameToIndexMap;
QItemSelectionModel* m_selectionModel = nullptr;
QHash<QString, QSet<QModelIndex>> m_gemDependencyMap;
QHash<QString, QSet<QModelIndex>> m_gemReverseDependencyMap;
};
} // namespace O3DE::ProjectManager
@@ -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<GemStatus>(GemModel::IsAdded(sourceIndex));
if (m_gemStatusFilter != sourceGemStatus)
const GemSelected sourceGemStatus = static_cast<GemSelected>(GemModel::IsAdded(sourceIndex));
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;
}
@@ -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 "<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()
{
invalidate();
@@ -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 = {};
@@ -11,10 +11,12 @@
namespace O3DE::ProjectManager
{
GemRepoInfo::GemRepoInfo(
const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled = true)
const QString& name,
const QString& creator,
const QDateTime& lastUpdated,
bool isEnabled = true)
: m_name(name)
, m_creator(creator)
, m_summary(summary)
, m_lastUpdated(lastUpdated)
, m_isEnabled(isEnabled)
{
@@ -19,19 +19,25 @@ namespace O3DE::ProjectManager
{
public:
GemRepoInfo() = default;
GemRepoInfo(const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled);
GemRepoInfo(
const QString& name,
const QString& creator,
const QDateTime& lastUpdated,
bool isEnabled);
bool IsValid() const;
bool operator<(const GemRepoInfo& gemRepoInfo) const;
QString m_path;
QString m_path = "";
QString m_name = "Unknown Gem Repo Name";
QString m_creator = "Unknown Creator";
bool m_isEnabled = false; //! Is the repo currently enabled for this engine?
QString m_summary = "No summary provided.";
QString m_directoryLink;
QString m_repoLink;
QString m_additionalInfo = "";
QString m_directoryLink = "";
QString m_repoLink = "";
QStringList m_includedGemPaths = {};
QDateTime m_lastUpdated;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,144 @@
/*
* 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 <GemRepo/GemRepoInspector.h>
#include <GemRepo/GemRepoItemDelegate.h>
#include <QFrame>
#include <QLabel>
#include <QVBoxLayout>
#include <QIcon>
namespace O3DE::ProjectManager
{
GemRepoInspector::GemRepoInspector(GemRepoModel* model, QWidget* parent)
: QScrollArea(parent)
, m_model(model)
{
setObjectName("gemRepoInspector");
setWidgetResizable(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_mainWidget = new QWidget();
setWidget(m_mainWidget);
m_mainLayout = new QVBoxLayout();
m_mainLayout->setMargin(15);
m_mainLayout->setAlignment(Qt::AlignTop);
m_mainWidget->setLayout(m_mainLayout);
InitMainWidget();
connect(m_model->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, &GemRepoInspector::OnSelectionChanged);
Update({});
}
void GemRepoInspector::OnSelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
{
const QModelIndexList selectedIndices = selected.indexes();
if (selectedIndices.empty())
{
Update({});
return;
}
Update(selectedIndices[0]);
}
void GemRepoInspector::Update(const QModelIndex& modelIndex)
{
if (!modelIndex.isValid())
{
m_mainWidget->hide();
}
// Repo name and url link
m_nameLabel->setText(m_model->GetName(modelIndex));
m_repoLinkLabel->setText(m_model->GetRepoLink(modelIndex));
m_repoLinkLabel->SetUrl(m_model->GetRepoLink(modelIndex));
// Repo summary
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
m_summaryLabel->adjustSize();
// Additional information
if (m_model->HasAdditionalInfo(modelIndex))
{
m_addInfoTitleLabel->show();
m_addInfoTextLabel->show();
m_addInfoSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_addInfoTextLabel->setText(m_model->GetAdditionalInfo(modelIndex));
}
else
{
m_addInfoTitleLabel->hide();
m_addInfoTextLabel->hide();
m_addInfoSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
}
// Included Gems
m_includedGems->Update(tr("Included Gems"), "", m_model->GetIncludedGemNames(modelIndex));
m_mainWidget->adjustSize();
m_mainWidget->show();
}
void GemRepoInspector::InitMainWidget()
{
// Repo name and url link
m_nameLabel = new QLabel();
m_nameLabel->setObjectName("gemRepoInspectorNameLabel");
m_mainLayout->addWidget(m_nameLabel);
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(""), 12, this);
m_mainLayout->addWidget(m_repoLinkLabel);
m_mainLayout->addSpacing(5);
// Repo summary
m_summaryLabel = new QLabel();
m_summaryLabel->setObjectName("gemRepoInspectorBodyLabel");
m_summaryLabel->setWordWrap(true);
m_summaryLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_summaryLabel->setOpenExternalLinks(true);
m_mainLayout->addWidget(m_summaryLabel);
m_mainLayout->addSpacing(20);
// Separating line
QFrame* hLine = new QFrame();
hLine->setFrameShape(QFrame::HLine);
hLine->setObjectName("horizontalSeparatingLine");
m_mainLayout->addWidget(hLine);
m_mainLayout->addSpacing(10);
// Additional information
m_addInfoTitleLabel = new QLabel();
m_addInfoTitleLabel->setObjectName("gemRepoInspectorAddInfoTitleLabel");
m_addInfoTitleLabel->setText(tr("Additional Information"));
m_mainLayout->addWidget(m_addInfoTitleLabel);
m_addInfoTextLabel = new QLabel();
m_addInfoTextLabel->setObjectName("gemRepoInspectorBodyLabel");
m_addInfoTextLabel->setWordWrap(true);
m_addInfoTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_addInfoTextLabel->setOpenExternalLinks(true);
m_mainLayout->addWidget(m_addInfoTextLabel);
// Conditional spacing for additional info section
m_addInfoSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding);
m_mainLayout->addSpacerItem(m_addInfoSpacer);
// Included Gems
m_includedGems = new GemsSubWidget();
m_mainLayout->addWidget(m_includedGems);
m_mainLayout->addSpacing(20);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,59 @@
/*
* 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
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <GemRepo/GemRepoModel.h>
#include <LinkWidget.h>
#include <GemsSubWidget.h>
#include <QItemSelection>
#include <QScrollArea>
#include <QSpacerItem>
#include <QWidget>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QLabel)
namespace O3DE::ProjectManager
{
class GemRepoInspector : public QScrollArea
{
Q_OBJECT // AUTOMOC
public : explicit GemRepoInspector(GemRepoModel* model, QWidget* parent = nullptr);
~GemRepoInspector() = default;
void Update(const QModelIndex& modelIndex);
private slots:
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
private:
void InitMainWidget();
GemRepoModel* m_model = nullptr;
QWidget* m_mainWidget = nullptr;
QVBoxLayout* m_mainLayout = nullptr;
// General info section
QLabel* m_nameLabel = nullptr;
LinkLabel* m_repoLinkLabel = nullptr;
QLabel* m_summaryLabel = nullptr;
// Additional information
QLabel* m_addInfoTitleLabel = nullptr;
QLabel* m_addInfoTextLabel = nullptr;
QSpacerItem* m_addInfoSpacer = nullptr;
// Included Gems
GemsSubWidget* m_includedGems = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -11,13 +11,14 @@
namespace O3DE::ProjectManager
{
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QWidget* parent)
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
: QListView(parent)
{
setObjectName("gemRepoListView");
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setModel(model);
setSelectionModel(selectionModel);
setItemDelegate(new GemRepoItemDelegate(model, this));
}
} // namespace O3DE::ProjectManager
@@ -10,6 +10,7 @@
#if !defined(Q_MOC_RUN)
#include <QListView>
#include <QItemSelectionModel>
#endif
QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
@@ -22,7 +23,7 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
explicit GemRepoListView(QAbstractItemModel* model, QWidget* parent = nullptr);
explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
~GemRepoListView() = default;
};
} // namespace O3DE::ProjectManager
@@ -7,8 +7,10 @@
*/
#include <GemRepo/GemRepoModel.h>
#include <PythonBindings.h>
#include <QItemSelectionModel>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -16,6 +18,7 @@ namespace O3DE::ProjectManager
: QStandardItemModel(parent)
{
m_selectionModel = new QItemSelectionModel(this, parent);
m_gemModel = new GemModel(this);
}
QItemSelectionModel* GemRepoModel::GetSelectionModel() const
@@ -37,8 +40,17 @@ namespace O3DE::ProjectManager
item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
item->setData(gemRepoInfo.m_path, RolePath);
item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo);
item->setData(gemRepoInfo.m_includedGemPaths, RoleIncludedGems);
appendRow(item);
QVector<GemInfo> includedGemInfos = GetIncludedGemInfos(item->index());
for (const GemInfo& gemInfo : includedGemInfos)
{
m_gemModel->AddGem(gemInfo);
}
}
void GemRepoModel::Clear()
@@ -61,6 +73,11 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleSummary).toString();
}
QString GemRepoModel::GetAdditionalInfo(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleAdditionalInfo).toString();
}
QString GemRepoModel::GetDirectoryLink(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleDirectoryLink).toString();
@@ -81,6 +98,45 @@ namespace O3DE::ProjectManager
return modelIndex.data(RolePath).toString();
}
QStringList GemRepoModel::GetIncludedGemPaths(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleIncludedGems).toStringList();
}
QStringList GemRepoModel::GetIncludedGemNames(const QModelIndex& modelIndex)
{
QStringList gemNames;
QVector<GemInfo> gemInfos = GetIncludedGemInfos(modelIndex);
for (const GemInfo& gemInfo : gemInfos)
{
gemNames.append(gemInfo.m_displayName);
}
return gemNames;
}
QVector<GemInfo> GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex)
{
QVector<GemInfo> allGemInfos;
QStringList repoGemPaths = GetIncludedGemPaths(modelIndex);
for (const QString& gemPath : repoGemPaths)
{
AZ::Outcome<GemInfo> gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(gemPath);
if (gemInfoResult.IsSuccess())
{
allGemInfos.append(gemInfoResult.GetValue());
}
else
{
QMessageBox::critical(nullptr, tr("Gem Not Found"), tr("Cannot find info for gem %1.").arg(gemPath));
}
}
return allGemInfos;
}
bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleIsEnabled).toBool();
@@ -91,4 +147,9 @@ namespace O3DE::ProjectManager
model.setData(modelIndex, isEnabled, RoleIsEnabled);
}
bool GemRepoModel::HasAdditionalInfo(const QModelIndex& modelIndex)
{
return !modelIndex.data(RoleAdditionalInfo).toString().isEmpty();
}
} // namespace O3DE::ProjectManager
@@ -11,6 +11,7 @@
#if !defined(Q_MOC_RUN)
#include <QStandardItemModel>
#include <GemRepo/GemRepoInfo.h>
#include <GemCatalog/GemModel.h>
#endif
QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
@@ -32,13 +33,19 @@ namespace O3DE::ProjectManager
static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static QString GetAdditionalInfo(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetRepoLink(const QModelIndex& modelIndex);
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex);
static QStringList GetIncludedGemPaths(const QModelIndex& modelIndex);
static QStringList GetIncludedGemNames(const QModelIndex& modelIndex);
static QVector<GemInfo> GetIncludedGemInfos(const QModelIndex& modelIndex);
static bool IsEnabled(const QModelIndex& modelIndex);
static void SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled);
static bool HasAdditionalInfo(const QModelIndex& modelIndex);
private:
enum UserRole
@@ -50,9 +57,13 @@ namespace O3DE::ProjectManager
RoleDirectoryLink,
RoleRepoLink,
RoleLastUpdated,
RolePath
RolePath,
RoleAdditionalInfo,
RoleIncludedGems,
};
QItemSelectionModel* m_selectionModel = nullptr;
GemModel* m_gemModel = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -10,6 +10,7 @@
#include <GemRepo/GemRepoItemDelegate.h>
#include <GemRepo/GemRepoListView.h>
#include <GemRepo/GemRepoModel.h>
#include <GemRepo/GemRepoInspector.h>
#include <GemRepo/GemRepoAddDialog.h>
#include <PythonBindingsInterface.h>
@@ -34,7 +35,7 @@ namespace O3DE::ProjectManager
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setMargin(0);
vLayout->setSpacing(0);
setLayout(vLayout);
setLayout(vLayout);
m_contentStack = new QStackedWidget(this);
@@ -168,10 +169,6 @@ namespace O3DE::ProjectManager
hLayout->addSpacing(60);
m_gemRepoInspector = new QFrame(this);
m_gemRepoInspector->setObjectName(tr("gemRepoInspector"));
m_gemRepoInspector->setFixedWidth(240);
QVBoxLayout* middleVLayout = new QVBoxLayout();
middleVLayout->setMargin(0);
middleVLayout->setSpacing(0);
@@ -231,10 +228,13 @@ namespace O3DE::ProjectManager
m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }");
middleVLayout->addWidget(m_gemRepoHeaderTable);
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, this);
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this);
middleVLayout->addWidget(m_gemRepoListView);
hLayout->addLayout(middleVLayout);
m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this);
m_gemRepoInspector->setFixedWidth(240);
hLayout->addWidget(m_gemRepoInspector);
return contentFrame;
@@ -21,6 +21,7 @@ QT_FORWARD_DECLARE_CLASS(QStackedWidget)
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(GemRepoInspector)
QT_FORWARD_DECLARE_CLASS(GemRepoListView)
QT_FORWARD_DECLARE_CLASS(GemRepoModel)
@@ -51,7 +52,7 @@ namespace O3DE::ProjectManager
QTableWidget* m_gemRepoHeaderTable = nullptr;
QHeaderView* m_gemRepoListHeader = nullptr;
GemRepoListView* m_gemRepoListView = nullptr;
QFrame* m_gemRepoInspector = nullptr;
GemRepoInspector* m_gemRepoInspector = nullptr;
GemRepoModel* m_gemRepoModel = nullptr;
QLabel* m_lastAllUpdateLabel;
@@ -0,0 +1,45 @@
/*
* 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 <GemsSubWidget.h>
#include <TagWidget.h>
#include <QFrame>
#include <QLabel>
#include <QVBoxLayout>
namespace O3DE::ProjectManager
{
GemsSubWidget::GemsSubWidget(QWidget* parent)
: QWidget(parent)
{
m_layout = new QVBoxLayout();
m_layout->setAlignment(Qt::AlignTop);
m_layout->setMargin(0);
setLayout(m_layout);
m_titleLabel = new QLabel();
m_titleLabel->setObjectName("gemSubWidgetTitleLabel");
m_layout->addWidget(m_titleLabel);
m_textLabel = new QLabel();
m_textLabel->setObjectName("gemSubWidgetTextLabel");
m_textLabel->setWordWrap(true);
m_layout->addWidget(m_textLabel);
m_tagWidget = new TagContainerWidget();
m_layout->addWidget(m_tagWidget);
}
void GemsSubWidget::Update(const QString& title, const QString& text, const QStringList& gemNames)
{
m_titleLabel->setText(title);
m_textLabel->setText(text);
m_tagWidget->Update(gemNames);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,35 @@
/*
* 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
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <TagWidget.h>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QLabel)
namespace O3DE::ProjectManager
{
// Title, description and tag widget container used for the depending and conflicting gems
class GemsSubWidget
: public QWidget
{
public:
GemsSubWidget(QWidget* parent = nullptr);
void Update(const QString& title, const QString& text, const QStringList& gemNames);
private:
QLabel* m_titleLabel = nullptr;
QLabel* m_textLabel = nullptr;
QVBoxLayout* m_layout = nullptr;
TagContainerWidget* m_tagWidget = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -14,9 +14,10 @@
namespace O3DE::ProjectManager
{
LinkLabel::LinkLabel(const QString& text, const QUrl& url, QWidget* parent)
LinkLabel::LinkLabel(const QString& text, const QUrl& url, int fontSize, QWidget* parent)
: QLabel(text, parent)
, m_url(url)
, m_fontSize(fontSize)
{
SetDefaultStyle();
}
@@ -33,7 +34,7 @@ namespace O3DE::ProjectManager
void LinkLabel::enterEvent([[maybe_unused]] QEvent* event)
{
setStyleSheet("font-size: 10px; color: #94D2FF; text-decoration: underline;");
setStyleSheet(QString("font-size: %1px; color: #94D2FF; text-decoration: underline;").arg(m_fontSize));
}
void LinkLabel::leaveEvent([[maybe_unused]] QEvent* event)
@@ -48,6 +49,6 @@ namespace O3DE::ProjectManager
void LinkLabel::SetDefaultStyle()
{
setStyleSheet("font-size: 10px; color: #94D2FF;");
setStyleSheet(QString("font-size: %1px; color: #94D2FF;").arg(m_fontSize));
}
} // namespace O3DE::ProjectManager
@@ -25,7 +25,7 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
LinkLabel(const QString& text = {}, const QUrl& url = {}, QWidget* parent = nullptr);
LinkLabel(const QString& text = {}, const QUrl& url = {}, int fontSize = 10, QWidget* parent = nullptr);
void SetUrl(const QUrl& url);
@@ -40,5 +40,6 @@ namespace O3DE::ProjectManager
private:
QUrl m_url;
int m_fontSize;
};
} // namespace O3DE::ProjectManager
@@ -8,8 +8,15 @@
#include <ProjectBuilderWorker.h>
#include <ProjectManagerDefs.h>
#include <PythonBindingsInterface.h>
#include <ProjectUtils.h>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTextStream>
#include <QThread>
//#define MOCK_BUILD_PROJECT true
@@ -67,4 +74,176 @@ namespace O3DE::ProjectManager
{
AZ_TracePrintf("Project Manager", error.toStdString().c_str());
}
AZ::Outcome<void, QString> ProjectBuilderWorker::BuildProjectForPlatform()
{
// Check if we are trying to cancel task
if (QThread::currentThread()->isInterruptionRequested())
{
QStringToAZTracePrint(BuildCancelled);
return AZ::Failure(BuildCancelled);
}
QFile logFile(GetLogFilePath());
if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
{
QString error = tr("Failed to open log file.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
EngineInfo engineInfo;
AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
if (engineInfoResult.IsSuccess())
{
engineInfo = engineInfoResult.GetValue();
}
else
{
QString error = tr("Failed to get engine info.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
QTextStream logStream(&logFile);
if (QThread::currentThread()->isInterruptionRequested())
{
logFile.close();
QStringToAZTracePrint(BuildCancelled);
return AZ::Failure(BuildCancelled);
}
// Show some kind of progress with very approximate estimates
UpdateProgress(++m_progressEstimate);
auto currentEnvironmentRequest = ProjectUtils::GetCommandLineProcessEnvironment();
if (!currentEnvironmentRequest.IsSuccess())
{
QStringToAZTracePrint(currentEnvironmentRequest.GetError());
return AZ::Failure(currentEnvironmentRequest.GetError());
}
QProcessEnvironment currentEnvironment = currentEnvironmentRequest.GetValue();
m_configProjectProcess = new QProcess(this);
m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_configProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeGenerateArgumentsResult = ConstructCmakeGenerateProjectArguments(engineInfo.m_thirdPartyPath);
if (!cmakeGenerateArgumentsResult.IsSuccess())
{
QStringToAZTracePrint(cmakeGenerateArgumentsResult.GetError());
return AZ::Failure(cmakeGenerateArgumentsResult.GetError());
}
auto cmakeGenerateArguments = cmakeGenerateArgumentsResult.GetValue();
m_configProjectProcess->start(cmakeGenerateArguments.front(), cmakeGenerateArguments.mid(1));
if (!m_configProjectProcess->waitForStarted())
{
QString error = tr("Configuring project failed to start.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
bool containsGeneratingDone = false;
while (m_configProjectProcess->waitForReadyRead(MaxBuildTimeMSecs))
{
QString configOutput = m_configProjectProcess->readAllStandardOutput();
if (configOutput.contains("Generating done"))
{
containsGeneratingDone = true;
}
logStream << configOutput;
logStream.flush();
UpdateProgress(qMin(++m_progressEstimate, 19));
if (QThread::currentThread()->isInterruptionRequested())
{
logFile.close();
m_configProjectProcess->close();
QStringToAZTracePrint(BuildCancelled);
return AZ::Failure(BuildCancelled);
}
}
if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit || m_configProjectProcess->exitCode() != 0 ||
!containsGeneratingDone)
{
QString error = tr("Configuring project failed. See log for details.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
UpdateProgress(++m_progressEstimate);
m_buildProjectProcess = new QProcess(this);
m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_buildProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeBuildArgumentsResult = ConstructCmakeBuildCommandArguments();
if (!cmakeBuildArgumentsResult.IsSuccess())
{
QStringToAZTracePrint(cmakeBuildArgumentsResult.GetError());
return AZ::Failure(cmakeBuildArgumentsResult.GetError());
}
auto cmakeBuildArguments = cmakeBuildArgumentsResult.GetValue();
m_buildProjectProcess->start(cmakeBuildArguments.front(), cmakeBuildArguments.mid(1));
if (!m_buildProjectProcess->waitForStarted())
{
QString error = tr("Building project failed to start.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
// There are a lot of steps when building so estimate around 800 more steps ((100 - 20) * 10) remaining
m_progressEstimate = 200;
while (m_buildProjectProcess->waitForReadyRead(MaxBuildTimeMSecs))
{
logStream << m_buildProjectProcess->readAllStandardOutput();
logStream.flush();
// Show 1% progress for every 10 steps completed
UpdateProgress(qMin(++m_progressEstimate / 10, 99));
if (QThread::currentThread()->isInterruptionRequested())
{
// QProcess is unable to kill its child processes so we need to ask the operating system to do that for us
auto killProcessArgumentsResult = ConstructKillProcessCommandArguments(QString::number(m_buildProjectProcess->processId()));
if (!killProcessArgumentsResult.IsSuccess())
{
return AZ::Failure(killProcessArgumentsResult.GetError());
}
auto killProcessArguments = killProcessArgumentsResult.GetValue();
QProcess killBuildProcess;
killBuildProcess.setProcessChannelMode(QProcess::MergedChannels);
killBuildProcess.start(killProcessArguments.front(), killProcessArguments.mid(1));
killBuildProcess.waitForFinished();
logStream << "Killing Project Build.";
logStream << killBuildProcess.readAllStandardOutput();
m_buildProjectProcess->kill();
logFile.close();
QStringToAZTracePrint(BuildCancelled);
return AZ::Failure(BuildCancelled);
}
}
if (m_buildProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit || m_buildProjectProcess->exitCode() != 0)
{
QString error = tr("Building project failed. See log for details.");
QStringToAZTracePrint(error);
return AZ::Failure(error);
}
return AZ::Success();
}
} // namespace O3DE::ProjectManager
@@ -12,6 +12,7 @@
#include <AzCore/Outcome/Outcome.h>
#include <QObject>
#include <QProcessEnvironment>
#endif
QT_FORWARD_DECLARE_CLASS(QProcess)
@@ -44,6 +45,12 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, QString> BuildProjectForPlatform();
void QStringToAZTracePrint(const QString& error);
// Command line argument builders
AZ::Outcome<QStringList, QString> ConstructCmakeGenerateProjectArguments(const QString& thirdPartyPath) const;
AZ::Outcome<QStringList, QString> ConstructCmakeBuildCommandArguments() const;
AZ::Outcome<QStringList, QString> ConstructKillProcessCommandArguments(const QString& pidToKill) const;
QProcess* m_configProjectProcess = nullptr;
QProcess* m_buildProjectProcess = nullptr;
ProjectInfo m_projectInfo;
@@ -14,6 +14,7 @@ namespace O3DE::ProjectManager
inline constexpr static int ProjectPreviewImageWidth = 210;
inline constexpr static int ProjectPreviewImageHeight = 280;
inline constexpr static int ProjectTemplateImageWidth = 92;
inline constexpr static int ProjectCommandLineTimeoutSeconds = 30;
static const QString ProjectBuildDirectoryName = "build";
extern const QString ProjectBuildPathPostfix;
@@ -21,4 +22,8 @@ namespace O3DE::ProjectManager
static const QString ProjectBuildErrorLogName = "CMakeProjectBuildError.log";
static const QString ProjectCacheDirectoryName = "Cache";
static const QString ProjectPreviewImagePath = "preview.png";
static const QString ProjectCMakeCommand = "cmake";
static const QString ProjectCMakeBuildTargetEditor = "Editor";
} // namespace O3DE::ProjectManager
@@ -22,6 +22,8 @@
#include <QSpacerItem>
#include <QGridLayout>
#include <AzCore/std/chrono/chrono.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -507,5 +509,33 @@ namespace O3DE::ProjectManager
return ProjectManagerScreen::Invalid;
}
AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds /*= ProjectCommandLineTimeoutSeconds*/)
{
QProcess execProcess;
execProcess.setProcessEnvironment(processEnv);
execProcess.setProcessChannelMode(QProcess::MergedChannels);
execProcess.start(cmd, arguments);
if (!execProcess.waitForStarted())
{
return AZ::Failure(QObject::tr("Unable to start process for command '%1'").arg(cmd));
}
if (!execProcess.waitForFinished(commandTimeoutSeconds * 1000 /* Milliseconds per second */))
{
return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds));
}
int resultCode = execProcess.exitCode();
if (resultCode != 0)
{
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode));
}
QString resultOutput = execProcess.readAllStandardOutput();
return AZ::Success(resultOutput);
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -9,8 +9,11 @@
#include <ScreenDefs.h>
#include <ProjectInfo.h>
#include <ProjectManagerDefs.h>
#include <QWidget>
#include <QProcessEnvironment>
#include <AzCore/Outcome/Outcome.h>
namespace O3DE::ProjectManager
@@ -28,8 +31,17 @@ namespace O3DE::ProjectManager
bool ReplaceProjectFile(const QString& origFile, const QString& newFile, QWidget* parent = nullptr, bool interactive = true);
bool FindSupportedCompiler(QWidget* parent = nullptr);
AZ::Outcome<void, QString> FindSupportedCompilerForPlatform();
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform();
ProjectManagerScreen GetProjectManagerScreen(const QString& screen);
AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds = ProjectCommandLineTimeoutSeconds);
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment();
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -339,7 +339,7 @@ namespace O3DE::ProjectManager
{
for (auto engine : allEngines)
{
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine));
if (enginePath.Compare(m_enginePath) == 0)
{
return;
@@ -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")
{
@@ -953,8 +961,16 @@ namespace O3DE::ProjectManager
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
#else
gemRepos.push_back(GemRepoInfo("JohnCreates", "John Smith", "", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true));
gemRepos.push_back(GemRepoInfo("JanesGems", "Jane Doe", "", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false));
GemRepoInfo mockJohnRepo("JohnCreates", "John Smith", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true);
mockJohnRepo.m_summary = "John's Summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitudin dapibus urna";
mockJohnRepo.m_repoLink = "https://github.com/o3de/o3de";
mockJohnRepo.m_additionalInfo = "John's additional info. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitu.";
gemRepos.push_back(mockJohnRepo);
GemRepoInfo mockJaneRepo("JanesGems", "Jane Doe", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false);
mockJaneRepo.m_summary = "Jane's Summary.";
mockJaneRepo.m_repoLink = "https://github.com/o3de/o3de.org";
gemRepos.push_back(mockJaneRepo);
#endif // MOCK_GEM_REPO_INFO
std::sort(gemRepos.begin(), gemRepos.end());
@@ -56,8 +56,9 @@ namespace O3DE::ProjectManager
// Gems
/**
* Get info about a Gem
* @param projectPath the absolute path to the Gem
* Get info about a Gem.
* @param path The absolute path to the Gem
* @param projectPath (Optional) The absolute path to the Gem project
* @return an outcome with GemInfo on success
*/
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) = 0;