2
0

NewLevelDialog.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include <AzCore/Utils/Utils.h>
  10. #include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
  11. #include "NewLevelDialog.h"
  12. // Qt
  13. #include <QtWidgets/QPushButton>
  14. #include <QFileDialog>
  15. #include <QMessageBox>
  16. #include <QTimer>
  17. #include <QToolButton>
  18. #include <QListWidgetItem>
  19. AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
  20. #include <ui_NewLevelDialog.h>
  21. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
  22. // Folder in which levels are stored
  23. static const char kNewLevelDialog_LevelsFolder[] = "Levels";
  24. static constexpr const char* RegistryKey_CustomTemplatePaths = "/O3DE/Preferences/Prefab/CustomTemplatePaths";
  25. class LevelFolderValidator : public QValidator
  26. {
  27. public:
  28. LevelFolderValidator(QObject* parent)
  29. : QValidator(parent)
  30. {
  31. m_parentDialog = qobject_cast<CNewLevelDialog*>(parent);
  32. }
  33. QValidator::State validate([[maybe_unused]] QString& input, [[maybe_unused]] int& pos) const override
  34. {
  35. if (m_parentDialog->ValidateLevel())
  36. {
  37. return QValidator::Acceptable;
  38. }
  39. return QValidator::Intermediate;
  40. }
  41. private:
  42. CNewLevelDialog* m_parentDialog;
  43. };
  44. static QString ChangeFileExtension(const QString& filePath, const QString& newExtension)
  45. {
  46. QFileInfo fileInfo(filePath);
  47. QString newFilePath = fileInfo.absolutePath() + QDir::separator() + fileInfo.baseName() + "." + newExtension;
  48. return newFilePath;
  49. }
  50. // CNewLevelDialog dialog
  51. CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=nullptr*/)
  52. : QDialog(pParent)
  53. , ui(new Ui::CNewLevelDialog)
  54. , m_initialized(false)
  55. {
  56. ui->setupUi(this);
  57. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  58. setWindowTitle(tr("New Level"));
  59. setStyleSheet("QListWidget::item {height: 148px; padding-left: 0px; padding-right: 0px; background-color: transparent;}");
  60. InitTemplateListWidget();
  61. // Level name only supports ASCII characters
  62. QRegExp rx("[_a-zA-Z0-9-]+");
  63. QValidator* validator = new QRegExpValidator(rx, this);
  64. ui->LEVEL->setValidator(validator);
  65. validator = new LevelFolderValidator(this);
  66. ui->LEVEL_FOLDERS->lineEdit()->setValidator(validator);
  67. ui->LEVEL_FOLDERS->setErrorToolTip(
  68. QString("The location must be a folder underneath the current project's %1 folder. (%2)")
  69. .arg(kNewLevelDialog_LevelsFolder)
  70. .arg(GetLevelsFolder()));
  71. ui->LEVEL_FOLDERS->setClearButtonEnabled(true);
  72. QToolButton* clearButton = AzQtComponents::LineEdit::getClearButton(ui->LEVEL_FOLDERS->lineEdit());
  73. assert(clearButton);
  74. connect(clearButton, &QToolButton::clicked, this, &CNewLevelDialog::OnClearButtonClicked);
  75. connect(ui->LEVEL_FOLDERS->lineEdit(), &QLineEdit::textEdited, this, &CNewLevelDialog::OnLevelNameChange);
  76. connect(ui->LEVEL_FOLDERS, &AzQtComponents::BrowseEdit::attachedButtonTriggered, this, &CNewLevelDialog::PopupAssetPicker);
  77. connect(ui->LEVEL, &QLineEdit::textChanged, this, &CNewLevelDialog::OnLevelNameChange);
  78. m_levelFolders = GetLevelsFolder();
  79. m_level = "";
  80. // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which
  81. // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last.
  82. // in OnStartup()
  83. // Secondly, using singleShot() allows OnStartup() slot of the QLineEdit instance to be invoked right after the event system
  84. // is ready to do so. Therefore, it is better to use singleShot() than directly call OnStartup().
  85. QTimer::singleShot(0, this, &CNewLevelDialog::OnStartup);
  86. ReloadLevelFolder();
  87. }
  88. CNewLevelDialog::~CNewLevelDialog()
  89. {
  90. }
  91. void CNewLevelDialog::InitTemplateListWidget() const
  92. {
  93. ui->listTemplates->clear();
  94. QStringList templatePaths;
  95. if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry)
  96. {
  97. auto AppendCustomTemplatePath = [&templatePaths](const AZ::SettingsRegistryInterface::VisitArgs& visitArgs)
  98. {
  99. AZ::IO::FixedMaxPath customTemplatePath;
  100. if (visitArgs.m_registry.Get(customTemplatePath.Native(), visitArgs.m_jsonKeyPath))
  101. {
  102. if (AZ::IO::FileIOBase::GetInstance()->ResolvePath(customTemplatePath, customTemplatePath))
  103. {
  104. templatePaths.push_back(QString::fromUtf8(customTemplatePath.c_str(), int(customTemplatePath.Native().size())));
  105. }
  106. }
  107. return AZ::SettingsRegistryInterface::VisitResponse::Skip;
  108. };
  109. AZ::SettingsRegistryVisitorUtils::VisitObject(*settingsRegistry, AppendCustomTemplatePath, RegistryKey_CustomTemplatePaths);
  110. }
  111. // Get all prefab files.
  112. const QStringList fileFilter = {"*.prefab"};
  113. QStringList allTemplateFiles;
  114. for(const QString& path: templatePaths)
  115. {
  116. QDir projectTemplateDirectory(path);
  117. projectTemplateDirectory.setNameFilters(fileFilter);
  118. const QStringList projectTemplateFiles = projectTemplateDirectory.entryList(QDir::Files);
  119. for (const QString& fileName: projectTemplateFiles)
  120. {
  121. allTemplateFiles.push_back(projectTemplateDirectory.filePath(fileName));
  122. }
  123. }
  124. // Create the item with its icons to the QListWidget.
  125. const QIcon defaultIcon(":/NewLevel/res/Prefab_80.svg");
  126. for (const QString& fileName: allTemplateFiles)
  127. {
  128. QFileInfo info(fileName);
  129. auto* item = new QListWidgetItem(info.baseName());
  130. const QString iconPath = ChangeFileExtension(fileName, "png");
  131. const QIcon itemIcon = QFile::exists(iconPath) ? QIcon(iconPath) : defaultIcon;
  132. item->setIcon(itemIcon);
  133. item->setData(Qt::UserRole, fileName);
  134. ui->listTemplates->addItem(item);
  135. }
  136. const QSize iconSize(128, 128);
  137. ui->listTemplates->setViewMode(QListWidget::IconMode);
  138. ui->listTemplates->setIconSize(iconSize);
  139. ui->listTemplates->setDragDropMode(QAbstractItemView::NoDragDrop);
  140. }
  141. QString CNewLevelDialog::GetTemplateName() const
  142. {
  143. const QString name = ui->listTemplates->currentItem()->data(Qt::UserRole).toString();
  144. return name;
  145. }
  146. void CNewLevelDialog::OnStartup()
  147. {
  148. UpdateData(false);
  149. }
  150. void CNewLevelDialog::UpdateData(bool fromUi)
  151. {
  152. if (fromUi)
  153. {
  154. m_level = ui->LEVEL->text();
  155. m_levelFolders = ui->LEVEL_FOLDERS->text();
  156. }
  157. else
  158. {
  159. ui->LEVEL->setText(m_level);
  160. ui->LEVEL_FOLDERS->lineEdit()->setText(m_levelFolders);
  161. }
  162. }
  163. // CNewLevelDialog message handlers
  164. void CNewLevelDialog::OnInitDialog()
  165. {
  166. ReloadLevelFolder();
  167. // Disable OK until some text is entered
  168. if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok))
  169. {
  170. button->setEnabled(false);
  171. }
  172. // Save data.
  173. UpdateData(false);
  174. }
  175. //////////////////////////////////////////////////////////////////////////
  176. void CNewLevelDialog::ReloadLevelFolder()
  177. {
  178. ui->LEVEL_FOLDERS->lineEdit()->clear();
  179. ui->LEVEL_FOLDERS->setText(QString(kNewLevelDialog_LevelsFolder) + '/');
  180. }
  181. QString CNewLevelDialog::GetLevelsFolder() const
  182. {
  183. QDir projectDir = QDir(Path::GetEditingGameDataFolder().c_str());
  184. QDir projectLevelsDir = QDir(QStringLiteral("%1/%2").arg(projectDir.absolutePath()).arg(kNewLevelDialog_LevelsFolder));
  185. return projectLevelsDir.absolutePath();
  186. }
  187. //////////////////////////////////////////////////////////////////////////
  188. QString CNewLevelDialog::GetLevel() const
  189. {
  190. QString output = m_level;
  191. QDir projectLevelsDir = QDir(GetLevelsFolder());
  192. if (!m_levelFolders.isEmpty())
  193. {
  194. output = m_levelFolders + "/" + m_level;
  195. }
  196. QString relativePath = projectLevelsDir.relativeFilePath(output);
  197. return relativePath;
  198. }
  199. bool CNewLevelDialog::ValidateLevel()
  200. {
  201. // Check that the selected folder is in or below the project/LEVELS folder.
  202. QDir projectLevelsDir = QDir(GetLevelsFolder());
  203. QString selectedFolder = ui->LEVEL_FOLDERS->text();
  204. QString absolutePath = QDir::cleanPath(projectLevelsDir.absoluteFilePath(selectedFolder));
  205. QString relativePath = projectLevelsDir.relativeFilePath(absolutePath);
  206. // Prevent saving to a different drive.
  207. if (projectLevelsDir.absolutePath()[0] != absolutePath[0])
  208. {
  209. return false;
  210. }
  211. if (relativePath.startsWith(".."))
  212. {
  213. return false;
  214. }
  215. return true;
  216. }
  217. void CNewLevelDialog::OnLevelNameChange()
  218. {
  219. UpdateData(true);
  220. // QRegExpValidator means the string will always be valid as long as it's not empty:
  221. const bool valid = !m_level.isEmpty() && ValidateLevel();
  222. // Use the validity to dynamically change the Ok button's enabled state
  223. if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok))
  224. {
  225. button->setEnabled(valid);
  226. }
  227. }
  228. void CNewLevelDialog::OnClearButtonClicked()
  229. {
  230. ui->LEVEL_FOLDERS->lineEdit()->setText(GetLevelsFolder());
  231. UpdateData(true);
  232. }
  233. void CNewLevelDialog::PopupAssetPicker()
  234. {
  235. QString newPath = QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose Destination Folder"), GetLevelsFolder());
  236. if (!newPath.isEmpty())
  237. {
  238. ui->LEVEL_FOLDERS->setText(newPath);
  239. OnLevelNameChange();
  240. }
  241. }
  242. //////////////////////////////////////////////////////////////////////////
  243. void CNewLevelDialog::showEvent(QShowEvent* event)
  244. {
  245. if (!m_initialized)
  246. {
  247. OnInitDialog();
  248. m_initialized = true;
  249. }
  250. QDialog::showEvent(event);
  251. }
  252. #include <moc_NewLevelDialog.cpp>