Merge pull request #5513 from aws-lumberyard-dev/Prism/DeleteUpdateGemsUI

Adds Update and Uninstall Options for Downloaded Remote Gems to Gem Catalog
This commit is contained in:
AMZN-Phil
2021-11-11 14:01:42 -08:00
committed by GitHub
23 changed files with 518 additions and 71 deletions
@@ -563,6 +563,52 @@ QProgressBar::chunk {
margin-top:5px;
}
#gemCatalogUpdateGemButton,
#gemCatalogUninstallGemButton
{
qproperty-flat: true;
min-height:24px;
max-height:24px;
border-radius: 3px;
text-align:center;
font-size:12px;
font-weight:600;
}
#gemCatalogUpdateGemButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #888888, stop: 1.0 #555555);
}
#gemCatalogUpdateGemButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #999999, stop: 1.0 #666666);
}
#gemCatalogUpdateGemButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #555555, stop: 1.0 #777777);
}
#footer > #gemCatalogUninstallGemButton,
#gemCatalogUninstallGemButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E32C27, stop: 1.0 #951D21);
}
#footer > #gemCatalogUninstallGemButton:hover,
#gemCatalogUninstallGemButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #FD3129, stop: 1.0 #AF2221);
}
#footer > #gemCatalogUninstallGemButton:pressed,
#gemCatalogUninstallGemButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #951D1F, stop: 1.0 #C92724);
}
#gemCatalogDialogSubTitle {
font-size:14px;
font-weight:600;
}
/************** Filter Tag widget **************/
#FilterTagWidgetTextLabel {
@@ -24,7 +24,9 @@ namespace O3DE::ProjectManager
{
emit UpdateProgress(bytesDownloaded, totalBytes);
};
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
AZ::Outcome<void, AZStd::string> gemInfoResult =
PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true);
if (gemInfoResult.IsSuccess())
{
emit Done("");
@@ -500,6 +500,7 @@ namespace O3DE::ProjectManager
hLayout->addSpacing(16);
QMenu* gemMenu = new QMenu(this);
gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); });
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
gemMenu->addSeparator();
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
@@ -103,6 +103,7 @@ namespace O3DE::ProjectManager
signals:
void AddGem();
void OpenGemsRepo();
void RefreshGems();
private:
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
@@ -12,7 +12,11 @@
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemRequirementDialog.h>
#include <GemCatalog/GemDependenciesDialog.h>
#include <GemCatalog/GemUpdateDialog.h>
#include <GemCatalog/GemUninstallDialog.h>
#include <DownloadController.h>
#include <ProjectUtils.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
@@ -47,6 +51,7 @@ namespace O3DE::ProjectManager
vLayout->addWidget(m_headerWidget);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh);
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked);
connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult);
@@ -60,6 +65,8 @@ namespace O3DE::ProjectManager
m_gemInspector->setFixedWidth(240);
connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); });
connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem);
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
QWidget* filterWidget = new QWidget(this);
filterWidget->setFixedWidth(240);
@@ -99,7 +106,7 @@ namespace O3DE::ProjectManager
FillModel(projectPath);
m_proxyModel->ResetFilters();
m_proxyModel->ResetFilters(false);
m_proxyModel->sort(/*column=*/0);
if (m_filterWidget)
@@ -118,9 +125,9 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
m_gemModel->GetSelectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
void GemCatalogScreen::OnAddGemClicked()
@@ -228,8 +235,11 @@ namespace O3DE::ProjectManager
m_proxyModel->sort(/*column=*/0);
// temporary, until we can refresh filter counts
m_proxyModel->ResetFilters();
m_proxyModel->ResetFilters(false);
m_filterWidget->ResetAllFilters();
// Reselect the same selection to proc UI updates
m_proxyModel->GetSelectionModel()->select(m_proxyModel->GetSelectionModel()->selection(), QItemSelectionModel::Select);
}
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
@@ -253,7 +263,7 @@ namespace O3DE::ProjectManager
notification = GemModel::GetDisplayName(modelIndex);
if (numChangedDependencies > 0)
{
notification += " " + tr("and") + " ";
notification += tr(" and ");
}
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
{
@@ -262,15 +272,15 @@ namespace O3DE::ProjectManager
}
}
if (numChangedDependencies == 1 )
if (numChangedDependencies == 1)
{
notification += "1 Gem " + tr("dependency");
notification += tr("1 Gem dependency");
}
else if (numChangedDependencies > 1)
{
notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies");
notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies"));
}
notification += " " + (added ? tr("activated") : tr("deactivated"));
notification += (added ? tr(" activated") : tr(" deactivated"));
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
toastConfiguration.m_customIconImage = ":/gem.svg";
@@ -294,6 +304,88 @@ namespace O3DE::ProjectManager
m_gemListView->scrollTo(proxyIndex);
}
void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex)
{
const QString selectedGemName = m_gemModel->GetName(modelIndex);
const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex);
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex);
// Refresh gem repo
if (!selectedGemRepoUri.isEmpty())
{
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri);
if (refreshResult.IsSuccess())
{
Refresh();
}
else
{
QMessageBox::critical(
this, tr("Operation failed"),
tr("Failed to refresh gem repository %1<br>Error:<br>%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str()));
}
}
// If repo uri isn't specified warn user that repo might not be refreshed
else
{
int result = QMessageBox::warning(
this, tr("Gem Repository Unspecified"),
tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. "
"Please ensure this gem's repo is refreshed before attempting to update.")
.arg(selectedDisplayGemName),
QMessageBox::Cancel, QMessageBox::Ok);
// Allow user to cancel update to manually refresh repo
if (result != QMessageBox::Ok)
{
return;
}
}
// Check if there is an update avaliable now that repo is refreshed
bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate);
GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this);
if (confirmUpdateDialog->exec() == QDialog::Accepted)
{
m_downloadController->AddGemDownload(selectedGemName);
}
}
void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex)
{
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this);
if (confirmUninstallDialog->exec() == QDialog::Accepted)
{
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
// Unregister the gem
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
if (!unregisterResult)
{
QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str());
}
else
{
// Remove gem from model
m_gemModel->removeRow(modelIndex.row());
// Delete uninstalled gem directory
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
{
QMessageBox::critical(
this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:<br>%1").arg(selectedGemPath));
}
// Show undownloaded remote gem again
Refresh();
}
}
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
@@ -51,6 +51,8 @@ namespace O3DE::ProjectManager
void SelectGem(const QString& gemName);
void OnGemDownloadResult(const QString& gemName, bool succeeded = true);
void Refresh();
void UpdateGem(const QModelIndex& modelIndex);
void UninstallGem(const QModelIndex& modelIndex);
protected:
void hideEvent(QHideEvent* event) override;
@@ -77,6 +79,6 @@ namespace O3DE::ProjectManager
DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
QSet<QString> m_gemsToRegisterWithProject;
QString m_projectPath = nullptr;
QString m_projectPath;
};
} // namespace O3DE::ProjectManager
@@ -87,6 +87,7 @@ namespace O3DE::ProjectManager
QString m_licenseLink;
QString m_directoryLink;
QString m_documentationLink;
QString m_repoUri;
QString m_version = "Unknown Version";
QString m_lastUpdatedDate = "Unknown Date";
int m_binarySizeInKB = 0;
@@ -14,6 +14,7 @@
#include <QSpacerItem>
#include <QVBoxLayout>
#include <QIcon>
#include <QPushButton>
namespace O3DE::ProjectManager
{
@@ -70,6 +71,8 @@ namespace O3DE::ProjectManager
void GemInspector::Update(const QModelIndex& modelIndex)
{
m_curModelIndex = modelIndex;
if (!modelIndex.isValid())
{
m_mainWidget->hide();
@@ -123,6 +126,20 @@ namespace O3DE::ProjectManager
const int binarySize = m_model->GetBinarySizeInKB(modelIndex);
m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown")));
// Update and Uninstall buttons
if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote &&
(m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded ||
m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful))
{
m_updateGemButton->show();
m_uninstallGemButton->show();
}
else
{
m_updateGemButton->hide();
m_uninstallGemButton->hide();
}
m_mainWidget->adjustSize();
m_mainWidget->show();
}
@@ -223,7 +240,7 @@ namespace O3DE::ProjectManager
// Depending gems
m_dependingGems = new GemsSubWidget();
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); });
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); });
m_mainLayout->addWidget(m_dependingGems);
m_mainLayout->addSpacing(20);
@@ -234,5 +251,20 @@ namespace O3DE::ProjectManager
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_mainLayout->addSpacing(20);
// Update and Uninstall buttons
m_updateGemButton = new QPushButton(tr("Update Gem"));
m_updateGemButton->setObjectName("gemCatalogUpdateGemButton");
m_mainLayout->addWidget(m_updateGemButton);
connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); });
m_mainLayout->addSpacing(10);
m_uninstallGemButton = new QPushButton(tr("Uninstall Gem"));
m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton");
m_mainLayout->addWidget(m_uninstallGemButton);
connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); });
}
} // namespace O3DE::ProjectManager
@@ -16,11 +16,12 @@
#include <QItemSelection>
#include <QScrollArea>
#include <QSpacerItem>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QSpacerItem)
QT_FORWARD_DECLARE_CLASS(QPushButton)
namespace O3DE::ProjectManager
{
@@ -45,6 +46,8 @@ namespace O3DE::ProjectManager
signals:
void TagClicked(const Tag& tag);
void UpdateGem(const QModelIndex& modelIndex);
void UninstallGem(const QModelIndex& modelIndex);
private slots:
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
@@ -55,6 +58,7 @@ namespace O3DE::ProjectManager
GemModel* m_model = nullptr;
QWidget* m_mainWidget = nullptr;
QVBoxLayout* m_mainLayout = nullptr;
QModelIndex m_curModelIndex;
// General info (top) section
QLabel* m_nameLabel = nullptr;
@@ -77,5 +81,8 @@ namespace O3DE::ProjectManager
QLabel* m_versionLabel = nullptr;
QLabel* m_lastUpdatedLabel = nullptr;
QLabel* m_binarySizeLabel = nullptr;
QPushButton* m_updateGemButton = nullptr;
QPushButton* m_uninstallGemButton = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -61,6 +61,7 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
item->setData(gemInfo.m_licenseText, RoleLicenseText);
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
item->setData(gemInfo.m_repoUri, RoleRepoUri);
appendRow(item);
@@ -255,6 +256,11 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleLicenseLink).toString();
}
QString GemModel::GetRepoUri(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleRepoUri).toString();
}
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
{
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
@@ -369,11 +375,30 @@ namespace O3DE::ProjectManager
void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last)
{
bool selectedRowRemoved = false;
for (int i = first; i <= last; ++i)
{
QModelIndex modelIndex = index(i, 0, parent);
const QString& gemName = GetName(modelIndex);
m_nameToIndexMap.remove(gemName);
if (GetSelectionModel()->isRowSelected(i))
{
selectedRowRemoved = true;
}
}
// Select a valid row if currently selected row was removed
if (selectedRowRemoved)
{
for (const QModelIndex& index : m_nameToIndexMap)
{
if (index.isValid())
{
GetSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
break;
}
}
}
}
@@ -51,7 +51,8 @@ namespace O3DE::ProjectManager
RoleRequirement,
RoleDownloadStatus,
RoleLicenseText,
RoleLicenseLink
RoleLicenseLink,
RoleRepoUri
};
void AddGem(const GemInfo& gemInfo);
@@ -80,6 +81,7 @@ namespace O3DE::ProjectManager
static QString GetRequirement(const QModelIndex& modelIndex);
static QString GetLicenseText(const QModelIndex& modelIndex);
static QString GetLicenseLink(const QModelIndex& modelIndex);
static QString GetRepoUri(const QModelIndex& modelIndex);
static GemModel* GetSourceModel(QAbstractItemModel* model);
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
@@ -204,9 +204,12 @@ namespace O3DE::ProjectManager
emit OnInvalidated();
}
void GemSortFilterProxyModel::ResetFilters()
void GemSortFilterProxyModel::ResetFilters(bool clearSearchString)
{
m_searchString.clear();
if (clearSearchString)
{
m_searchString.clear();
}
m_gemSelectedFilter = GemSelected::NoFilter;
m_gemActiveFilter = GemActive::NoFilter;
m_gemOriginFilter = {};
@@ -70,7 +70,7 @@ namespace O3DE::ProjectManager
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
void InvalidateFilter();
void ResetFilters();
void ResetFilters(bool clearSearchString = true);
signals:
void OnInvalidated();
@@ -0,0 +1,60 @@
/*
* 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 <GemCatalog/GemUninstallDialog.h>
#include <QVBoxLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVariant>
namespace O3DE::ProjectManager
{
GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Uninstall Remote Gem"));
setObjectName("GemUninstallDialog");
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(30);
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
// Body
QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName));
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
layout->addWidget(subTitleLabel);
layout->addSpacing(10);
QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. "
"You can reinstall this Gem from the Catalog, but its contents may be subject to change."));
bodyLabel->setWordWrap(true);
bodyLabel->setFixedSize(QSize(440, 80));
layout->addWidget(bodyLabel);
layout->addSpacing(40);
// Buttons
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
dialogButtons->setObjectName("footer");
layout->addWidget(dialogButtons);
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
cancelButton->setProperty("secondary", true);
QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole);
uninstallButton->setObjectName("gemCatalogUninstallGemButton");
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,25 @@
/*
* 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 <QDialog>
#endif
namespace O3DE::ProjectManager
{
class GemUninstallDialog
: public QDialog
{
Q_OBJECT // AUTOMOC
public:
explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr);
~GemUninstallDialog() = default;
};
} // namespace O3DE::ProjectManager
@@ -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 <GemCatalog/GemUpdateDialog.h>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QVariant>
namespace O3DE::ProjectManager
{
GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent)
: QDialog(parent)
{
setWindowTitle(tr("Update Remote Gem"));
setObjectName("GemUpdateDialog");
setAttribute(Qt::WA_DeleteOnClose);
setModal(true);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(30);
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
// Body
QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg(
updateAvaliable ? tr("Update") : tr("Force update"), gemName));
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
layout->addWidget(subTitleLabel);
layout->addSpacing(10);
QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. "
"Updating this Gem will remove any local changes made to this Gem, "
"and may remove old features that are in use.").arg(
updateAvaliable ? "" : tr("No update detected for Gem. "
"This will force a re-download of the gem. ")));
bodyLabel->setWordWrap(true);
bodyLabel->setFixedSize(QSize(440, 80));
layout->addWidget(bodyLabel);
layout->addSpacing(40);
// Buttons
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
dialogButtons->setObjectName("footer");
layout->addWidget(dialogButtons);
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
cancelButton->setProperty("secondary", true);
QPushButton* updateButton =
dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole);
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
connect(updateButton, &QPushButton::clicked, this, &QDialog::accept);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,25 @@
/*
* 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 <QDialog>
#endif
namespace O3DE::ProjectManager
{
class GemUpdateDialog
: public QDialog
{
Q_OBJECT // AUTOMOC
public :
explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr);
~GemUpdateDialog() = default;
};
} // namespace O3DE::ProjectManager
@@ -515,7 +515,11 @@ namespace O3DE::ProjectManager
auto pyProjectPath = QString_To_Py_Path(projectPath);
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
{
gems.push_back(GemInfoFromPath(path, pyProjectPath));
GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath);
// Mark as downloaded because this gem was registered with an existing directory
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
gems.push_back(AZStd::move(gemInfo));
}
});
if (!result.IsSuccess())
@@ -560,7 +564,7 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemNames));
}
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
AZ::Outcome<void, AZStd::string> PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove)
{
bool registrationResult = false;
auto result = ExecuteWithLockErrorHandling(
@@ -582,7 +586,8 @@ namespace O3DE::ProjectManager
pybind11::none(), // default_restricted_folder
pybind11::none(), // default_third_party_folder
pybind11::none(), // external_subdir_engine_path
externalProjectPath // external_subdir_project_path
externalProjectPath, // external_subdir_project_path
remove // remove
);
// Returns an exit code so boolify it then invert result
@@ -595,12 +600,23 @@ namespace O3DE::ProjectManager
}
else if (!registrationResult)
{
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
return AZ::Failure<AZStd::string>(AZStd::string::format(
"Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData()));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
{
return GemRegistration(gemPath, projectPath);
}
AZ::Outcome<void, AZStd::string> PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath)
{
return GemRegistration(gemPath, projectPath, /*remove*/true);
}
bool PythonBindings::AddProject(const QString& path)
{
bool registrationResult = false;
@@ -715,6 +731,7 @@ namespace O3DE::ProjectManager
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", "");
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
@@ -728,6 +745,11 @@ namespace O3DE::ProjectManager
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
}
// If no origin was provided this cannot be remote and would be specified if O3DE so it should be local
else
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
}
// As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
@@ -1166,7 +1188,35 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemRepos));
}
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback)
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
{
QVector<GemInfo> gemInfos;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
[&]
{
auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")();
if (pybind11::isinstance<pybind11::set>(gemPaths))
{
for (auto path : gemPaths)
{
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
gemInfos.push_back(gemInfo);
}
}
});
if (!result.IsSuccess())
{
return AZ::Failure(result.GetError());
}
return AZ::Success(AZStd::move(gemInfos));
}
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force)
{
// This process is currently limited to download a single gem at a time.
bool downloadSucceeded = false;
@@ -1179,7 +1229,7 @@ namespace O3DE::ProjectManager
QString_To_Py_String(gemName), // gem name
pybind11::none(), // destination path
false, // skip auto register
false, // force
force, // force overwrite
pybind11::cpp_function(
[this, gemProgressCallback](int bytesDownloaded, int totalBytes)
{
@@ -1209,30 +1259,19 @@ namespace O3DE::ProjectManager
m_requestCancelDownload = true;
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated)
{
QVector<GemInfo> gemInfos;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
bool updateAvaliableResult = false;
bool result = ExecuteWithLock(
[&]
{
auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")();
auto pyGemName = QString_To_Py_String(gemName);
auto pyLastUpdated = QString_To_Py_String(lastUpdated);
auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated);
if (pybind11::isinstance<pybind11::set>(gemPaths))
{
for (auto path : gemPaths)
{
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
gemInfos.push_back(gemInfo);
}
}
updateAvaliableResult = pythonUpdateAvaliableResult.cast<bool>();
});
if (!result.IsSuccess())
{
return AZ::Failure(result.GetError());
}
return AZ::Success(AZStd::move(gemInfos));
return result && updateAvaliableResult;
}
}
@@ -43,6 +43,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override;
// Project
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
@@ -64,9 +65,10 @@ namespace O3DE::ProjectManager
bool AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback) override;
void CancelDownload() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) override;
void CancelDownload() override;
bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
@@ -77,6 +79,7 @@ namespace O3DE::ProjectManager
GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
bool RegisterThisEngine();
bool StopPython();
@@ -94,11 +94,19 @@ namespace O3DE::ProjectManager
/**
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
* @param gemPath the path to the gem
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
/**
* Unregisters the gem from the specified project, or from the o3de_manifest.json if no project path is given
* @param gemPath the path to the gem
* @param projectPath the path to the project. If empty, will unregister the external path in o3de_manifest.json
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
// Projects
@@ -209,24 +217,34 @@ namespace O3DE::ProjectManager
*/
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
/**
* Downloads and registers a Gem.
* @param gemName the name of the Gem to download
* @param gemProgressCallback a callback function that is called with an int percentage download value
* @return an outcome with a string error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback) = 0;
/**
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Gathers all gem infos for all gems registered from repos.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
/**
* Downloads and registers a Gem.
* @param gemName the name of the Gem to download.
* @param gemProgressCallback a callback function that is called with an int percentage download value.
* @param force should we forcibly overwrite the old version of the gem.
* @return an outcome with a string error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> DownloadGem(
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
/**
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Checks if there is an update avaliable for a gem on a repo.
* @param gemName the name of the gem to check.
* @param lastUpdated last time the gem was update.
* @return true if update is avaliable, false if not.
*/
virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
@@ -96,6 +96,10 @@ set(FILES
Source/GemCatalog/GemListHeaderWidget.cpp
Source/GemCatalog/GemModel.h
Source/GemCatalog/GemModel.cpp
Source/GemCatalog/GemUninstallDialog.h
Source/GemCatalog/GemUninstallDialog.cpp
Source/GemCatalog/GemUpdateDialog.h
Source/GemCatalog/GemUpdateDialog.cpp
Source/GemCatalog/GemDependenciesDialog.h
Source/GemCatalog/GemDependenciesDialog.cpp
Source/GemCatalog/GemRequirementDialog.h