Merge pull request #5752 from aws-lumberyard-dev/puvvadar/gitflow_211118_o3de
Merge stabilization/2110
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <xxhash/xxhash.h>
|
||||
|
||||
namespace AssetBuilderSDK
|
||||
{
|
||||
const char* const ErrorWindow = "Error"; //Use this window name to log error messages.
|
||||
@@ -1599,4 +1601,70 @@ namespace AssetBuilderSDK
|
||||
{
|
||||
return m_errorsOccurred;
|
||||
}
|
||||
|
||||
AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut, int hashMsDelay)
|
||||
{
|
||||
constexpr AZ::u64 HashBufferSize = 1024 * 64;
|
||||
char buffer[HashBufferSize];
|
||||
|
||||
if(readStream.IsOpen() && readStream.CanRead())
|
||||
{
|
||||
AZ::IO::SizeType bytesRead;
|
||||
|
||||
auto* state = XXH64_createState();
|
||||
|
||||
if(state == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Failed to create hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (XXH64_reset(state, 0) == XXH_ERROR)
|
||||
{
|
||||
AZ_Assert(false, "Failed to reset hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked,
|
||||
// the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size
|
||||
// was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read
|
||||
// will be out of date in the edge cases where another process is actively writing to this file while this hash is running.
|
||||
// The stream's length ends up more accurate in this case, preventing this assert and shut down.
|
||||
// One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level,
|
||||
// the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change.
|
||||
AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast<AZ::IO::SizeType>(AZ_ARRAY_SIZE(buffer)));
|
||||
bytesRead = readStream.Read(remainingToRead, buffer);
|
||||
|
||||
if(bytesReadOut)
|
||||
{
|
||||
*bytesReadOut += bytesRead;
|
||||
}
|
||||
|
||||
XXH64_update(state, buffer, bytesRead);
|
||||
|
||||
// Used by unit tests to force the race condition mentioned above, to verify the crash fix.
|
||||
if(hashMsDelay > 0)
|
||||
{
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay));
|
||||
}
|
||||
|
||||
} while (bytesRead > 0);
|
||||
|
||||
auto hash = XXH64_digest(state);
|
||||
|
||||
XXH64_freeState(state);
|
||||
|
||||
return hash;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut, int hashMsDelay)
|
||||
{
|
||||
constexpr bool ErrorOnReadFailure = true;
|
||||
AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure);
|
||||
return GetHashFromIOStream(readStream, bytesReadOut, hashMsDelay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,6 +911,19 @@ namespace AssetBuilderSDK
|
||||
//! There can be multiple builders running at once, so we need to filter out ones coming from other builders
|
||||
AZStd::thread_id m_jobThreadId;
|
||||
};
|
||||
|
||||
//! Get hash for a whole file
|
||||
//! @filePath the path for the file
|
||||
//! @bytesReadOut output the read file size in bytes
|
||||
//! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading.
|
||||
AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
|
||||
//! Get hash for a generic IO stream
|
||||
//! @readStream the input readable stream
|
||||
//! @bytesReadOut output the read size in bytes
|
||||
//! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading.
|
||||
AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
|
||||
} // namespace AssetBuilderSDK
|
||||
|
||||
namespace AZ
|
||||
|
||||
@@ -32,6 +32,7 @@ ly_add_target(
|
||||
PUBLIC
|
||||
AZ::AzFramework
|
||||
AZ::AzToolsFramework
|
||||
3rdParty::xxhash
|
||||
)
|
||||
ly_add_source_properties(
|
||||
SOURCES AssetBuilderSDK/AssetBuilderSDK.cpp
|
||||
|
||||
@@ -32,7 +32,8 @@ struct FolderRootWatch::PlatformImplementation
|
||||
{
|
||||
if (m_iNotifyHandle < 0)
|
||||
{
|
||||
m_iNotifyHandle = inotify_init();
|
||||
// The CLOEXEC flag prevents the inotify watchers from copying on fork/exec
|
||||
m_iNotifyHandle = inotify_init1(IN_CLOEXEC);
|
||||
}
|
||||
return (m_iNotifyHandle >= 0);
|
||||
}
|
||||
|
||||
@@ -1161,7 +1161,7 @@ namespace AssetUtilities
|
||||
{
|
||||
#ifndef AZ_TESTS_ENABLED
|
||||
// Only used for unit tests, speed is critical for GetFileHash.
|
||||
AZ_UNUSED(hashMsDelay);
|
||||
hashMsDelay = 0;
|
||||
#endif
|
||||
bool useFileHashing = ShouldUseFileHashing();
|
||||
|
||||
@@ -1170,10 +1170,10 @@ namespace AssetUtilities
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u64 hash = 0;
|
||||
if(!force)
|
||||
{
|
||||
auto* fileStateInterface = AZ::Interface<AssetProcessor::IFileStateRequests>::Get();
|
||||
AZ::u64 hash = 0;
|
||||
|
||||
if (fileStateInterface && fileStateInterface->GetHash(filePath, &hash))
|
||||
{
|
||||
@@ -1181,64 +1181,8 @@ namespace AssetUtilities
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[FileHashBufferSize];
|
||||
|
||||
constexpr bool ErrorOnReadFailure = true;
|
||||
AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure);
|
||||
|
||||
if(readStream.IsOpen() && readStream.CanRead())
|
||||
{
|
||||
AZ::IO::SizeType bytesRead;
|
||||
|
||||
auto* state = XXH64_createState();
|
||||
|
||||
if(state == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Failed to create hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (XXH64_reset(state, 0) == XXH_ERROR)
|
||||
{
|
||||
AZ_Assert(false, "Failed to reset hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked,
|
||||
// the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size
|
||||
// was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read
|
||||
// will be out of date in the edge cases where another process is actively writing to this file while this hash is running.
|
||||
// The stream's length ends up more accurate in this case, preventing this assert and shut down.
|
||||
// One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level,
|
||||
// the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change.
|
||||
AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast<AZ::IO::SizeType>(AZ_ARRAY_SIZE(buffer)));
|
||||
bytesRead = readStream.Read(remainingToRead, buffer);
|
||||
|
||||
if(bytesReadOut)
|
||||
{
|
||||
*bytesReadOut += bytesRead;
|
||||
}
|
||||
|
||||
XXH64_update(state, buffer, bytesRead);
|
||||
#ifdef AZ_TESTS_ENABLED
|
||||
// Used by unit tests to force the race condition mentioned above, to verify the crash fix.
|
||||
if(hashMsDelay > 0)
|
||||
{
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay));
|
||||
}
|
||||
#endif
|
||||
|
||||
} while (bytesRead > 0);
|
||||
|
||||
auto hash = XXH64_digest(state);
|
||||
|
||||
XXH64_freeState(state);
|
||||
|
||||
return hash;
|
||||
}
|
||||
return 0;
|
||||
hash = AssetBuilderSDK::GetFileHash(filePath, bytesReadOut, hashMsDelay);
|
||||
return hash;
|
||||
}
|
||||
|
||||
AZ::u64 AdjustTimestamp(QDateTime timestamp)
|
||||
|
||||
@@ -238,7 +238,6 @@ namespace AssetUtilities
|
||||
// hashMsDelay is only for automated tests to test that writing to a file while it's hashing does not cause a crash.
|
||||
// hashMsDelay is not used in non-unit test builds.
|
||||
AZ::u64 GetFileHash(const char* filePath, bool force = false, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
inline constexpr AZ::u64 FileHashBufferSize = 1024 * 64;
|
||||
|
||||
//! Adjusts a timestamp to fix timezone settings and account for any precision adjustment needed
|
||||
AZ::u64 AdjustTimestamp(QDateTime timestamp);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:342c3eaccf68a178dfd8c2b1792a93a8c9197c8184dca11bf90706d7481df087
|
||||
size 1611268
|
||||
oid sha256:e9ad0383f3b917fa7f4efa307a8e109a70bb5f66deb197189d013f60eb8dc32c
|
||||
size 1010250
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:797794816e4b1702f1ae1f32b408c95c79eb1f8a95aba43cfad9cccc181b0bda
|
||||
size 1135182
|
||||
oid sha256:84aab95ec8a5e3ba6ecb3aff1a814afc3171a937aa658decd18c2740623bd172
|
||||
size 984146
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <GemRepo/GemRepoScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
@@ -73,13 +73,25 @@ namespace O3DE::ProjectManager
|
||||
emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes);
|
||||
}
|
||||
|
||||
void DownloadController::HandleResults(const QString& result)
|
||||
void DownloadController::HandleResults(const QString& result, const QString& detailedError)
|
||||
{
|
||||
bool succeeded = true;
|
||||
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem download"), result);
|
||||
if (!detailedError.isEmpty())
|
||||
{
|
||||
QMessageBox gemDownloadError;
|
||||
gemDownloadError.setIcon(QMessageBox::Critical);
|
||||
gemDownloadError.setWindowTitle(tr("Gem download"));
|
||||
gemDownloadError.setText(result);
|
||||
gemDownloadError.setDetailedText(detailedError);
|
||||
gemDownloadError.exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem download"), result);
|
||||
}
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
public slots:
|
||||
void UpdateUIProgress(int bytesDownloaded, int totalBytes);
|
||||
void HandleResults(const QString& result);
|
||||
void HandleResults(const QString& result, const QString& detailedError);
|
||||
|
||||
signals:
|
||||
void StartGemDownload(const QString& gemName);
|
||||
|
||||
@@ -24,16 +24,16 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
emit UpdateProgress(bytesDownloaded, totalBytes);
|
||||
};
|
||||
AZ::Outcome<void, AZStd::string> gemInfoResult =
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> gemInfoResult =
|
||||
PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true);
|
||||
|
||||
if (gemInfoResult.IsSuccess())
|
||||
{
|
||||
emit Done("");
|
||||
emit Done("", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
emit Done(tr("Gem download failed"));
|
||||
emit Done(gemInfoResult.GetError().first.c_str(), gemInfoResult.GetError().second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void UpdateProgress(int bytesDownloaded, int totalBytes);
|
||||
void Done(QString result = "");
|
||||
void Done(QString result = "", QString detailedResult = "");
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -72,12 +72,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum();
|
||||
}
|
||||
|
||||
void EngineScreenCtrl::NotifyCurrentScreen()
|
||||
|
||||
@@ -7,25 +7,32 @@
|
||||
*/
|
||||
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <TagWidget.h>
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <TagWidget.h>
|
||||
#include <QMenu>
|
||||
#include <QLocale>
|
||||
#include <QMovie>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
GemCartWidget::GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QScrollArea(parent)
|
||||
, m_gemModel(gemModel)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
setObjectName("GemCatalogCart");
|
||||
setWidgetResizable(true);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
|
||||
m_layout = new QVBoxLayout();
|
||||
m_layout->setSpacing(0);
|
||||
@@ -118,17 +125,15 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
return dependencies;
|
||||
});
|
||||
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||
}
|
||||
|
||||
CartOverlayWidget::~CartOverlayWidget()
|
||||
GemCartWidget::~GemCartWidget()
|
||||
{
|
||||
// disconnect from all download controller signals
|
||||
disconnect(m_downloadController, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
|
||||
void GemCartWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setFixedWidth(s_width);
|
||||
@@ -164,12 +169,12 @@ namespace O3DE::ProjectManager
|
||||
update();
|
||||
}
|
||||
|
||||
void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName)
|
||||
void GemCartWidget::OnCancelDownloadActivated(const QString& gemName)
|
||||
{
|
||||
m_downloadController->CancelGemDownload(gemName);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::CreateDownloadSection()
|
||||
void GemCartWidget::CreateDownloadSection()
|
||||
{
|
||||
m_downloadSectionWidget = new QWidget();
|
||||
m_downloadSectionWidget->setFixedWidth(s_width);
|
||||
@@ -223,12 +228,12 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// connect to download controller data changed
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCartWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCartWidget::GemDownloadRemoved);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &GemCartWidget::GemDownloadProgress);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadAdded(const QString& gemName)
|
||||
void GemCartWidget::GemDownloadAdded(const QString& gemName)
|
||||
{
|
||||
// Containing widget for the current download item
|
||||
QWidget* newGemDownloadWidget = new QWidget();
|
||||
@@ -246,7 +251,7 @@ namespace O3DE::ProjectManager
|
||||
nameProgressLayout->addStretch();
|
||||
QLabel* cancelText = new QLabel(tr("<a href=\"%1\">Cancel</a>").arg(gemName), newGemDownloadWidget);
|
||||
cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
|
||||
connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated);
|
||||
connect(cancelText, &QLabel::linkActivated, this, &GemCartWidget::OnCancelDownloadActivated);
|
||||
nameProgressLayout->addWidget(cancelText);
|
||||
downloadingGemLayout->addLayout(nameProgressLayout);
|
||||
|
||||
@@ -267,7 +272,7 @@ namespace O3DE::ProjectManager
|
||||
m_downloadingListWidget->show();
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadRemoved(const QString& gemName)
|
||||
void GemCartWidget::GemDownloadRemoved(const QString& gemName)
|
||||
{
|
||||
QWidget* gemToRemove = m_downloadingListWidget->findChild<QWidget*>(gemName);
|
||||
if (gemToRemove)
|
||||
@@ -289,7 +294,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
|
||||
void GemCartWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
QWidget* gemToUpdate = m_downloadingListWidget->findChild<QWidget*>(gemName);
|
||||
if (gemToUpdate)
|
||||
@@ -324,7 +329,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Tag> CartOverlayWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
QVector<Tag> GemCartWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
{
|
||||
QVector<Tag> tags;
|
||||
tags.reserve(gems.size());
|
||||
@@ -349,7 +354,7 @@ namespace O3DE::ProjectManager
|
||||
iconButton->setFocusPolicy(Qt::NoFocus);
|
||||
iconButton->setIcon(QIcon(":/Summary.svg"));
|
||||
iconButton->setFixedSize(s_iconSize, s_iconSize);
|
||||
connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
|
||||
m_layout->addWidget(iconButton);
|
||||
|
||||
m_countLabel = new QLabel();
|
||||
@@ -362,7 +367,7 @@ namespace O3DE::ProjectManager
|
||||
m_dropDownButton->setFocusPolicy(Qt::NoFocus);
|
||||
m_dropDownButton->setIcon(QIcon(":/CarrotArrowDown.svg"));
|
||||
m_dropDownButton->setFixedSize(s_arrowDownIconSize, s_arrowDownIconSize);
|
||||
connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
|
||||
m_layout->addWidget(m_dropDownButton);
|
||||
|
||||
// Adjust the label text whenever the model gets updated.
|
||||
@@ -377,28 +382,28 @@ namespace O3DE::ProjectManager
|
||||
m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty());
|
||||
|
||||
// Automatically close the overlay window in case there are no gems to be activated or deactivated anymore.
|
||||
if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
if (m_gemCart && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
m_cartOverlay = nullptr;
|
||||
m_gemCart->deleteLater();
|
||||
m_gemCart = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CartButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
ShowOverlay();
|
||||
ShowGemCart();
|
||||
}
|
||||
|
||||
void CartButton::hideEvent(QHideEvent*)
|
||||
{
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
m_cartOverlay->hide();
|
||||
m_gemCart->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void CartButton::ShowOverlay()
|
||||
void CartButton::ShowGemCart()
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
|
||||
@@ -407,37 +412,33 @@ namespace O3DE::ProjectManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
// Directly delete the former overlay before creating the new one.
|
||||
// Don't use deleteLater() here. This might overwrite the new overlay pointer
|
||||
// depending on the event queue.
|
||||
delete m_cartOverlay;
|
||||
delete m_gemCart;
|
||||
}
|
||||
|
||||
m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this);
|
||||
connect(m_cartOverlay, &QWidget::destroyed, this, [=]
|
||||
m_gemCart = new GemCartWidget(m_gemModel, m_downloadController, this);
|
||||
connect(m_gemCart, &QWidget::destroyed, this, [=]
|
||||
{
|
||||
// Reset the overlay pointer on destruction to prevent dangling pointers.
|
||||
m_cartOverlay = nullptr;
|
||||
m_gemCart = nullptr;
|
||||
// Tell header gem cart is no longer open
|
||||
UpdateGemCart(nullptr);
|
||||
});
|
||||
m_cartOverlay->show();
|
||||
m_gemCart->show();
|
||||
|
||||
const QPoint parentPos = m_dropDownButton->mapToParent(m_dropDownButton->pos());
|
||||
const QPoint globalPos = m_dropDownButton->mapToGlobal(m_dropDownButton->pos());
|
||||
const QPoint offset(-4, 10);
|
||||
m_cartOverlay->setGeometry(globalPos.x() - parentPos.x() - m_cartOverlay->width() + width() + offset.x(),
|
||||
globalPos.y() + offset.y(),
|
||||
m_cartOverlay->width(),
|
||||
m_cartOverlay->height());
|
||||
emit UpdateGemCart(m_gemCart);
|
||||
}
|
||||
|
||||
CartButton::~CartButton()
|
||||
{
|
||||
// Make sure the overlay window is automatically closed in case the gem catalog is destroyed.
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
m_gemCart->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,6 +515,17 @@ namespace O3DE::ProjectManager
|
||||
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
|
||||
|
||||
connect(
|
||||
m_cartButton, &CartButton::UpdateGemCart, this,
|
||||
[this](QWidget* gemCart)
|
||||
{
|
||||
GemCartShown(gemCart);
|
||||
if (gemCart)
|
||||
{
|
||||
emit UpdateGemCart(gemCart);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/)
|
||||
@@ -521,7 +533,7 @@ namespace O3DE::ProjectManager
|
||||
m_downloadSpinner->show();
|
||||
m_downloadLabel->show();
|
||||
m_downloadSpinnerMovie->start();
|
||||
m_cartButton->ShowOverlay();
|
||||
m_cartButton->ShowGemCart();
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/)
|
||||
@@ -534,8 +546,44 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemCartShown(bool state)
|
||||
{
|
||||
m_showGemCart = state;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::ReinitForProject()
|
||||
{
|
||||
m_filterLineEdit->setText({});
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||
{
|
||||
// Only show triangle when cart is shown
|
||||
if (!m_showGemCart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint buttonPos = m_cartButton->pos();
|
||||
const QSize buttonSize = m_cartButton->size();
|
||||
|
||||
// Draw isosceles triangle with top point touching bottom of cartButton
|
||||
// Bottom aligned with header bottom and top of right panel
|
||||
const QPoint topPoint(buttonPos.x() + buttonSize.width() / 2, buttonPos.y() + buttonSize.height());
|
||||
const QPoint bottomLeftPoint(topPoint.x() - 20, height());
|
||||
const QPoint bottomRightPoint(topPoint.x() + 20, height());
|
||||
|
||||
QPainterPath trianglePath;
|
||||
trianglePath.moveTo(topPoint);
|
||||
trianglePath.lineTo(bottomLeftPoint);
|
||||
trianglePath.lineTo(bottomRightPoint);
|
||||
trianglePath.lineTo(topPoint);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(trianglePath, QBrush(QColor("#555555")));
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <TagWidget.h>
|
||||
#include <QFrame>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QScrollArea>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
@@ -28,14 +30,14 @@ QT_FORWARD_DECLARE_CLASS(QMovie)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class CartOverlayWidget
|
||||
: public QWidget
|
||||
class GemCartWidget
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~CartOverlayWidget();
|
||||
GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~GemCartWidget();
|
||||
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
@@ -68,7 +70,10 @@ namespace O3DE::ProjectManager
|
||||
public:
|
||||
CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~CartButton();
|
||||
void ShowOverlay();
|
||||
void ShowGemCart();
|
||||
|
||||
signals:
|
||||
void UpdateGemCart(QWidget* gemCart);
|
||||
|
||||
private:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
@@ -78,7 +83,7 @@ namespace O3DE::ProjectManager
|
||||
QHBoxLayout* m_layout = nullptr;
|
||||
QLabel* m_countLabel = nullptr;
|
||||
QPushButton* m_dropDownButton = nullptr;
|
||||
CartOverlayWidget* m_cartOverlay = nullptr;
|
||||
GemCartWidget* m_gemCart = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
inline constexpr static int s_iconSize = 24;
|
||||
@@ -99,11 +104,16 @@ namespace O3DE::ProjectManager
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemCartShown(bool state = false);
|
||||
|
||||
signals:
|
||||
void AddGem();
|
||||
void OpenGemsRepo();
|
||||
void RefreshGems();
|
||||
void UpdateGemCart(QWidget* gemCart);
|
||||
|
||||
protected slots:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
@@ -113,5 +123,6 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_downloadLabel = nullptr;
|
||||
QMovie* m_downloadSpinnerMovie = nullptr;
|
||||
CartButton* m_cartButton = nullptr;
|
||||
bool m_showGemCart = false;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemListHeaderWidget.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <GemCatalog/GemRequirementDialog.h>
|
||||
@@ -28,6 +33,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QHash>
|
||||
#include <QStackedWidget>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -51,9 +57,12 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
connect(m_gemModel, &GemModel::dependencyGemStatusChanged, this, &GemCatalogScreen::OnDependencyGemStatusChanged);
|
||||
connect(m_gemModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, [this]{ ShowInspector(); });
|
||||
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_headerWidget, &GemCatalogHeaderWidget::UpdateGemCart, this, &GemCatalogScreen::UpdateAndShowGemCart);
|
||||
connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
@@ -61,8 +70,11 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
|
||||
|
||||
m_rightPanelStack = new QStackedWidget(this);
|
||||
m_rightPanelStack->setFixedWidth(240);
|
||||
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
m_gemInspector->setFixedWidth(240);
|
||||
|
||||
connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); });
|
||||
connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem);
|
||||
@@ -85,7 +97,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
hLayout->addWidget(filterWidget);
|
||||
hLayout->addLayout(middleVLayout);
|
||||
hLayout->addWidget(m_gemInspector);
|
||||
|
||||
hLayout->addWidget(m_rightPanelStack);
|
||||
m_rightPanelStack->addWidget(m_gemInspector);
|
||||
|
||||
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
|
||||
m_notificationsView->SetOffset(QPoint(10, 70));
|
||||
@@ -188,7 +202,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// add all the gem repos into the hash
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& allRepoGemInfos = allRepoGemInfosResult.GetValue();
|
||||
@@ -266,7 +280,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
notification += tr(" and ");
|
||||
}
|
||||
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
|
||||
if (added && (GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) ||
|
||||
(GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::DownloadFailed))
|
||||
{
|
||||
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
|
||||
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
|
||||
@@ -291,6 +306,18 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnDependencyGemStatusChanged(const QString& gemName)
|
||||
{
|
||||
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
bool added = GemModel::IsAddedDependency(modelIndex);
|
||||
if (added && (GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) ||
|
||||
(GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::DownloadFailed))
|
||||
{
|
||||
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
|
||||
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::SelectGem(const QString& gemName)
|
||||
{
|
||||
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
@@ -303,6 +330,8 @@ namespace O3DE::ProjectManager
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_gemListView->scrollTo(proxyIndex);
|
||||
|
||||
ShowInspector();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex)
|
||||
@@ -363,8 +392,12 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
|
||||
|
||||
// Remove gem from gems to be added
|
||||
const bool wasAdded = GemModel::WasPreviouslyAdded(modelIndex);
|
||||
const bool wasAddedDependency = GemModel::WasPreviouslyAddedDependency(modelIndex);
|
||||
|
||||
// Remove gem from gems to be added to update any dependencies
|
||||
GemModel::SetIsAdded(*m_gemModel, modelIndex, false);
|
||||
GemModel::DeactivateDependentGems(*m_gemModel, modelIndex);
|
||||
|
||||
// Unregister the gem
|
||||
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
|
||||
@@ -391,6 +424,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Select remote gem
|
||||
QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName);
|
||||
GemModel::SetWasPreviouslyAdded(*m_gemModel, remoteGemIndex, wasAdded);
|
||||
GemModel::SetWasPreviouslyAddedDependency(*m_gemModel, remoteGemIndex, wasAddedDependency);
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
@@ -435,7 +470,7 @@ namespace O3DE::ProjectManager
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& allRepoGemInfos = allRepoGemInfosResult.GetValue();
|
||||
@@ -491,6 +526,12 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::ShowInspector()
|
||||
{
|
||||
m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Inspector);
|
||||
m_headerWidget->GemCartShown();
|
||||
}
|
||||
|
||||
GemCatalogScreen::EnableDisableGemsResult GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
|
||||
{
|
||||
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
|
||||
@@ -523,7 +564,9 @@ namespace O3DE::ProjectManager
|
||||
const QString& gemPath = GemModel::GetPath(modelIndex);
|
||||
|
||||
// make sure any remote gems we added were downloaded successfully
|
||||
if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote && GemModel::GetDownloadStatus(modelIndex) != GemInfo::Downloaded)
|
||||
const GemInfo::DownloadStatus status = GemModel::GetDownloadStatus(modelIndex);
|
||||
if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote &&
|
||||
!(status == GemInfo::Downloaded || status == GemInfo::DownloadSuccessful))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr, "Cannot add gem that isn't downloaded",
|
||||
@@ -570,6 +613,18 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemRepos);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UpdateAndShowGemCart(QWidget* cartWidget)
|
||||
{
|
||||
QWidget* previousCart = m_rightPanelStack->widget(RightPanelWidgetOrder::Cart);
|
||||
if (previousCart)
|
||||
{
|
||||
m_rightPanelStack->removeWidget(previousCart);
|
||||
}
|
||||
|
||||
m_rightPanelStack->insertWidget(RightPanelWidgetOrder::Cart, cartWidget);
|
||||
m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Cart);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemDownloadResult(const QString& gemName, bool succeeded)
|
||||
{
|
||||
if (succeeded)
|
||||
@@ -620,6 +675,8 @@ namespace O3DE::ProjectManager
|
||||
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (index.isValid())
|
||||
{
|
||||
GemModel::SetIsAdded(*m_gemModel, index, false);
|
||||
GemModel::DeactivateDependentGems(*m_gemModel, index);
|
||||
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,24 @@
|
||||
#include <ScreenWidget.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogHeaderWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(GemFilterWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(GemListView)
|
||||
QT_FORWARD_DECLARE_CLASS(GemInspector)
|
||||
QT_FORWARD_DECLARE_CLASS(GemModel)
|
||||
QT_FORWARD_DECLARE_CLASS(GemSortFilterProxyModel)
|
||||
QT_FORWARD_DECLARE_CLASS(DownloadController)
|
||||
|
||||
class GemCatalogScreen
|
||||
: public ScreenWidget
|
||||
{
|
||||
@@ -47,6 +53,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
public slots:
|
||||
void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
|
||||
void OnDependencyGemStatusChanged(const QString& gemName);
|
||||
void OnAddGemClicked();
|
||||
void SelectGem(const QString& gemName);
|
||||
void OnGemDownloadResult(const QString& gemName, bool succeeded = true);
|
||||
@@ -62,14 +69,22 @@ namespace O3DE::ProjectManager
|
||||
|
||||
private slots:
|
||||
void HandleOpenGemRepo();
|
||||
|
||||
void UpdateAndShowGemCart(QWidget* cartWidget);
|
||||
void ShowInspector();
|
||||
|
||||
private:
|
||||
enum RightPanelWidgetOrder
|
||||
{
|
||||
Inspector = 0,
|
||||
Cart
|
||||
};
|
||||
|
||||
void FillModel(const QString& projectPath);
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
|
||||
|
||||
GemListView* m_gemListView = nullptr;
|
||||
QStackedWidget* m_rightPanelStack = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemCatalogHeaderWidget* m_headerWidget = nullptr;
|
||||
|
||||
@@ -53,10 +53,13 @@ namespace O3DE::ProjectManager
|
||||
Update(selectedIndices[0]);
|
||||
}
|
||||
|
||||
void SetLabelElidedText(QLabel* label, QString text)
|
||||
void SetLabelElidedText(QLabel* label, QString text, int labelWidth = 0)
|
||||
{
|
||||
QFontMetrics nameFontMetrics(label->font());
|
||||
int labelWidth = label->width();
|
||||
if (!labelWidth)
|
||||
{
|
||||
labelWidth = label->width();
|
||||
}
|
||||
|
||||
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
|
||||
if (labelWidth > 100)
|
||||
@@ -84,7 +87,8 @@ namespace O3DE::ProjectManager
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
m_summaryLabel->adjustSize();
|
||||
|
||||
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
|
||||
// Manually define remaining space to elide text because spacer would like to take all of the space
|
||||
SetLabelElidedText(m_licenseLinkLabel, m_model->GetLicenseText(modelIndex), width() - m_licenseLabel->width() - 35);
|
||||
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
|
||||
|
||||
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
|
||||
@@ -175,8 +179,8 @@ namespace O3DE::ProjectManager
|
||||
licenseHLayout->setAlignment(Qt::AlignLeft);
|
||||
m_mainLayout->addLayout(licenseHLayout);
|
||||
|
||||
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
licenseLabel->setText(tr("License: "));
|
||||
m_licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
m_licenseLabel->setText(tr("License: "));
|
||||
|
||||
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
|
||||
licenseHLayout->addWidget(m_licenseLinkLabel);
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_nameLabel = nullptr;
|
||||
QLabel* m_creatorLabel = nullptr;
|
||||
QLabel* m_summaryLabel = nullptr;
|
||||
QLabel* m_licenseLabel = nullptr;
|
||||
LinkLabel* m_licenseLinkLabel = nullptr;
|
||||
LinkLabel* m_directoryLinkLabel = nullptr;
|
||||
LinkLabel* m_documentationLinkLabel = nullptr;
|
||||
|
||||
@@ -357,6 +357,8 @@ namespace O3DE::ProjectManager
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
const QString dependencyName = gemModel->GetName(dependency);
|
||||
gemModel->emit dependencyGemStatusChanged(dependencyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,6 +383,8 @@ namespace O3DE::ProjectManager
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
const QString dependencyName = gemModel->GetName(dependency);
|
||||
gemModel->emit dependencyGemStatusChanged(dependencyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,6 +483,23 @@ namespace O3DE::ProjectManager
|
||||
return previouslyAdded && !added;
|
||||
}
|
||||
|
||||
void GemModel::DeactivateDependentGems(QAbstractItemModel& model, const QModelIndex& modelIndex)
|
||||
{
|
||||
GemModel* gemModel = GetSourceModel(&model);
|
||||
AZ_Assert(gemModel, "Failed to obtain GemModel");
|
||||
|
||||
QVector<QModelIndex> dependentGems = gemModel->GatherDependentGems(modelIndex);
|
||||
if (!dependentGems.isEmpty())
|
||||
{
|
||||
// we need to deactivate all gems that depend on this one
|
||||
for (auto dependentModelIndex : dependentGems)
|
||||
{
|
||||
SetIsAdded(model, dependentModelIndex, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status)
|
||||
{
|
||||
model.setData(modelIndex, status, RoleDownloadStatus);
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace O3DE::ProjectManager
|
||||
static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false);
|
||||
static bool HasRequirement(const QModelIndex& modelIndex);
|
||||
static void UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded);
|
||||
static void DeactivateDependentGems(QAbstractItemModel& model, const QModelIndex& modelIndex);
|
||||
static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status);
|
||||
|
||||
bool DoGemsToBeAddedHaveRequirements() const;
|
||||
@@ -113,6 +114,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
|
||||
void dependencyGemStatusChanged(const QString& gemName);
|
||||
|
||||
protected slots:
|
||||
void OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_additionalInfo = "";
|
||||
QString m_directoryLink = "";
|
||||
QString m_repoUri = "";
|
||||
QStringList m_includedGemPaths = {};
|
||||
QStringList m_includedGemUris = {};
|
||||
QDateTime m_lastUpdated;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <GemRepo/GemRepoInspector.h>
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
@@ -60,8 +61,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Repo name and url link
|
||||
m_nameLabel->setText(m_model->GetName(modelIndex));
|
||||
m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex));
|
||||
m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex));
|
||||
|
||||
const QString repoUri = m_model->GetRepoUri(modelIndex);
|
||||
m_repoLinkLabel->setText(repoUri);
|
||||
m_repoLinkLabel->SetUrl(repoUri);
|
||||
|
||||
// Repo summary
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
|
||||
item->setData(gemRepoInfo.m_path, RolePath);
|
||||
item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo);
|
||||
item->setData(gemRepoInfo.m_includedGemPaths, RoleIncludedGems);
|
||||
item->setData(gemRepoInfo.m_includedGemUris, RoleIncludedGems);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RolePath).toString();
|
||||
}
|
||||
|
||||
QStringList GemRepoModel::GetIncludedGemPaths(const QModelIndex& modelIndex)
|
||||
QStringList GemRepoModel::GetIncludedGemUris(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleIncludedGems).toStringList();
|
||||
}
|
||||
@@ -118,23 +118,19 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QVector<GemInfo> GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex)
|
||||
{
|
||||
QVector<GemInfo> allGemInfos;
|
||||
QStringList repoGemPaths = GetIncludedGemPaths(modelIndex);
|
||||
QString repoUri = GetRepoUri(modelIndex);
|
||||
|
||||
for (const QString& gemPath : repoGemPaths)
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForRepo(repoUri);
|
||||
if (gemInfosResult.IsSuccess())
|
||||
{
|
||||
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 gemInfosResult.GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gems not found"), tr("Cannot find info for gems from repo %1").arg(GetName(modelIndex)));
|
||||
}
|
||||
|
||||
return allGemInfos;
|
||||
return QVector<GemInfo>();
|
||||
}
|
||||
|
||||
bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace O3DE::ProjectManager
|
||||
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
|
||||
static QString GetPath(const QModelIndex& modelIndex);
|
||||
|
||||
static QStringList GetIncludedGemPaths(const QModelIndex& modelIndex);
|
||||
static QStringList GetIncludedGemUris(const QModelIndex& modelIndex);
|
||||
static QVector<Tag> GetIncludedGemTags(const QModelIndex& modelIndex);
|
||||
static QVector<GemInfo> GetIncludedGemInfos(const QModelIndex& modelIndex);
|
||||
|
||||
|
||||
@@ -92,8 +92,9 @@ namespace O3DE::ProjectManager
|
||||
return;
|
||||
}
|
||||
|
||||
bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
|
||||
if (addGemRepoResult)
|
||||
AZ::Outcome < void,
|
||||
AZStd::pair<AZStd::string, AZStd::string>> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
|
||||
if (addGemRepoResult.IsSuccess())
|
||||
{
|
||||
Reinit();
|
||||
emit OnRefresh();
|
||||
@@ -101,8 +102,21 @@ namespace O3DE::ProjectManager
|
||||
else
|
||||
{
|
||||
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());
|
||||
if (!addGemRepoResult.GetError().second.empty())
|
||||
{
|
||||
QMessageBox addRepoError;
|
||||
addRepoError.setIcon(QMessageBox::Critical);
|
||||
addRepoError.setWindowTitle(failureMessage);
|
||||
addRepoError.setText(addGemRepoResult.GetError().first.c_str());
|
||||
addRepoError.setDetailedText(addGemRepoResult.GetError().second.c_str());
|
||||
addRepoError.exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, failureMessage, addGemRepoResult.GetError().first.c_str());
|
||||
}
|
||||
|
||||
AZ_Error("Project Manager", false, failureMessage.toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ProjectBuilderWorker.h>
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent)
|
||||
@@ -27,6 +29,15 @@ namespace O3DE::ProjectManager
|
||||
m_worker = new ProjectBuilderWorker(m_projectInfo);
|
||||
m_worker->moveToThread(&m_workerThread);
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
// Remove key here in case Project Manager crashing while building that causes HandleResults to not be called
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
settingsRegistry->Remove(settingsKey.toStdString().c_str());
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
|
||||
connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater);
|
||||
connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject);
|
||||
connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults);
|
||||
@@ -80,6 +91,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void ProjectBuilderController::HandleResults(const QString& result)
|
||||
{
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
if (result.contains(tr("log")))
|
||||
@@ -109,12 +122,26 @@ namespace O3DE::ProjectManager
|
||||
emit NotifyBuildProject(m_projectInfo);
|
||||
}
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Remove(settingsKey.toStdString().c_str());
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
|
||||
emit Done(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_projectInfo.m_buildFailed = false;
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Set(settingsKey.toStdString().c_str(), true);
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
}
|
||||
|
||||
emit Done(true);
|
||||
|
||||
@@ -203,6 +203,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Configure Gems..."), this, [this]() { emit EditProjectGems(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); });
|
||||
menu->addAction(tr("Open CMake GUI..."), this, [this]() { emit OpenCMakeGUI(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace O3DE::ProjectManager
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
void EditProjectGems(const QString& projectName);
|
||||
void CopyProject(const ProjectInfo& projectInfo);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 "ProjectManagerSettings.h"
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
void SaveProjectManagerSettings()
|
||||
{
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
|
||||
dumperSettings.m_prettifyOutput = true;
|
||||
dumperSettings.m_jsonPointerPrefix = ProjectManagerKeyPrefix;
|
||||
|
||||
AZStd::string stringBuffer;
|
||||
AZ::IO::ByteContainerStream stringStream(&stringBuffer);
|
||||
if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(
|
||||
*settingsRegistry, ProjectManagerKeyPrefix, stringStream, dumperSettings))
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream");
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::IO::FixedMaxPath o3deUserPath = AZ::Utils::GetO3deManifestDirectory();
|
||||
o3deUserPath /= AZ::SettingsRegistryInterface::RegistryFolder;
|
||||
o3deUserPath /= "ProjectManager.setreg";
|
||||
|
||||
bool saved = false;
|
||||
constexpr auto configurationMode =
|
||||
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;
|
||||
|
||||
AZ::IO::SystemFile outputFile;
|
||||
if (outputFile.Open(o3deUserPath.c_str(), configurationMode))
|
||||
{
|
||||
saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size();
|
||||
}
|
||||
|
||||
AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str());
|
||||
}
|
||||
|
||||
QString GetProjectBuiltSuccessfullyKey(const QString& projectName)
|
||||
{
|
||||
return QString("%1/Projects/%2/BuiltSuccessfully").arg(ProjectManagerKeyPrefix).arg(projectName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 <QString>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager";
|
||||
|
||||
void SaveProjectManagerSettings();
|
||||
QString GetProjectBuiltSuccessfullyKey(const QString& projectName);
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QStandardPaths>
|
||||
#include <QScrollArea>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -33,11 +34,23 @@ namespace O3DE::ProjectManager
|
||||
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
|
||||
QFrame* projectSettingsFrame = new QFrame(this);
|
||||
projectSettingsFrame->setObjectName("projectSettings");
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
|
||||
// you cannot remove content margins in qss
|
||||
m_verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setMargin(0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
projectSettingsFrame->setLayout(vLayout);
|
||||
|
||||
QScrollArea* scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
vLayout->addWidget(scrollArea);
|
||||
|
||||
QWidget* scrollWidget = new QWidget(this);
|
||||
scrollArea->setWidget(scrollWidget);
|
||||
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
m_verticalLayout->setMargin(0);
|
||||
m_verticalLayout->setAlignment(Qt::AlignTop);
|
||||
scrollWidget->setLayout(m_verticalLayout);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), "", this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectNameUpdated);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
#include <AzCore/Platform.h>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -181,6 +183,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
|
||||
connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
|
||||
connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
@@ -269,17 +272,36 @@ namespace O3DE::ProjectManager
|
||||
// Add any missing project buttons and restore buttons to default state
|
||||
for (const ProjectInfo& project : projectsVector)
|
||||
{
|
||||
ProjectButton* currentButton = nullptr;
|
||||
if (!m_projectButtons.contains(QDir::toNativeSeparators(project.m_path)))
|
||||
{
|
||||
m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), CreateProjectButton(project));
|
||||
currentButton = CreateProjectButton(project);
|
||||
m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), currentButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto projectButtonIter = m_projectButtons.find(QDir::toNativeSeparators(project.m_path));
|
||||
if (projectButtonIter != m_projectButtons.end())
|
||||
{
|
||||
projectButtonIter.value()->RestoreDefaultState();
|
||||
m_projectsFlowLayout->addWidget(projectButtonIter.value());
|
||||
currentButton = projectButtonIter.value();
|
||||
currentButton->RestoreDefaultState();
|
||||
m_projectsFlowLayout->addWidget(currentButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether project manager has successfully built the project
|
||||
if (currentButton)
|
||||
{
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
bool projectBuiltSuccessfully = false;
|
||||
if (settingsRegistry)
|
||||
{
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(project.m_projectName);
|
||||
settingsRegistry->Get(projectBuiltSuccessfully, settingsKey.toStdString().c_str());
|
||||
}
|
||||
if (!projectBuiltSuccessfully)
|
||||
{
|
||||
currentButton->ShowBuildRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,6 +470,14 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectInfo.m_path))
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace O3DE::ProjectManager
|
||||
void HandleAddProjectButton();
|
||||
void HandleOpenProject(const QString& projectPath);
|
||||
void HandleEditProject(const QString& projectPath);
|
||||
void HandleEditProjectGems(const QString& projectPath);
|
||||
void HandleCopyProject(const ProjectInfo& projectInfo);
|
||||
void HandleRemoveProject(const QString& projectPath);
|
||||
void HandleDeleteProject(const QString& projectPath);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
|
||||
#include <QDir>
|
||||
@@ -210,6 +211,16 @@ namespace RedirectOutput
|
||||
});
|
||||
|
||||
SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, []([[maybe_unused]] const char* msg) {
|
||||
AZStd::string lastPythonError = msg;
|
||||
constexpr const char* pythonErrorPrefix = "ERROR:root:";
|
||||
constexpr size_t lengthOfErrorPrefix = AZStd::char_traits<char>::length(pythonErrorPrefix);
|
||||
auto errorPrefix = lastPythonError.find(pythonErrorPrefix);
|
||||
if (errorPrefix != AZStd::string::npos)
|
||||
{
|
||||
lastPythonError.erase(errorPrefix, lengthOfErrorPrefix);
|
||||
}
|
||||
O3DE::ProjectManager::PythonBindingsInterface::Get()->AddErrorString(lastPythonError);
|
||||
|
||||
AZ_TracePrintf("Python", msg);
|
||||
});
|
||||
|
||||
@@ -376,6 +387,8 @@ namespace O3DE::ProjectManager
|
||||
pybind11::gil_scoped_release release;
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
ClearErrorStrings();
|
||||
|
||||
try
|
||||
{
|
||||
executionCallback();
|
||||
@@ -1051,7 +1064,7 @@ namespace O3DE::ProjectManager
|
||||
return result && refreshResult;
|
||||
}
|
||||
|
||||
bool PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
@@ -1065,7 +1078,12 @@ namespace O3DE::ProjectManager
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
if (!result || !registrationResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
bool PythonBindings::RemoveGemRepo(const QString& repoUri)
|
||||
@@ -1135,11 +1153,11 @@ namespace O3DE::ProjectManager
|
||||
gemRepoInfo.m_isEnabled = false;
|
||||
}
|
||||
|
||||
if (data.contains("gem_paths"))
|
||||
if (data.contains("gems"))
|
||||
{
|
||||
for (auto gemPath : data["gem_paths"])
|
||||
for (auto gemPath : data["gems"])
|
||||
{
|
||||
gemRepoInfo.m_includedGemPaths.push_back(Py_To_String(gemPath));
|
||||
gemRepoInfo.m_includedGemUris.push_back(Py_To_String(gemPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1188,7 +1206,35 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemRepos));
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetGemInfosForRepo(const QString& repoUri)
|
||||
{
|
||||
QVector<GemInfo> gemInfos;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto pyUri = QString_To_Py_String(repoUri);
|
||||
auto gemPaths = m_repo.attr("get_gem_json_paths_from_cached_repo")(pyUri);
|
||||
|
||||
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<QVector<GemInfo>, AZStd::string> PythonBindings::GetGemInfosForAllRepos()
|
||||
{
|
||||
QVector<GemInfo> gemInfos;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
@@ -1215,7 +1261,7 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemInfos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, 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.
|
||||
@@ -1244,11 +1290,12 @@ namespace O3DE::ProjectManager
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
AZStd::pair<AZStd::string, AZStd::string> pythonRunError(result.GetError(), result.GetError());
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(AZStd::move(pythonRunError));
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to download gem.");
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
@@ -1274,4 +1321,23 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return result && updateAvaliableResult;
|
||||
}
|
||||
|
||||
AZStd::pair<AZStd::string, AZStd::string> PythonBindings::GetSimpleDetailedErrorPair()
|
||||
{
|
||||
AZStd::string detailedString = m_pythonErrorStrings.size() == 1
|
||||
? ""
|
||||
: AZStd::accumulate(m_pythonErrorStrings.begin(), m_pythonErrorStrings.end(), AZStd::string(""));
|
||||
|
||||
return AZStd::pair<AZStd::string, AZStd::string>(m_pythonErrorStrings.front(), detailedString);
|
||||
}
|
||||
|
||||
void PythonBindings::AddErrorString(AZStd::string errorString)
|
||||
{
|
||||
m_pythonErrorStrings.push_back(errorString);
|
||||
}
|
||||
|
||||
void PythonBindings::ClearErrorStrings()
|
||||
{
|
||||
m_pythonErrorStrings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +62,19 @@ namespace O3DE::ProjectManager
|
||||
// Gem Repos
|
||||
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
|
||||
bool RefreshAllGemRepos() override;
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() 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;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForAllRepos() override;
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, 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;
|
||||
|
||||
void AddErrorString(AZStd::string errorString) override;
|
||||
void ClearErrorStrings() override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
|
||||
@@ -82,6 +87,7 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
|
||||
bool RegisterThisEngine();
|
||||
bool StopPython();
|
||||
AZStd::pair<AZStd::string, AZStd::string> GetSimpleDetailedErrorPair();
|
||||
|
||||
|
||||
bool m_pythonStarted = false;
|
||||
@@ -101,5 +107,6 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_pathlib;
|
||||
|
||||
bool m_requestCancelDownload = false;
|
||||
AZStd::vector<AZStd::string> m_pythonErrorStrings;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,9 +200,9 @@ namespace O3DE::ProjectManager
|
||||
/**
|
||||
* 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.
|
||||
* @return an outcome with a pair of string error and detailed messages on failure.
|
||||
*/
|
||||
virtual bool AddGemRepo(const QString& repoUri) = 0;
|
||||
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Unregisters this gem repo with the current engine.
|
||||
@@ -217,20 +217,27 @@ namespace O3DE::ProjectManager
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
|
||||
|
||||
/**
|
||||
* Gathers all gem infos from the provided repo
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForRepo(const QString& repoUri) = 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;
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForAllRepos() = 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.
|
||||
* @return an outcome with a pair of string error and detailed messages on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> DownloadGem(
|
||||
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
|
||||
|
||||
/**
|
||||
@@ -245,6 +252,17 @@ namespace O3DE::ProjectManager
|
||||
* @return true if update is avaliable, false if not.
|
||||
*/
|
||||
virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0;
|
||||
|
||||
/**
|
||||
* Add an error string to be returned when the current python call is complete.
|
||||
* @param The error string to be displayed.
|
||||
*/
|
||||
virtual void AddErrorString(AZStd::string errorString) = 0;
|
||||
|
||||
/**
|
||||
* Clears the current list of error strings.
|
||||
*/
|
||||
virtual void ClearErrorStrings() = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -47,9 +47,9 @@ namespace O3DE::ProjectManager
|
||||
return tr("Missing");
|
||||
}
|
||||
|
||||
virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
virtual bool ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
return false;
|
||||
return GetScreenEnum() == screen;
|
||||
}
|
||||
virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
{
|
||||
@@ -58,7 +58,6 @@ namespace O3DE::ProjectManager
|
||||
//! Notify this screen it is the current screen
|
||||
virtual void NotifyCurrentScreen()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include <UpdateProjectCtrl.h>
|
||||
#include <UpdateProjectSettingsScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
@@ -94,6 +97,17 @@ namespace O3DE::ProjectManager
|
||||
return ProjectManagerScreen::UpdateProject;
|
||||
}
|
||||
|
||||
bool UpdateProjectCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
// Do not include GemRepos because we don't want to advertise jumping to it from all other screens here
|
||||
return screen == GetScreenEnum() || screen == ProjectManagerScreen::GemCatalog;
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::GoToScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
OnChangeScreenRequest(screen);
|
||||
}
|
||||
|
||||
// Called when pressing "Edit Project Settings..."
|
||||
void UpdateProjectCtrl::NotifyCurrentScreen()
|
||||
{
|
||||
@@ -114,6 +128,16 @@ namespace O3DE::ProjectManager
|
||||
m_stack->setCurrentWidget(m_gemRepoScreen);
|
||||
Update();
|
||||
}
|
||||
else if (screen == ProjectManagerScreen::GemCatalog)
|
||||
{
|
||||
m_stack->setCurrentWidget(m_gemCatalogScreen);
|
||||
Update();
|
||||
}
|
||||
else if (screen == ProjectManagerScreen::UpdateProjectSettings)
|
||||
{
|
||||
m_stack->setCurrentWidget(m_updateSettingsScreen);
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
emit ChangeScreenRequest(screen);
|
||||
@@ -280,6 +304,21 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
if (newProjectSettings.m_projectName != m_projectInfo.m_projectName)
|
||||
{
|
||||
// update reg key
|
||||
QString oldSettingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
QString newSettingsKey = GetProjectBuiltSuccessfullyKey(newProjectSettings.m_projectName);
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
bool projectBuiltSuccessfully = false;
|
||||
if (settingsRegistry && settingsRegistry->Get(projectBuiltSuccessfully, oldSettingsKey.toStdString().c_str()))
|
||||
{
|
||||
settingsRegistry->Set(newSettingsKey.toStdString().c_str(), projectBuiltSuccessfully);
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (!newProjectSettings.m_newPreviewImagePath.isEmpty())
|
||||
{
|
||||
if (!ProjectUtils::ReplaceProjectFile(
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace O3DE::ProjectManager
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogScreen)
|
||||
QT_FORWARD_DECLARE_CLASS(GemRepoScreen)
|
||||
|
||||
class UpdateProjectCtrl : public ScreenWidget
|
||||
class UpdateProjectCtrl
|
||||
: public ScreenWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -32,7 +33,8 @@ namespace O3DE::ProjectManager
|
||||
~UpdateProjectCtrl() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
protected:
|
||||
bool ContainsScreen(ProjectManagerScreen screen) override;
|
||||
void GoToScreen(ProjectManagerScreen screen) override;
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace O3DE::ProjectManager
|
||||
previewExtrasLayout->setContentsMargins(50, 0, 0, 0);
|
||||
|
||||
QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.")
|
||||
.arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight)));
|
||||
.arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight)));
|
||||
projectPreviewLabel->setObjectName("projectPreviewLabel");
|
||||
previewExtrasLayout->addWidget(projectPreviewLabel);
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ set(FILES
|
||||
Source/CreateProjectCtrl.cpp
|
||||
Source/UpdateProjectCtrl.h
|
||||
Source/UpdateProjectCtrl.cpp
|
||||
Source/ProjectManagerSettings.h
|
||||
Source/ProjectManagerSettings.cpp
|
||||
Source/ProjectsScreen.h
|
||||
Source/ProjectsScreen.cpp
|
||||
Source/ProjectSettingsScreen.h
|
||||
|
||||
Reference in New Issue
Block a user