NewLevelDialog.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 "NewLevelDialog.h"
  10. // Qt
  11. #include <QtWidgets/QPushButton>
  12. #include <QFileDialog>
  13. #include <QMessageBox>
  14. #include <QTimer>
  15. #include <QToolButton>
  16. AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
  17. #include <ui_NewLevelDialog.h>
  18. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
  19. // Folder in which levels are stored
  20. static const char kNewLevelDialog_LevelsFolder[] = "Levels";
  21. class LevelFolderValidator : public QValidator
  22. {
  23. public:
  24. LevelFolderValidator(QObject* parent)
  25. : QValidator(parent)
  26. {
  27. m_parentDialog = qobject_cast<CNewLevelDialog*>(parent);
  28. }
  29. QValidator::State validate([[maybe_unused]] QString& input, [[maybe_unused]] int& pos) const override
  30. {
  31. if (m_parentDialog->ValidateLevel())
  32. {
  33. return QValidator::Acceptable;
  34. }
  35. return QValidator::Intermediate;
  36. }
  37. private:
  38. CNewLevelDialog* m_parentDialog;
  39. };
  40. // CNewLevelDialog dialog
  41. CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=nullptr*/)
  42. : QDialog(pParent)
  43. , m_bUpdate(false)
  44. , ui(new Ui::CNewLevelDialog)
  45. , m_initialized(false)
  46. {
  47. ui->setupUi(this);
  48. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  49. setWindowTitle(tr("New Level"));
  50. setMaximumSize(QSize(320, 180));
  51. adjustSize();
  52. m_bIsResize = false;
  53. ui->TITLE->setText(tr("Assign a name and location to the new level."));
  54. ui->STATIC1->setText(tr("Location:"));
  55. ui->STATIC2->setText(tr("Name:"));
  56. // Level name only supports ASCII characters
  57. QRegExp rx("[_a-zA-Z0-9-]+");
  58. QValidator* validator = new QRegExpValidator(rx, this);
  59. ui->LEVEL->setValidator(validator);
  60. validator = new LevelFolderValidator(this);
  61. ui->LEVEL_FOLDERS->lineEdit()->setValidator(validator);
  62. ui->LEVEL_FOLDERS->setErrorToolTip(
  63. QString("The location must be a folder underneath the current project's %1 folder. (%2)")
  64. .arg(kNewLevelDialog_LevelsFolder)
  65. .arg(GetLevelsFolder()));
  66. ui->LEVEL_FOLDERS->setClearButtonEnabled(true);
  67. QToolButton* clearButton = AzQtComponents::LineEdit::getClearButton(ui->LEVEL_FOLDERS->lineEdit());
  68. assert(clearButton);
  69. connect(clearButton, &QToolButton::clicked, this, &CNewLevelDialog::OnClearButtonClicked);
  70. connect(ui->LEVEL_FOLDERS->lineEdit(), &QLineEdit::textEdited, this, &CNewLevelDialog::OnLevelNameChange);
  71. connect(ui->LEVEL_FOLDERS, &AzQtComponents::BrowseEdit::attachedButtonTriggered, this, &CNewLevelDialog::PopupAssetPicker);
  72. connect(ui->LEVEL, &QLineEdit::textChanged, this, &CNewLevelDialog::OnLevelNameChange);
  73. m_levelFolders = GetLevelsFolder();
  74. m_level = "";
  75. // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which
  76. // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last.
  77. // in OnStartup()
  78. // Secondly, using singleShot() allows OnStartup() slot of the QLineEdit instance to be invoked right after the event system
  79. // is ready to do so. Therefore, it is better to use singleShot() than directly call OnStartup().
  80. QTimer::singleShot(0, this, &CNewLevelDialog::OnStartup);
  81. ReloadLevelFolder();
  82. }
  83. CNewLevelDialog::~CNewLevelDialog()
  84. {
  85. }
  86. void CNewLevelDialog::OnStartup()
  87. {
  88. UpdateData(false);
  89. }
  90. void CNewLevelDialog::UpdateData(bool fromUi)
  91. {
  92. if (fromUi)
  93. {
  94. m_level = ui->LEVEL->text();
  95. m_levelFolders = ui->LEVEL_FOLDERS->text();
  96. }
  97. else
  98. {
  99. ui->LEVEL->setText(m_level);
  100. ui->LEVEL_FOLDERS->lineEdit()->setText(m_levelFolders);
  101. }
  102. }
  103. // CNewLevelDialog message handlers
  104. void CNewLevelDialog::OnInitDialog()
  105. {
  106. ReloadLevelFolder();
  107. // Disable OK until some text is entered
  108. if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok))
  109. {
  110. button->setEnabled(false);
  111. }
  112. // Save data.
  113. UpdateData(false);
  114. }
  115. //////////////////////////////////////////////////////////////////////////
  116. void CNewLevelDialog::ReloadLevelFolder()
  117. {
  118. m_itemFolders.clear();
  119. ui->LEVEL_FOLDERS->lineEdit()->clear();
  120. ui->LEVEL_FOLDERS->setText(QString(kNewLevelDialog_LevelsFolder) + '/');
  121. }
  122. QString CNewLevelDialog::GetLevelsFolder() const
  123. {
  124. QDir projectDir = QDir(Path::GetEditingGameDataFolder().c_str());
  125. QDir projectLevelsDir = QDir(QStringLiteral("%1/%2").arg(projectDir.absolutePath()).arg(kNewLevelDialog_LevelsFolder));
  126. return projectLevelsDir.absolutePath();
  127. }
  128. //////////////////////////////////////////////////////////////////////////
  129. QString CNewLevelDialog::GetLevel() const
  130. {
  131. QString output = m_level;
  132. QDir projectLevelsDir = QDir(GetLevelsFolder());
  133. if (!m_levelFolders.isEmpty())
  134. {
  135. output = m_levelFolders + "/" + m_level;
  136. }
  137. QString relativePath = projectLevelsDir.relativeFilePath(output);
  138. return relativePath;
  139. }
  140. bool CNewLevelDialog::ValidateLevel()
  141. {
  142. // Check that the selected folder is in or below the project/LEVELS folder.
  143. QDir projectLevelsDir = QDir(GetLevelsFolder());
  144. QString selectedFolder = ui->LEVEL_FOLDERS->text();
  145. QString absolutePath = QDir::cleanPath(projectLevelsDir.absoluteFilePath(selectedFolder));
  146. QString relativePath = projectLevelsDir.relativeFilePath(absolutePath);
  147. // Prevent saving to a different drive.
  148. if (projectLevelsDir.absolutePath()[0] != absolutePath[0])
  149. {
  150. return false;
  151. }
  152. if (relativePath.startsWith(".."))
  153. {
  154. return false;
  155. }
  156. return true;
  157. }
  158. void CNewLevelDialog::OnLevelNameChange()
  159. {
  160. UpdateData(true);
  161. // QRegExpValidator means the string will always be valid as long as it's not empty:
  162. const bool valid = !m_level.isEmpty() && ValidateLevel();
  163. // Use the validity to dynamically change the Ok button's enabled state
  164. if (QPushButton* button = ui->buttonBox->button(QDialogButtonBox::Ok))
  165. {
  166. button->setEnabled(valid);
  167. }
  168. }
  169. void CNewLevelDialog::OnClearButtonClicked()
  170. {
  171. ui->LEVEL_FOLDERS->lineEdit()->setText(GetLevelsFolder());
  172. UpdateData(true);
  173. }
  174. void CNewLevelDialog::PopupAssetPicker()
  175. {
  176. QString newPath = QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose Destination Folder"), GetLevelsFolder());
  177. if (!newPath.isEmpty())
  178. {
  179. ui->LEVEL_FOLDERS->setText(newPath);
  180. OnLevelNameChange();
  181. }
  182. }
  183. //////////////////////////////////////////////////////////////////////////
  184. void CNewLevelDialog::IsResize(bool bIsResize)
  185. {
  186. m_bIsResize = bIsResize;
  187. }
  188. //////////////////////////////////////////////////////////////////////////
  189. void CNewLevelDialog::showEvent(QShowEvent* event)
  190. {
  191. if (!m_initialized)
  192. {
  193. OnInitDialog();
  194. m_initialized = true;
  195. }
  196. QDialog::showEvent(event);
  197. }
  198. #include <moc_NewLevelDialog.cpp>