From 4899309b6a8a34fbadf546f1261f3100ed9ced84 Mon Sep 17 00:00:00 2001 From: AMZN-nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:40:34 -0700 Subject: [PATCH] Connect Adding and Removal of Gem Repo UI to CLI (#4729) * Connect Adding and Removal of Gem Repo UI to CLI Signed-off-by: nggieber * Addressed PR feedback Signed-off-by: nggieber --- .../Source/GemRepo/GemRepoAddDialog.cpp | 4 +- .../Source/GemRepo/GemRepoInfo.h | 2 +- .../Source/GemRepo/GemRepoInspector.cpp | 4 +- .../Source/GemRepo/GemRepoItemDelegate.cpp | 18 +++++- .../Source/GemRepo/GemRepoItemDelegate.h | 4 ++ .../Source/GemRepo/GemRepoListView.cpp | 7 ++- .../Source/GemRepo/GemRepoListView.h | 3 + .../Source/GemRepo/GemRepoModel.cpp | 6 +- .../Source/GemRepo/GemRepoModel.h | 4 +- .../Source/GemRepo/GemRepoScreen.cpp | 42 +++++++++++-- .../Source/GemRepo/GemRepoScreen.h | 1 + .../ProjectManager/Source/PythonBindings.cpp | 59 ++++++++++++++++--- .../ProjectManager/Source/PythonBindings.h | 3 +- .../Source/PythonBindingsInterface.h | 15 +++-- scripts/o3de/o3de/manifest.py | 3 +- scripts/o3de/o3de/repo.py | 2 +- 16 files changed, 144 insertions(+), 33 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp index 1839948e80..601c62d6e1 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoAddDialog.cpp @@ -7,7 +7,7 @@ */ #include -#include +#include #include #include @@ -40,7 +40,7 @@ namespace O3DE::ProjectManager instructionContextLabel->setAlignment(Qt::AlignLeft); vLayout->addWidget(instructionContextLabel); - m_repoPath = new FormLineEditWidget(tr("Repository Path"), "", this); + m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this); m_repoPath->setFixedWidth(600); vLayout->addWidget(m_repoPath); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h index 61220cefe7..f1d1c2a8a2 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInfo.h @@ -36,7 +36,7 @@ namespace O3DE::ProjectManager QString m_summary = "No summary provided."; QString m_additionalInfo = ""; QString m_directoryLink = ""; - QString m_repoLink = ""; + QString m_repoUri = ""; QStringList m_includedGemPaths = {}; QDateTime m_lastUpdated; }; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp index 93f5890b94..d065ab59f8 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp @@ -60,8 +60,8 @@ namespace O3DE::ProjectManager // 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)); + m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex)); + m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex)); // Repo summary m_summaryLabel->setText(m_model->GetSummary(modelIndex)); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index 58b10a1d1a..8589337b87 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp @@ -145,6 +145,11 @@ namespace O3DE::ProjectManager GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); return true; } + else if (keyEvent->key() == Qt::Key_X) + { + emit RemoveRepo(modelIndex); + return true; + } } if (event->type() == QEvent::MouseButtonPress) @@ -154,6 +159,7 @@ namespace O3DE::ProjectManager QRect fullRect, itemRect, contentRect; CalcRects(option, fullRect, itemRect, contentRect); const QRect buttonRect = CalcButtonRect(contentRect); + const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect); if (buttonRect.contains(mouseEvent->pos())) { @@ -161,6 +167,11 @@ namespace O3DE::ProjectManager GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); return true; } + else if (deleteButtonRect.contains(mouseEvent->pos())) + { + emit RemoveRepo(modelIndex); + return true; + } } return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); @@ -214,9 +225,14 @@ namespace O3DE::ProjectManager painter->restore(); } + QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const + { + const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2); + return QRect(topLeft, QSize(s_iconSize, s_iconSize)); + } + void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const { - painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon); painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon); } diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 08d1fdffae..560616bf9d 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h @@ -66,10 +66,14 @@ namespace O3DE::ProjectManager inline constexpr static int s_refreshIconSize = 14; inline constexpr static int s_refreshIconSpacing = 10; + signals: + void RemoveRepo(const QModelIndex& modelIndex); + protected: 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; + QRect CalcDeleteButtonRect(const QRect& contentRect) const; void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; void DrawEditButtons(QPainter* painter, const QRect& contentRect) const; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp index 54d5b337e5..53d6478954 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp @@ -9,6 +9,8 @@ #include #include +#include + namespace O3DE::ProjectManager { GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent) @@ -19,6 +21,9 @@ namespace O3DE::ProjectManager setModel(model); setSelectionModel(selectionModel); - setItemDelegate(new GemRepoItemDelegate(model, this)); + + GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this); + connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo); + setItemDelegate(itemDelegate); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h index b71b49f390..be2af4b5a6 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h @@ -25,5 +25,8 @@ namespace O3DE::ProjectManager public: explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr); ~GemRepoListView() = default; + + signals: + void RemoveRepo(const QModelIndex& modelIndex); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp index 61ac6dc8a3..7a9617e6c1 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager item->setData(gemRepoInfo.m_summary, RoleSummary); item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled); item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink); - item->setData(gemRepoInfo.m_repoLink, RoleRepoLink); + item->setData(gemRepoInfo.m_repoUri, RoleRepoUri); item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated); item->setData(gemRepoInfo.m_path, RolePath); item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo); @@ -83,9 +83,9 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleDirectoryLink).toString(); } - QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex) + QString GemRepoModel::GetRepoUri(const QModelIndex& modelIndex) { - return modelIndex.data(RoleRepoLink).toString(); + return modelIndex.data(RoleRepoUri).toString(); } QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex) diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h index ad139bc12b..f36b66ca48 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h @@ -35,7 +35,7 @@ namespace O3DE::ProjectManager 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 QString GetRepoUri(const QModelIndex& modelIndex); static QDateTime GetLastUpdated(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex); @@ -55,7 +55,7 @@ namespace O3DE::ProjectManager RoleSummary, RoleIsEnabled, RoleDirectoryLink, - RoleRepoLink, + RoleRepoUri, RoleLastUpdated, RolePath, RoleAdditionalInfo, diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index a233010225..82fa13505a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -79,21 +80,48 @@ namespace O3DE::ProjectManager if (repoAddDialog->exec() == QDialog::DialogCode::Accepted) { - QString repoUrl = repoAddDialog->GetRepoPath(); - if (repoUrl.isEmpty()) + QString repoUri = repoAddDialog->GetRepoPath(); + if (repoUri.isEmpty()) { + QMessageBox::warning(this, tr("No Input"), tr("Please provide a repo Uri.")); return; } - AZ::Outcome addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUrl); - if (addGemRepoResult.IsSuccess()) + bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri); + if (addGemRepoResult) { Reinit(); } else { - QMessageBox::critical(this, tr("Operation failed"), - QString("Failed to add gem repo: %1.
Error:
%2").arg(repoUrl, addGemRepoResult.GetError().c_str())); + QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri); + QMessageBox::critical(this, tr("Operation failed"), failureMessage); + AZ_Error("Project Manger", false, failureMessage.toUtf8()); + } + } + } + + void GemRepoScreen::HandleRemoveRepoButton(const QModelIndex& modelIndex) + { + QString repoName = m_gemRepoModel->GetName(modelIndex); + + QMessageBox::StandardButton warningResult = QMessageBox::warning( + this, tr("Remove Repo"), tr("Are you sure you would like to remove gem repo: %1?").arg(repoName), + QMessageBox::No | QMessageBox::Yes); + + if (warningResult == QMessageBox::Yes) + { + QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex); + bool removeGemRepoResult = PythonBindingsInterface::Get()->RemoveGemRepo(repoUri); + if (removeGemRepoResult) + { + Reinit(); + } + else + { + QString failureMessage = tr("Failed to remove gem repo: %1.").arg(repoUri); + QMessageBox::critical(this, tr("Operation failed"), failureMessage); + AZ_Error("Project Manger", false, failureMessage.toUtf8()); } } } @@ -251,6 +279,8 @@ namespace O3DE::ProjectManager m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this); middleVLayout->addWidget(m_gemRepoListView); + connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton); + hLayout->addLayout(middleVLayout); m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h index 284118b978..3ac89f6813 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h @@ -39,6 +39,7 @@ namespace O3DE::ProjectManager public slots: void HandleAddRepoButton(); + void HandleRemoveRepoButton(const QModelIndex& modelIndex); private: void FillModel(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index d91f08c73e..633116e8b6 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -939,17 +939,60 @@ namespace O3DE::ProjectManager } } - AZ::Outcome PythonBindings::AddGemRepo(const QString& repoUri) + bool PythonBindings::AddGemRepo(const QString& repoUri) { - // o3de scripts need method added - (void)repoUri; - return AZ::Failure("Adding Gem Repo not implemented yet in o3de scripts."); + bool registrationResult = false; + bool result = ExecuteWithLock( + [&] + { + auto pyUri = QString_To_Py_String(repoUri); + auto pythonRegistrationResult = m_register.attr("register")( + pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pyUri); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); + + return result && registrationResult; + } + + bool PythonBindings::RemoveGemRepo(const QString& repoUri) + { + bool registrationResult = false; + bool result = ExecuteWithLock( + [&] + { + auto pythonRegistrationResult = m_register.attr("register")( + pybind11::none(), // engine_path + pybind11::none(), // project_path + pybind11::none(), // gem_path + pybind11::none(), // external_subdir_path + pybind11::none(), // template_path + pybind11::none(), // restricted_path + QString_To_Py_String(repoUri), // repo_uri + pybind11::none(), // default_engines_folder + pybind11::none(), // default_projects_folder + pybind11::none(), // default_gems_folder + pybind11::none(), // default_templates_folder + pybind11::none(), // default_restricted_folder + pybind11::none(), // default_third_party_folder + pybind11::none(), // external_subdir_engine_path + pybind11::none(), // external_subdir_project_path + true, // remove + false // force + ); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); + + return result && registrationResult; } GemRepoInfo PythonBindings::GetGemRepoInfo(pybind11::handle repoUri) { GemRepoInfo gemRepoInfo; - gemRepoInfo.m_repoLink = Py_To_String(repoUri); + gemRepoInfo.m_repoUri = Py_To_String(repoUri); auto data = m_manifest.attr("get_repo_json_data")(repoUri); if (pybind11::isinstance(data)) @@ -957,7 +1000,7 @@ namespace O3DE::ProjectManager try { // required - gemRepoInfo.m_repoLink = Py_To_String(data["repo_uri"]); + gemRepoInfo.m_repoUri = Py_To_String(data["repo_uri"]); gemRepoInfo.m_name = Py_To_String(data["repo_name"]); gemRepoInfo.m_creator = Py_To_String(data["origin"]); @@ -1019,13 +1062,13 @@ namespace O3DE::ProjectManager #else 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_repoUri = "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"; + mockJaneRepo.m_repoUri = "https://github.com/o3de/o3de.org"; gemRepos.push_back(mockJaneRepo); #endif // MOCK_GEM_REPO_INFO diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 090094e9b6..d0704d0bd1 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -58,7 +58,8 @@ namespace O3DE::ProjectManager AZ::Outcome> GetProjectTemplates(const QString& projectPath = {}) override; // Gem Repos - AZ::Outcome AddGemRepo(const QString& repoUri) override; + bool AddGemRepo(const QString& repoUri) override; + bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; private: diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 589dfb604a..5daf543c11 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -169,11 +169,18 @@ namespace O3DE::ProjectManager // Gem Repos /** - * A gem repo to engine. Registers this gem repo with the current engine. - * @param repoUri the absolute filesystem path or url to the gem repo manifest file. - * @return An outcome with the success flag as well as an error message in case of a failure. + * Registers this gem repo with the current engine. + * @param repoUri the absolute filesystem path or url to the gem repo. + * @return true on success, false on failure. + */ + virtual bool AddGemRepo(const QString& repoUri) = 0; + + /** + * Unregisters this gem repo with the current engine. + * @param repoUri the absolute filesystem path or url to the gem repo. + * @return true on success, false on failure. */ - virtual AZ::Outcome AddGemRepo(const QString& repoUri) = 0; + virtual bool RemoveGemRepo(const QString& repoUri) = 0; /** * Get all available gem repo infos. Gathers all repos registered with the engine. diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index b665727a4e..d9cbe92829 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -536,7 +536,8 @@ def get_repo_path(repo_uri: str, cache_folder: str = None) -> pathlib.Path: if not cache_folder: cache_folder = get_o3de_cache_folder() - repo_sha256 = hashlib.sha256(repo_uri.encode()) + repo_manifest = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_manifest.encode()) return cache_folder / str(repo_sha256.hexdigest() + '.json') def get_registered(engine_name: str = None, diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 52abfc3386..7427a71c4a 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -71,7 +71,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, # Having a repo is also optional repo_list = [] try: - repo_list.add(repo_data['repos']) + repo_list.append(repo_data['repos']) except KeyError: pass