From d590a91fe791a85ab81baf5dd1f4b262ba008c73 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Fri, 27 Aug 2021 11:24:05 -0500 Subject: [PATCH 1/2] Implemented helper method of QFileDialog::getSaveFileName to prevent user from saving files with invalid names. Signed-off-by: Chris Galvan --- Code/Editor/TrackView/TrackViewDialog.cpp | 3 +- Code/Editor/TrackView/TrackViewNodes.cpp | 3 +- .../Components/Widgets/FileDialog.cpp | 53 +++++++++++++++++++ .../Components/Widgets/FileDialog.h | 29 ++++++++++ .../AzQtComponents/azqtcomponents_files.cmake | 2 + .../AssetEditor/AssetEditorWidget.cpp | 13 ++--- .../Source/ImageProcessingSystemComponent.cpp | 4 +- .../Code/Source/Util/Util.cpp | 6 +-- .../CreateMaterialDialog.cpp | 6 +-- .../EditorMaterialComponentExporter.cpp | 4 +- .../EMStudioSDK/Source/FileManager.cpp | 39 +++++--------- Gems/LyShine/Code/Editor/EditorWindow.cpp | 4 +- .../Code/Editor/View/Windows/MainWindow.cpp | 4 +- .../Code/Source/EditorWhiteBoxComponent.cpp | 6 +-- 14 files changed, 127 insertions(+), 49 deletions(-) create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h diff --git a/Code/Editor/TrackView/TrackViewDialog.cpp b/Code/Editor/TrackView/TrackViewDialog.cpp index cfaa82fdb4..d70cbad29e 100644 --- a/Code/Editor/TrackView/TrackViewDialog.cpp +++ b/Code/Editor/TrackView/TrackViewDialog.cpp @@ -34,6 +34,7 @@ // AzQtComponents #include +#include // CryCommon #include @@ -2324,7 +2325,7 @@ void CTrackViewDialog::SaveCurrentSequenceToFBX() } } - QString filename = QFileDialog::getSaveFileName(this, tr("Export Selected Nodes To FBX File"), selectedSequenceFBXStr, szFilters); + QString filename = AzQtComponents::FileDialog::GetSaveFileName(this, tr("Export Selected Nodes To FBX File"), selectedSequenceFBXStr, szFilters); if (!filename.isEmpty()) { pExportManager->SetBakedKeysSequenceExport(true); diff --git a/Code/Editor/TrackView/TrackViewNodes.cpp b/Code/Editor/TrackView/TrackViewNodes.cpp index e3b994c488..2a2d584e46 100644 --- a/Code/Editor/TrackView/TrackViewNodes.cpp +++ b/Code/Editor/TrackView/TrackViewNodes.cpp @@ -30,6 +30,7 @@ // AzQtComponents #include +#include // CryCommon #include @@ -1044,7 +1045,7 @@ void CTrackViewNodesCtrl::OnNMRclick(QPoint point) file = QString::fromUtf8(selectedNodes.GetNode(0)->GetName().c_str()) + QString(".fbx"); } - QString path = QFileDialog::getSaveFileName(this, tr("Export Selected Nodes To FBX File"), QString(), tr("FBX Files (*.fbx)")); + QString path = AzQtComponents::FileDialog::GetSaveFileName(this, tr("Export Selected Nodes To FBX File"), QString(), tr("FBX Files (*.fbx)")); if (!path.isEmpty()) { diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp new file mode 100644 index 0000000000..cdd6a77094 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp @@ -0,0 +1,53 @@ +/* + * 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 + +#include +#include + +namespace AzQtComponents +{ + QString FileDialog::GetSaveFileName(QWidget* parent, const QString& caption, const QString& dir, + const QString& filter, QString* selectedFilter, QFileDialog::Options options) + { + bool shouldPromptAgain = false; + QString filePath; + + do + { + // Trigger Qt's save filename dialog + // If filePath isn't empty, it means we are prompting again because the filename was invalid, + // so pass it instead of the directory so the filename is pre-filled in for the user + filePath = QFileDialog::getSaveFileName(parent, caption, (filePath.isEmpty()) ? dir : filePath, filter, selectedFilter, options); + + if (!filePath.isEmpty()) + { + QFileInfo fileInfo(filePath); + QString fileName = fileInfo.fileName(); + + // Check if the filename has any invalid characters + QRegExp validFileNameRegex("^[a-zA-Z0-9_\\-./]*$"); + shouldPromptAgain = !validFileNameRegex.exactMatch(fileName); + + // If the filename had invalid characters, then show a warning message and then we will re-prompt the save filename dialog + if (shouldPromptAgain) + { + QMessageBox::warning(parent, QObject::tr("Invalid filename"), QObject::tr("The filename contains invalid characters\n\n%1").arg(fileName)); + } + } + else + { + // If the filePath is empty, then the user cancelled the dialog so we don't need to prompt again + shouldPromptAgain = false; + } + } while (shouldPromptAgain); + + return filePath; + } +} // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h new file mode 100644 index 0000000000..6b63404949 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h @@ -0,0 +1,29 @@ +/* + * 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 + +#include + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace AzQtComponents +{ + class AZ_QT_COMPONENTS_API FileDialog + { + public: + //! Helper method that extends QFileDialog::getSaveFileName to prevent the user from + //! saving a filename with invalid characters (e.g. AP doesn't allow @ characters because they are used for aliases) + static QString GetSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(), + const QString& dir = QString(), const QString& filter = QString(), + QString* selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); + }; + +} // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_files.cmake b/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_files.cmake index af1a3a9f56..c214b81405 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_files.cmake +++ b/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_files.cmake @@ -146,6 +146,8 @@ set(FILES Components/Widgets/Eyedropper.h Components/Widgets/Eyedropper.cpp Components/Widgets/EyedropperConfig.ini + Components/Widgets/FileDialog.cpp + Components/Widgets/FileDialog.h Components/Widgets/FilteredSearchWidget.qss Components/Widgets/FilteredSearchWidgetConfig.ini Components/Widgets/GradientSlider.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp index 7198ed1be8..e155189a99 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp @@ -37,6 +37,10 @@ AZ_POP_DISABLE_WARNING #include #include +#include + +#include + #include #include @@ -46,9 +50,6 @@ AZ_POP_DISABLE_WARNING #include #include #include -AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QFileInfo::d_ptr': class 'QSharedDataPointer' needs to have dll-interface to be used by clients of class 'QFileInfo' -#include -AZ_POP_DISABLE_WARNING #include namespace AzToolsFramework @@ -414,7 +415,7 @@ namespace AzToolsFramework filter.append(")"); } - const QString saveAs = QFileDialog::getSaveFileName(nullptr, tr("Save As..."), m_userSettings->m_lastSavePath.c_str(), filter); + const QString saveAs = AzQtComponents::FileDialog::GetSaveFileName(AzToolsFramework::GetActiveWindow(), tr("Save As..."), m_userSettings->m_lastSavePath.c_str(), filter); return SaveImpl(asset, saveAs); } @@ -902,7 +903,7 @@ namespace AzToolsFramework statusString = QString("%1"); } - statusString = statusString.arg(m_currentAsset).arg(m_queuedAssetStatus); + statusString = statusString.arg(m_currentAsset); if (!m_queuedAssetStatus.isEmpty()) { @@ -920,7 +921,7 @@ namespace AzToolsFramework void AssetEditorWidget::SetupHeader() { - QString nameString = QString("%1").arg(m_currentAsset).arg(m_queuedAssetStatus); + QString nameString = QString("%1").arg(m_currentAsset); m_header->setName(nameString); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp index 0436ef0215..665d08b3ae 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include @@ -202,7 +204,7 @@ namespace ImageProcessingAtom AZ::Data::AssetId assetId = product->GetAssetId(); menu->addAction("Save as DDS...", [assetId, this]() { - QString filePath = QFileDialog::getSaveFileName(nullptr, QString("Save to file"), m_lastSavedPath, QString("DDS file (*.dds)")); + QString filePath = AzQtComponents::FileDialog::GetSaveFileName(nullptr, QString("Save to file"), m_lastSavedPath, QString("DDS file (*.dds)")); if (filePath.isEmpty()) { return; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp index ba913e317b..be112345a9 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp @@ -11,13 +11,13 @@ #include #include #include +#include #include #include #include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include -#include #include #include AZ_POP_DISABLE_WARNING @@ -29,7 +29,7 @@ namespace AtomToolsFramework const QFileInfo initialFileInfo(initialPath); const QString initialExt(initialFileInfo.completeSuffix()); - const QFileInfo selectedFileInfo(QFileDialog::getSaveFileName( + const QFileInfo selectedFileInfo(AzQtComponents::FileDialog::GetSaveFileName( QApplication::activeWindow(), "Save File", initialFileInfo.absolutePath() + @@ -104,7 +104,7 @@ namespace AtomToolsFramework const QFileInfo initialFileInfo(initialPath); const QString initialExt(initialFileInfo.completeSuffix()); - const QFileInfo duplicateFileInfo(QFileDialog::getSaveFileName( + const QFileInfo duplicateFileInfo(AzQtComponents::FileDialog::GetSaveFileName( QApplication::activeWindow(), "Duplicate File", GetUniqueFileInfo(initialPath).absoluteFilePath(), diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp index 608122b77a..f27a08b08d 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -19,8 +21,6 @@ #include -#include - namespace MaterialEditor { CreateMaterialDialog::CreateMaterialDialog(QWidget* parent) @@ -95,7 +95,7 @@ namespace MaterialEditor //When the file selection button is pressed, open a file dialog to select where the material will be saved QObject::connect(m_ui->m_materialFilePicker, &AzQtComponents::BrowseEdit::attachedButtonTriggered, m_ui->m_materialFilePicker, [this]() { - QFileInfo fileInfo = QFileDialog::getSaveFileName(this, + QFileInfo fileInfo = AzQtComponents::FileDialog::GetSaveFileName(this, QString("Select Material Filename"), m_materialFileInfo.absoluteFilePath(), QString("Material (*.material)")); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp index 32a36392a4..f55da28fa6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExporter.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -22,7 +23,6 @@ AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnin #include #include #include -#include #include #include #include @@ -145,7 +145,7 @@ namespace AZ // Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&dialog, &exportItem, materialFileWidget, overwriteCheckBox]() { - QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog, + QFileInfo fileInfo = AzQtComponents::FileDialog::GetSaveFileName(&dialog, QString("Select Material Filename"), exportItem.GetExportPath().c_str(), QString("Material (*.material)"), diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp index 5a89729e49..129d005f80 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp @@ -9,7 +9,6 @@ #include "FileManager.h" #include #include -#include #include #include #include @@ -36,6 +35,8 @@ #include #include +#include + #include #include #include @@ -412,14 +413,12 @@ namespace EMStudio { GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - const AZStd::string filename = QFileDialog::getSaveFileName(parent, // parent + const AZStd::string filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption GetLastUsedFolder(m_lastActorFolder), // directory "EMotion FX Actor Files (*.actor)", - &selectedFilter, - options).toUtf8().data(); + &selectedFilter).toUtf8().data(); GetManager()->SetAvoidRendering(false); @@ -471,14 +470,12 @@ namespace EMStudio { GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - AZStd::string filename = QFileDialog::getSaveFileName(parent, // parent + AZStd::string filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption GetLastUsedFolder(m_lastWorkspaceFolder), // directory "EMotionFX Editor Workspace Files (*.emfxworkspace)", - &selectedFilter, - options).toUtf8().data(); + &selectedFilter).toUtf8().data(); GetManager()->SetAvoidRendering(false); @@ -553,14 +550,12 @@ namespace EMStudio { GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - AZStd::string filename = QFileDialog::getSaveFileName(parent, // parent + AZStd::string filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption GetLastUsedFolder(m_lastMotionSetFolder), // directory "EMotion FX Motion Set Files (*.motionset)", - &selectedFilter, - options).toUtf8().data(); + &selectedFilter).toUtf8().data(); GetManager()->SetAvoidRendering(false); @@ -632,14 +627,12 @@ namespace EMStudio { GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - AZStd::string filename = QFileDialog::getSaveFileName(parent, // parent + AZStd::string filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption GetLastUsedFolder(m_lastAnimGraphFolder), // directory "EMotion FX Anim Graph Files (*.animgraph);;All Files (*)", - &selectedFilter, - options).toUtf8().data(); + &selectedFilter).toUtf8().data(); GetManager()->SetAvoidRendering(false); @@ -675,14 +668,12 @@ namespace EMStudio { GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - const AZStd::string filename = QFileDialog::getSaveFileName(parent, // parent + const AZStd::string filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption GetLastUsedFolder(m_lastNodeMapFolder), // directory "Node Map Files (*.nodeMap);;All Files (*)", - &selectedFilter, - options).toUtf8().data(); + &selectedFilter).toUtf8().data(); GetManager()->SetAvoidRendering(false); @@ -737,14 +728,12 @@ namespace EMStudio GetManager()->SetAvoidRendering(true); - QFileDialog::Options options; QString selectedFilter; - QString filename = QFileDialog::getSaveFileName(parent, // parent + QString filename = AzQtComponents::FileDialog::GetSaveFileName(parent, // parent "Save", // caption dir.c_str(), // directory "EMotion FX Blend Config Files (*.cfg);;All Files (*)", - &selectedFilter, - options); + &selectedFilter); GetManager()->SetAvoidRendering(false); diff --git a/Gems/LyShine/Code/Editor/EditorWindow.cpp b/Gems/LyShine/Code/Editor/EditorWindow.cpp index 5ae1eb44b9..663570bbf0 100644 --- a/Gems/LyShine/Code/Editor/EditorWindow.cpp +++ b/Gems/LyShine/Code/Editor/EditorWindow.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,6 @@ #include #include #include -#include #define UICANVASEDITOR_SETTINGS_EDIT_MODE_STATE_KEY (QString("Edit Mode State") + " " + FileHelpers::GetAbsoluteGameDir()) #define UICANVASEDITOR_SETTINGS_EDIT_MODE_GEOM_KEY (QString("Edit Mode Geometry") + " " + FileHelpers::GetAbsoluteGameDir()) @@ -706,7 +706,7 @@ bool EditorWindow::SaveCanvasToXml(UiCanvasMetadata& canvasMetadata, bool forceA dir.append(canvasMetadata.m_canvasDisplayName.c_str()); } - QString filename = QFileDialog::getSaveFileName(nullptr, + QString filename = AzQtComponents::FileDialog::GetSaveFileName(nullptr, QString(), dir, "*." UICANVASEDITOR_CANVAS_EXTENSION, diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp index f4241e3545..fbe03ffc0a 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include #include @@ -91,6 +90,7 @@ #include #include +#include #include #include @@ -1868,7 +1868,7 @@ namespace ScriptCanvasEditor while (!isValidFileName) { - selectedFile = QFileDialog::getSaveFileName(this, tr("Save As..."), suggestedFilename.data(), filter); + selectedFile = AzQtComponents::FileDialog::GetSaveFileName(this, tr("Save As..."), suggestedFilename.data(), filter); // If the selected file is empty that means we just cancelled. // So we want to break out. diff --git a/Gems/WhiteBox/Code/Source/EditorWhiteBoxComponent.cpp b/Gems/WhiteBox/Code/Source/EditorWhiteBoxComponent.cpp index 61796bde58..f18b69393c 100644 --- a/Gems/WhiteBox/Code/Source/EditorWhiteBoxComponent.cpp +++ b/Gems/WhiteBox/Code/Source/EditorWhiteBoxComponent.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -513,7 +513,7 @@ namespace WhiteBox WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension); const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str(); - const QString absoluteSaveFilePath = QFileDialog::getSaveFileName( + const QString absoluteSaveFilePath = AzQtComponents::FileDialog::GetSaveFileName( nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter); const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8(); @@ -577,7 +577,7 @@ namespace WhiteBox { const QString fileFilter = AZStd::string::format("*.%s", Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension).c_str(); - const QString absolutePath = QFileDialog::getSaveFileName( + const QString absolutePath = AzQtComponents::FileDialog::GetSaveFileName( nullptr, "Save As Asset...", QString(initialAbsolutePath.c_str()), fileFilter); return AZStd::string(absolutePath.toUtf8()); From 043b21816582ed76243113ff800b992a1f78c80f Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Fri, 27 Aug 2021 15:44:31 -0500 Subject: [PATCH 2/2] Updated invalid filename warning with more explicit message from PR feedback. Signed-off-by: Chris Galvan --- .../AzQtComponents/Components/Widgets/FileDialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp index cdd6a77094..d2d773ff93 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp @@ -38,7 +38,8 @@ namespace AzQtComponents // If the filename had invalid characters, then show a warning message and then we will re-prompt the save filename dialog if (shouldPromptAgain) { - QMessageBox::warning(parent, QObject::tr("Invalid filename"), QObject::tr("The filename contains invalid characters\n\n%1").arg(fileName)); + QMessageBox::warning(parent, QObject::tr("Invalid filename"), + QObject::tr("O3DE assets are restricted to alphanumeric characters, hyphens (-), underscores (_), and dots (.)\n\n%1").arg(fileName)); } } else