From ef3e31c740a576ed9c46aba7f9eb32f2049f110d Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 22 Jun 2021 09:15:08 +0100 Subject: [PATCH] Merge pull request #1454 from aws-lumberyard-dev/LYN-3619 New level dialog has no way to select a different folder --- Code/Sandbox/Editor/NewLevelDialog.cpp | 151 +++++++++++++++----- Code/Sandbox/Editor/NewLevelDialog.h | 14 +- Code/Sandbox/Editor/NewLevelDialog.ui | 185 ++++++++++++++++--------- 3 files changed, 245 insertions(+), 105 deletions(-) diff --git a/Code/Sandbox/Editor/NewLevelDialog.cpp b/Code/Sandbox/Editor/NewLevelDialog.cpp index 2b727665cd..75717462b7 100644 --- a/Code/Sandbox/Editor/NewLevelDialog.cpp +++ b/Code/Sandbox/Editor/NewLevelDialog.cpp @@ -17,7 +17,10 @@ // Qt #include +#include +#include #include +#include // Editor #include "NewTerrainDialog.h" @@ -30,11 +33,33 @@ AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING // Folder in which levels are stored static const char kNewLevelDialog_LevelsFolder[] = "Levels"; +class LevelFolderValidator : public QValidator +{ +public: + LevelFolderValidator(QObject* parent) + : QValidator(parent) + { + m_parentDialog = qobject_cast(parent); + } + + QValidator::State validate([[maybe_unused]] QString& input, [[maybe_unused]] int& pos) const override + { + if (m_parentDialog->ValidateLevel()) + { + return QValidator::Acceptable; + } + + return QValidator::Intermediate; + } + +private: + CNewLevelDialog* m_parentDialog; +}; + // CNewLevelDialog dialog CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=NULL*/) : QDialog(pParent) - , m_ilevelFolders(0) , m_bUpdate(false) , ui(new Ui::CNewLevelDialog) , m_initialized(false) @@ -43,46 +68,70 @@ CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=NULL*/) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("New Level")); - setMaximumSize(QSize(320, 280)); + setMaximumSize(QSize(430, 180)); adjustSize(); - // Default level folder is root (Levels/) - m_ilevelFolders = 0; - m_bIsResize = false; + + ui->TITLE->setText(tr("Assign a name and location to the new level.")); + ui->STATIC1->setText(tr("Location:")); + ui->STATIC2->setText(tr("Name:")); + // Level name only supports ASCII characters QRegExp rx("[_a-zA-Z0-9-]+"); QValidator* validator = new QRegExpValidator(rx, this); ui->LEVEL->setValidator(validator); - connect(ui->LEVEL_FOLDERS, SIGNAL(activated(int)), this, SLOT(OnCbnSelendokLevelFolders())); + validator = new LevelFolderValidator(this); + ui->LEVEL_FOLDERS->lineEdit()->setValidator(validator); + ui->LEVEL_FOLDERS->setErrorToolTip( + QString("The location must be a folder underneath the current project's %1 folder. (%2)") + .arg(kNewLevelDialog_LevelsFolder) + .arg(GetLevelsFolder())); + + ui->LEVEL_FOLDERS->setClearButtonEnabled(true); + QToolButton* clearButton = AzQtComponents::LineEdit::getClearButton(ui->LEVEL_FOLDERS->lineEdit()); + assert(clearButton); + connect(clearButton, &QToolButton::clicked, this, &CNewLevelDialog::OnClearButtonClicked); + + connect(ui->LEVEL_FOLDERS->lineEdit(), &QLineEdit::textEdited, this, &CNewLevelDialog::OnLevelNameChange); + connect(ui->LEVEL_FOLDERS, &AzQtComponents::BrowseEdit::attachedButtonTriggered, this, &CNewLevelDialog::PopupAssetPicker); + connect(ui->LEVEL, &QLineEdit::textChanged, this, &CNewLevelDialog::OnLevelNameChange); + m_levelFolders = GetLevelsFolder(); + m_level = ""; // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last. // Secondly, using singleShot() allows setFocus() slot of the QLineEdit instance to be invoked right after the event system // is ready to do so. Therefore, it is better to use singleShot() than directly call setFocus(). - QTimer::singleShot(0, ui->LEVEL, SLOT(setFocus())); + QTimer::singleShot(0, ui->LEVEL, SLOT(OnStartup())); + + ReloadLevelFolder(); } CNewLevelDialog::~CNewLevelDialog() { } +void CNewLevelDialog::OnStartup() +{ + UpdateData(false); + setFocus(); +} + void CNewLevelDialog::UpdateData(bool fromUi) { if (fromUi) { m_level = ui->LEVEL->text(); - m_levelFolders = ui->LEVEL_FOLDERS->currentText(); - m_ilevelFolders = ui->LEVEL_FOLDERS->currentIndex(); + m_levelFolders = ui->LEVEL_FOLDERS->text(); } else { ui->LEVEL->setText(m_level); - ui->LEVEL_FOLDERS->setCurrentText(m_levelFolders); - ui->LEVEL_FOLDERS->setCurrentIndex(m_ilevelFolders); + ui->LEVEL_FOLDERS->lineEdit()->setText(m_levelFolders); } } @@ -90,7 +139,7 @@ void CNewLevelDialog::UpdateData(bool fromUi) void CNewLevelDialog::OnInitDialog() { - ReloadLevelFolders(); + ReloadLevelFolder(); // Disable OK until some text is entered if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok)) @@ -104,28 +153,19 @@ void CNewLevelDialog::OnInitDialog() ////////////////////////////////////////////////////////////////////////// -void CNewLevelDialog::ReloadLevelFolders() +void CNewLevelDialog::ReloadLevelFolder() { - QString levelsFolder = QString(Path::GetEditingGameDataFolder().c_str()) + "/" + kNewLevelDialog_LevelsFolder; - m_itemFolders.clear(); - ui->LEVEL_FOLDERS->clear(); - ui->LEVEL_FOLDERS->addItem(QString(kNewLevelDialog_LevelsFolder) + '/'); - ReloadLevelFoldersRec(levelsFolder); + ui->LEVEL_FOLDERS->lineEdit()->clear(); + ui->LEVEL_FOLDERS->setText(QString(kNewLevelDialog_LevelsFolder) + '/'); } -////////////////////////////////////////////////////////////////////////// -void CNewLevelDialog::ReloadLevelFoldersRec(const QString& currentFolder) +QString CNewLevelDialog::GetLevelsFolder() const { - QDir dir(currentFolder); + QDir projectDir = QDir(Path::GetEditingGameDataFolder().c_str()); + QDir projectLevelsDir = QDir(QStringLiteral("%1/%2").arg(projectDir.absolutePath()).arg(kNewLevelDialog_LevelsFolder)); - QFileInfoList infoList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - - foreach(const QFileInfo &fi, infoList) - { - m_itemFolders.push_back(fi.baseName()); - ui->LEVEL_FOLDERS->addItem(QString(kNewLevelDialog_LevelsFolder) + '/' + fi.baseName()); - } + return projectLevelsDir.absolutePath(); } ////////////////////////////////////////////////////////////////////////// @@ -133,26 +173,47 @@ QString CNewLevelDialog::GetLevel() const { QString output = m_level; - if (m_itemFolders.size() > 0 && m_ilevelFolders > 0) + QDir projectLevelsDir = QDir(GetLevelsFolder()); + + if (!m_levelFolders.isEmpty()) { - output = m_itemFolders[m_ilevelFolders - 1] + "/" + m_level; + output = m_levelFolders + "/" + m_level; } - return output; + QString relativePath = projectLevelsDir.relativeFilePath(output); + + return relativePath; } -////////////////////////////////////////////////////////////////////////// -void CNewLevelDialog::OnCbnSelendokLevelFolders() +bool CNewLevelDialog::ValidateLevel() { - UpdateData(); + // Check that the selected folder is in or below the project/LEVELS folder. + QDir projectLevelsDir = QDir(GetLevelsFolder()); + + QString selectedFolder = ui->LEVEL_FOLDERS->text(); + QString absolutePath = QDir::cleanPath(projectLevelsDir.absoluteFilePath(selectedFolder)); + QString relativePath = projectLevelsDir.relativeFilePath(absolutePath); + + // Prevent saving to a different drive. + if (projectLevelsDir.absolutePath()[0] != absolutePath[0]) + { + return false; + } + + if (relativePath.startsWith("..")) + { + return false; + } + + return true; } void CNewLevelDialog::OnLevelNameChange() { - m_level = ui->LEVEL->text(); + UpdateData(true); // QRegExpValidator means the string will always be valid as long as it's not empty: - const bool valid = !m_level.isEmpty(); + const bool valid = !m_level.isEmpty() && ValidateLevel(); // Use the validity to dynamically change the Ok button's enabled state if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok)) @@ -161,6 +222,24 @@ void CNewLevelDialog::OnLevelNameChange() } } +void CNewLevelDialog::OnClearButtonClicked() +{ + ui->LEVEL_FOLDERS->lineEdit()->setText(GetLevelsFolder()); + UpdateData(true); + +} + +void CNewLevelDialog::PopupAssetPicker() +{ + QString newPath = QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose Destination Folder"), GetLevelsFolder()); + + if (!newPath.isEmpty()) + { + ui->LEVEL_FOLDERS->setText(newPath); + OnLevelNameChange(); + } +} + ////////////////////////////////////////////////////////////////////////// void CNewLevelDialog::IsResize(bool bIsResize) { diff --git a/Code/Sandbox/Editor/NewLevelDialog.h b/Code/Sandbox/Editor/NewLevelDialog.h index fd32c03700..f995ebd69c 100644 --- a/Code/Sandbox/Editor/NewLevelDialog.h +++ b/Code/Sandbox/Editor/NewLevelDialog.h @@ -34,6 +34,7 @@ #include +#include #include #endif @@ -50,28 +51,29 @@ public: CNewLevelDialog(QWidget* pParent = nullptr); // standard constructor ~CNewLevelDialog(); - QString GetLevel() const; void IsResize(bool bIsResize); - + bool ValidateLevel(); protected: void UpdateData(bool fromUi = true); void OnInitDialog(); - void ReloadLevelFolders(); - void ReloadLevelFoldersRec(const QString& currentFolder); + void ReloadLevelFolder(); void showEvent(QShowEvent* event); + QString GetLevelsFolder() const; + protected slots: - void OnCbnSelendokLevelFolders(); void OnLevelNameChange(); + void OnClearButtonClicked(); + void PopupAssetPicker(); + void OnStartup(); public: QString m_level; QString m_levelFolders; - int m_ilevelFolders; bool m_bIsResize; bool m_bUpdate; diff --git a/Code/Sandbox/Editor/NewLevelDialog.ui b/Code/Sandbox/Editor/NewLevelDialog.ui index 053616c78d..14227fbb53 100644 --- a/Code/Sandbox/Editor/NewLevelDialog.ui +++ b/Code/Sandbox/Editor/NewLevelDialog.ui @@ -6,74 +6,133 @@ 0 0 - 320 - 280 + 430 + 180 - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - - - - Level - - - - - - Name: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - LEVEL - + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + Assign a name and location to the new level. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + + border: 0px; + + + + + + Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + LEVEL + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 100 + 0 + + + + Location + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + LEVEL_FOLDERS + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Vertical + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + - - - - - Folder: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - LEVEL_FOLDERS - - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + + + AzQtComponents::BrowseEdit + QWidget +
AzQtComponents/Components/Widgets/BrowseEdit.h
+ 1 +
+