diff --git a/Code/Tools/AssetBundler/CMakeLists.txt b/Code/Tools/AssetBundler/CMakeLists.txt index 41b95fd2ae..c99ffaef6b 100644 --- a/Code/Tools/AssetBundler/CMakeLists.txt +++ b/Code/Tools/AssetBundler/CMakeLists.txt @@ -13,9 +13,13 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) return() endif() +include(${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + +# AssetBundlerBatch - CLI Application ly_add_target( - NAME AssetBundler.Static STATIC + NAME AssetBundlerBatch.Static STATIC NAMESPACE AZ + AUTOMOC FILES_CMAKE assetbundlerbatch_files.cmake INCLUDE_DIRECTORIES @@ -23,6 +27,9 @@ ly_add_target( . BUILD_DEPENDENCIES PUBLIC + 3rdParty::Qt::Core + 3rdParty::Qt::Gui + 3rdParty::Qt::Widgets AZ::AzToolsFramework ${additional_dependencies} ) @@ -32,12 +39,41 @@ ly_add_target( NAMESPACE AZ FILES_CMAKE assetbundlerbatch_exe_files.cmake + COMPILE_DEFINITIONS + PRIVATE + AB_BATCH_MODE INCLUDE_DIRECTORIES PRIVATE . BUILD_DEPENDENCIES PRIVATE - AZ::AssetBundler.Static + AZ::AssetBundlerBatch.Static +) + +# AssetBundler - Qt GUI Application +ly_add_target( + NAME AssetBundler ${PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE} + NAMESPACE AZ + AUTOMOC + AUTOUIC + AUTORCC + FILES_CMAKE + assetbundlergui_files.cmake + assetbundler_exe_files.cmake + Platform/${PAL_PLATFORM_NAME}/assetbundlergui_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + INCLUDE_DIRECTORIES + PUBLIC + Platform/${PAL_PLATFORM_NAME} + PRIVATE + . + BUILD_DEPENDENCIES + PUBLIC + 3rdParty::Qt::Core + 3rdParty::Qt::Gui + 3rdParty::Qt::Widgets + AZ::AzToolsFramework + AZ::AssetBundlerBatch.Static + ${additional_dependencies} ) if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) @@ -53,7 +89,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) BUILD_DEPENDENCIES PRIVATE AZ::AzTest - AZ::AssetBundler.Static + AZ::AssetBundlerBatch.Static AZ::AzFrameworkTestShared ) diff --git a/Code/Tools/AssetBundler/Platform/Linux/PAL_linux.cmake b/Code/Tools/AssetBundler/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..d0d27a99ac --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,13 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set (PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE APPLICATION) + diff --git a/Code/Tools/AssetBundler/Platform/Linux/assetbundlergui_linux_files.cmake b/Code/Tools/AssetBundler/Platform/Linux/assetbundlergui_linux_files.cmake new file mode 100644 index 0000000000..23bbefccdc --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Linux/assetbundlergui_linux_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + source/utils/GUIApplicationManager_Linux.cpp +) \ No newline at end of file diff --git a/Code/Tools/AssetBundler/Platform/Linux/source/utils/GUIApplicationManager_Linux.cpp b/Code/Tools/AssetBundler/Platform/Linux/source/utils/GUIApplicationManager_Linux.cpp new file mode 100644 index 0000000000..7af09214cb --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Linux/source/utils/GUIApplicationManager_Linux.cpp @@ -0,0 +1,21 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace Platform +{ + AzQtComponents::WindowDecorationWrapper::Option GetWindowDecorationWrapperOption() + { + return AzQtComponents::WindowDecorationWrapper::OptionDisabled; + } +} \ No newline at end of file diff --git a/Code/Tools/AssetBundler/Platform/Mac/PAL_mac.cmake b/Code/Tools/AssetBundler/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..d0d27a99ac --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,13 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set (PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE APPLICATION) + diff --git a/Code/Tools/AssetBundler/Platform/Mac/assetbundlergui_mac_files.cmake b/Code/Tools/AssetBundler/Platform/Mac/assetbundlergui_mac_files.cmake new file mode 100644 index 0000000000..a8a116ac79 --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Mac/assetbundlergui_mac_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + source/utils/GUIApplicationManager_OSX.cpp +) \ No newline at end of file diff --git a/Code/Tools/AssetBundler/Platform/Mac/source/utils/GUIApplicationManager_OSX.cpp b/Code/Tools/AssetBundler/Platform/Mac/source/utils/GUIApplicationManager_OSX.cpp new file mode 100644 index 0000000000..7af09214cb --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Mac/source/utils/GUIApplicationManager_OSX.cpp @@ -0,0 +1,21 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace Platform +{ + AzQtComponents::WindowDecorationWrapper::Option GetWindowDecorationWrapperOption() + { + return AzQtComponents::WindowDecorationWrapper::OptionDisabled; + } +} \ No newline at end of file diff --git a/Code/Tools/AssetBundler/Platform/Windows/PAL_windows.cmake b/Code/Tools/AssetBundler/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..d0d27a99ac --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,13 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set (PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE APPLICATION) + diff --git a/Code/Tools/AssetBundler/Platform/Windows/assetbundlergui_windows_files.cmake b/Code/Tools/AssetBundler/Platform/Windows/assetbundlergui_windows_files.cmake new file mode 100644 index 0000000000..f9e1ffda8d --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Windows/assetbundlergui_windows_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + source/utils/GUIApplicationManager_Win.cpp +) \ No newline at end of file diff --git a/Code/Tools/AssetBundler/Platform/Windows/source/utils/GUIApplicationManager_Win.cpp b/Code/Tools/AssetBundler/Platform/Windows/source/utils/GUIApplicationManager_Win.cpp new file mode 100644 index 0000000000..99e254a774 --- /dev/null +++ b/Code/Tools/AssetBundler/Platform/Windows/source/utils/GUIApplicationManager_Win.cpp @@ -0,0 +1,21 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace Platform +{ + AzQtComponents::WindowDecorationWrapper::Option GetWindowDecorationWrapperOption() + { + return AzQtComponents::WindowDecorationWrapper::OptionAutoAttach; + } +} \ No newline at end of file diff --git a/Code/Tools/AssetBundler/assetbundler_exe_files.cmake b/Code/Tools/AssetBundler/assetbundler_exe_files.cmake new file mode 100644 index 0000000000..976405c4f7 --- /dev/null +++ b/Code/Tools/AssetBundler/assetbundler_exe_files.cmake @@ -0,0 +1,15 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + source/main.cpp + source/AssetBundler.rc +) diff --git a/Code/Tools/AssetBundler/assetbundlergui_files.cmake b/Code/Tools/AssetBundler/assetbundlergui_files.cmake new file mode 100644 index 0000000000..7ac5d34eb3 --- /dev/null +++ b/Code/Tools/AssetBundler/assetbundlergui_files.cmake @@ -0,0 +1,70 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + source/models/AssetBundlerAbstractFileTableModel.h + source/models/AssetBundlerAbstractFileTableModel.cpp + source/models/AssetBundlerFileTableFilterModel.h + source/models/AssetBundlerFileTableFilterModel.cpp + source/models/AssetListFileTableModel.h + source/models/AssetListFileTableModel.cpp + source/models/AssetListTableModel.cpp + source/models/AssetListTableModel.h + source/models/BundleFileListModel.h + source/models/BundleFileListModel.cpp + source/models/RulesFileTableModel.h + source/models/RulesFileTableModel.cpp + source/models/SeedListFileTableModel.h + source/models/SeedListFileTableModel.cpp + source/models/SeedListTableModel.h + source/models/SeedListTableModel.cpp + source/ui/style/AssetBundler.qrc + source/ui/style/AssetBundler.qss + source/ui/style/AssetBundlerConfig.ini + source/ui/style/AssetBundler-Icon-256x256@x2.ico + source/ui/AddSeedDialog.h + source/ui/AddSeedDialog.cpp + source/ui/AddSeedDialog.ui + source/ui/AssetBundlerTabWidget.h + source/ui/AssetBundlerTabWidget.cpp + source/ui/AssetListTabWidget.h + source/ui/AssetListTabWidget.cpp + source/ui/AssetListTabWidget.ui + source/ui/BundleListTabWidget.h + source/ui/BundleListTabWidget.cpp + source/ui/BundleListTabWidget.ui + source/ui/ComparisonDataWidget.h + source/ui/ComparisonDataWidget.cpp + source/ui/ComparisonDataWidget.ui + source/ui/EditSeedDialog.h + source/ui/EditSeedDialog.cpp + source/ui/EditSeedDialog.ui + source/ui/GenerateBundlesModal.h + source/ui/GenerateBundlesModal.cpp + source/ui/GenerateBundlesModal.ui + source/ui/MainWindow.h + source/ui/MainWindow.cpp + source/ui/MainWindow.ui + source/ui/NewFileDialog.h + source/ui/NewFileDialog.cpp + source/ui/NewFileDialog.ui + source/ui/PlatformSelectionWidget.h + source/ui/PlatformSelectionWidget.cpp + source/ui/PlatformSelectionWidget.ui + source/ui/RulesTabWidget.h + source/ui/RulesTabWidget.cpp + source/ui/RulesTabWidget.ui + source/ui/SeedTabWidget.h + source/ui/SeedTabWidget.cpp + source/ui/SeedTabWidget.ui + source/utils/GUIApplicationManager.h + source/utils/GUIApplicationManager.cpp +) diff --git a/Code/Tools/AssetBundler/source/AssetBundler.rc b/Code/Tools/AssetBundler/source/AssetBundler.rc new file mode 100644 index 0000000000..a3116a384f --- /dev/null +++ b/Code/Tools/AssetBundler/source/AssetBundler.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "ui\\style\\AssetBundler-Icon-256x256@x2.ico" diff --git a/Code/Tools/AssetBundler/source/main.cpp b/Code/Tools/AssetBundler/source/main.cpp index f1b782ff44..be152d6bf3 100644 --- a/Code/Tools/AssetBundler/source/main.cpp +++ b/Code/Tools/AssetBundler/source/main.cpp @@ -10,17 +10,48 @@ * */ +#if defined(AB_BATCH_MODE) #include +#else +#include +#include +#endif + +#if defined(AZ_TESTS_ENABLED) +#include +DECLARE_AZ_UNIT_TEST_MAIN(); +#endif int main(int argc, char* argv[]) { +#if defined(AZ_TESTS_ENABLED) + INVOKE_AZ_UNIT_TEST_MAIN(); +#endif + AZ::AllocatorInstance::Create(); int runSuccess = 0; + { - AssetBundler::ApplicationManager applicationManger(&argc, &argv); - applicationManger.Init(); - runSuccess = applicationManger.Run() ? 0 : 1; + //This nested scope is necessary as the applicationManager needs to have its destructor called BEFORE you destroy the allocators + +#if defined(AB_BATCH_MODE) + AssetBundler::ApplicationManager applicationManager(&argc, &argv); +#else + // Must be called before using any Qt, or the app won't be able to locate Qt libs + AzQtComponents::PrepareQtPaths(); + AssetBundler::GUIApplicationManager applicationManager(&argc, &argv); +#endif + if (!applicationManager.Init()) + { + AZ_Error("AssetBundler", false, "AssetBundler initialization failed"); + runSuccess = 1; + } + else + { + runSuccess = applicationManager.Run() ? 0 : 1; + } } + AZ::AllocatorInstance::Destroy(); return runSuccess; } diff --git a/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp new file mode 100644 index 0000000000..c2aac4e943 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.cpp @@ -0,0 +1,233 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +#include + +namespace AssetBundler +{ + const char* DateTimeFormat = "hh:mm:ss MMM dd, yyyy"; + + AssetBundlerAbstractFileTableModel::AssetBundlerAbstractFileTableModel(QObject* parent) + : QAbstractTableModel(parent) + { + } + + void AssetBundlerAbstractFileTableModel::Reload(const char* fileExtension, const QSet& watchedFolders, const QSet& watchedFiles, const AZStd::unordered_map& pathToProjectNameMap) + { + AZStd::vector keysToRemove = m_fileListKeys; + + // Reload all the files in the watched folders + QString filter = QString("*.%1").arg(fileExtension); + + QSetIterator itr(watchedFolders); + while (itr.hasNext()) + { + QDir filesDir(itr.next()); + filesDir.setNameFilters({ filter }); + for (const QString& fileNameAndExtension : filesDir.entryList(QDir::Files)) + { + AZStd::string absolutePath = filesDir.absoluteFilePath(fileNameAndExtension).toUtf8().data(); + AZStd::string projectName; + if (pathToProjectNameMap.contains(absolutePath)) + { + projectName = pathToProjectNameMap.at(absolutePath); + } + + // If a project name is already specified, then the associated file is a default file + LoadFile(absolutePath, projectName, !projectName.empty()); + keysToRemove.erase(AZStd::remove(keysToRemove.begin(), keysToRemove.end(), AssetBundler::GenerateKeyFromAbsolutePath(absolutePath)), keysToRemove.end()); + } + } + + // Reload all the watched files + for (const QString& filePath : watchedFiles) + { + AZStd::string absolutePath = filePath.toUtf8().data(); + if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath.c_str())) + { + AZStd::string projectName = pathToProjectNameMap.at(absolutePath); + + // If a project name is already specified, then the associated file is a default file + LoadFile(absolutePath, projectName, !projectName.empty()); + keysToRemove.erase(AZStd::remove(keysToRemove.begin(), keysToRemove.end(), AssetBundler::GenerateKeyFromAbsolutePath(absolutePath)), keysToRemove.end()); + } + } + + //Remove nonexistant files from the model + for (const AZStd::string& key : keysToRemove) + { + DeleteFileByKey(key); + } + } + + void AssetBundlerAbstractFileTableModel::ReloadFiles(const AZStd::vector& absoluteFilePathList, AZStd::unordered_map pathToProjectNameMap) + { + for (const AZStd::string& absoluteFilePath : absoluteFilePathList) + { + if (!AZ::IO::FileIOBase::GetInstance()->Exists(absoluteFilePath.c_str())) + { + // File is not present on-disk, make sure to remove it from the model + DeleteFileByKey(AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath)); + continue; + } + + // If a project name is already specified, then the associated file is a default file + AZStd::string projectName = pathToProjectNameMap[absoluteFilePath]; + LoadFile(absoluteFilePath, projectName, !projectName.empty()); + } + } + + bool AssetBundlerAbstractFileTableModel::Save(const QModelIndex& selectedIndex) + { + if (!selectedIndex.isValid() || m_keysWithUnsavedChanges.empty()) + { + // There is nothing to save + return true; + } + + AZStd::string key = GetFileKey(selectedIndex); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + // Save the file + if (!WriteToDisk(key)) + { + return false; + } + + // Update the display + m_keysWithUnsavedChanges.erase(key); + QModelIndex topLeftIndex = index(selectedIndex.row(), 0); + QModelIndex bottomRightIndex = index(selectedIndex.row(), columnCount() - 1); + emit dataChanged(topLeftIndex, bottomRightIndex, { Qt::DisplayRole, Qt::FontRole }); + + return true; + } + + bool AssetBundlerAbstractFileTableModel::SaveAll() + { + if (!HasUnsavedChanges()) + { + // No need to update all of the elements if we are not changing anything + return true; + } + + bool hasSaveErrors = false; + + // Save every file with unsaved changes + AZStd::unordered_set keysWithErrors; + for (const AZStd::string& key : m_keysWithUnsavedChanges) + { + if (!WriteToDisk(key)) + { + // Error has already been thrown + hasSaveErrors = true; + keysWithErrors.emplace(key); + } + } + + m_keysWithUnsavedChanges = keysWithErrors; + + // Update the display of all elements + QModelIndex firstIndex = index(0, 0); + QModelIndex lastIndex = index(rowCount() - 1, columnCount() - 1); + emit dataChanged(firstIndex, lastIndex, { Qt::DisplayRole, Qt::FontRole }); + + return !hasSaveErrors; + } + + bool AssetBundlerAbstractFileTableModel::HasUnsavedChanges() const + { + return !m_keysWithUnsavedChanges.empty(); + } + + int AssetBundlerAbstractFileTableModel::rowCount(const QModelIndex& /*parent*/) const + { + return static_cast(m_fileListKeys.size()); + } + + AZStd::string AssetBundlerAbstractFileTableModel::GetFileKey(const QModelIndex& index) const + { + int row = index.row(); + int col = index.column(); + if (row >= rowCount() || row < 0 || col >= columnCount() || col < 0) + { + AZ_Error("AssetBundler", false, "Selected index (%i, %i) is out of range.", row, col); + return {}; + } + + return m_fileListKeys.at(row); + } + + const AZStd::vector& AssetBundlerAbstractFileTableModel::GetAllFileKeys() const + { + return m_fileListKeys; + } + + int AssetBundlerAbstractFileTableModel::GetIndexRowByKey(const AZStd::string& key) const + { + auto it = AZStd::find(m_fileListKeys.begin(), m_fileListKeys.end(), key); + return it == m_fileListKeys.end() ? -1 : static_cast(AZStd::distance(m_fileListKeys.begin(), it)); + } + + void AssetBundlerAbstractFileTableModel::AddFileKey(const AZStd::string& key) + { + if (AZStd::find(m_fileListKeys.begin(), m_fileListKeys.end(), key) != m_fileListKeys.end()) + { + // Key already exists. This could happen when we update existing entires. + return; + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_fileListKeys.push_back(key); + endInsertRows(); + } + + bool AssetBundlerAbstractFileTableModel::RemoveFileKey(const QModelIndex& index) + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + int row = index.row(); + beginRemoveRows(QModelIndex(), row, row); + + m_fileListKeys.erase(m_fileListKeys.begin() + row); + m_keysWithUnsavedChanges.erase(key); + + endRemoveRows(); + + return true; + } + + bool AssetBundlerAbstractFileTableModel::DeleteFileByKey(const AZStd::string& key) + { + int indexRow = GetIndexRowByKey(key); + if (indexRow >= 0) + { + return DeleteFile(index(indexRow, 0)); + } + + return false; + } +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.h b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.h new file mode 100644 index 0000000000..cfe3e3f241 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetBundlerAbstractFileTableModel.h @@ -0,0 +1,111 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +namespace AssetBundler +{ + + extern const char* DateTimeFormat; + + //! Provides an abstract model that can be subclassed to create table models used to store information about files found on-disk. + class AssetBundlerAbstractFileTableModel + : public QAbstractTableModel + { + public: + + enum DataRoles + { + SortRole = Qt::UserRole + 1, + }; + + explicit AssetBundlerAbstractFileTableModel(QObject* parent = nullptr); + virtual ~AssetBundlerAbstractFileTableModel() {} + + ////////////////////////////////////////////////////////////////////////// + // Pure virtual functions + virtual AZStd::vector CreateNewFiles(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& platforms, const QString& project = QString()) = 0; + virtual bool DeleteFile(const QModelIndex& index) = 0; + virtual void LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName = "", bool isDefaultFile = false) = 0; + virtual bool WriteToDisk(const AZStd::string& key) = 0; + + //! Returns the absolute path of the file at the given index on success, returns an empty string on failure. + virtual AZStd::string GetFileAbsolutePath(const QModelIndex& index) const = 0; + + virtual int GetFileNameColumnIndex() const = 0; + virtual int GetTimeStampColumnIndex() const = 0; + ////////////////////////////////////////////////////////////////////////// + + //! Reload all the data based on the watched folders and files + virtual void Reload(const char* fileExtension, const QSet& watchedFolders, const QSet& watchedFiles = QSet(), const AZStd::unordered_map& pathToProjectNameMap = AZStd::unordered_map()); + + virtual void ReloadFiles(const AZStd::vector& absoluteFilePathList, AZStd::unordered_map pathToProjectNameMap = AZStd::unordered_map()); + + bool Save(const QModelIndex& selectedIndex); + + bool SaveAll(); + + bool HasUnsavedChanges() const; + + ////////////////////////////////////////////////////////////////////////// + // QAbstractTableModel overrides + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + ////////////////////////////////////////////////////////////////////////// + + protected: + //! Verifies input index is in range and returns the associated key. + //! Throws an error and returns an empty string if the input index is out of range. + AZStd::string GetFileKey(const QModelIndex& index) const; + + //! Returns an ordered list of every file key in the model. + //! If a proxy model is not used, the order of this list will also be the display order. + const AZStd::vector& GetAllFileKeys() const; + + //! Get the index row if the file info is stored in the model + //! Returns -1 if the file key doesn't exist. + int GetIndexRowByKey(const AZStd::string& key) const; + + //! Adds input key to the end of the list of all keys and notifies the view that a row has been added. + //! When subclassing, instantiate all data associated with this key so the view can update properly. + void AddFileKey(const AZStd::string& key); + + //! Verifies input index, signals to the view that rows will be removed, and removes the key found at the input index. + //! When subclassing, be sure to remove all data associated with the key at this index before calling this function. + //! Returns true if the index is successfully removed, and false on failure + bool RemoveFileKey(const QModelIndex& index); + + //! Delete a file from the disk and remove it from the model by its key + bool DeleteFileByKey(const AZStd::string& key); + + AZStd::unordered_set m_keysWithUnsavedChanges; + + private: + //! When subclassing: store file information in a map, and add the keys to this vector. + //! Provides a 1:1 mapping between a QModelIndex::row value and a key. + AZStd::vector m_fileListKeys; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.cpp b/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.cpp new file mode 100644 index 0000000000..f9a24392a2 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.cpp @@ -0,0 +1,62 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "AssetBundlerFileTableFilterModel.h" + +#include + +#include + +namespace AssetBundler +{ + AssetBundlerFileTableFilterModel::AssetBundlerFileTableFilterModel(QObject* parent, int displayNameCol, int dateTimeCol) + : QSortFilterProxyModel(parent) + , m_displayNameCol(displayNameCol) + , m_dateTimeCol(dateTimeCol) + { + } + + void AssetBundlerFileTableFilterModel::FilterChanged(const QString& newFilter) + { + setFilterRegExp(newFilter.toLower()); + invalidateFilter(); + } + + bool AssetBundlerFileTableFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const + { + // Override the default implemention since we want to define custom rules here + QModelIndex index = sourceModel()->index(sourceRow, m_displayNameCol, sourceParent); + QRegExp filter(filterRegExp()); + + return sourceModel()->data(index).toString().toLower().contains(filter); + } + + bool AssetBundlerFileTableFilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const + { + // Any column displaying a DateTime string needs to be compared as a DateTime object to ensure + // proper sorting + if (left.column() != m_dateTimeCol || right.column() != m_dateTimeCol) + { + return QSortFilterProxyModel::lessThan(left, right); + } + + QVariant leftTime = sourceModel()->data(left, AssetBundlerAbstractFileTableModel::SortRole); + QVariant rightTime = sourceModel()->data(right, AssetBundlerAbstractFileTableModel::SortRole); + + if (leftTime.type() != QVariant::DateTime || rightTime.type() != QVariant::DateTime) + { + return QSortFilterProxyModel::lessThan(left, right); + } + + return leftTime.toDateTime() < rightTime.toDateTime(); + } +} diff --git a/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.h b/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.h new file mode 100644 index 0000000000..89f33c065a --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetBundlerFileTableFilterModel.h @@ -0,0 +1,34 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace AssetBundler +{ + class AssetBundlerFileTableFilterModel + : public QSortFilterProxyModel + { + public: + AssetBundlerFileTableFilterModel(QObject* parent, int displayNameCol, int dateTimeCol = -1); + + void FilterChanged(const QString& newFilter); + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + + int m_displayNameCol = 0; + int m_dateTimeCol = -1; + }; +} diff --git a/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.cpp b/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.cpp new file mode 100644 index 0000000000..e4bb7b6ca2 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.cpp @@ -0,0 +1,251 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace AssetBundler +{ + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // AssetListFileInfo + ////////////////////////////////////////////////////////////////////////////////////////////////// + AssetListFileInfo::AssetListFileInfo(const AZStd::string& absolutePath, const QString& fileName, const AZStd::string& platform) + : m_absolutePath(absolutePath) + , m_fileName(fileName) + , m_platform(QString(platform.c_str())) + { + AzFramework::StringFunc::Path::Normalize(m_absolutePath); + + // Modification time will either give us the time the file was last overwritten + // (or the time it was created if it has never been overwritten) + QFileInfo fileInfo(m_absolutePath.c_str()); + m_fileCreationTime = fileInfo.fileTime(QFileDevice::FileModificationTime); + + // Load the contents of the asset list file into memory + m_assetListModel.reset(new AssetListTableModel(nullptr, absolutePath, platform)); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // AssetListFileTableModel + ////////////////////////////////////////////////////////////////////////////////////////////////// + AssetListFileTableModel::AssetListFileTableModel() + : AssetBundlerAbstractFileTableModel() + { + } + + QSharedPointer AssetListFileTableModel::GetAssetListFileContents(const QModelIndex& index) + { + auto assetListFileInfoOutcome = GetAssetFileInfo(index); + if (!assetListFileInfoOutcome.IsSuccess()) + { + return QSharedPointer(); + } + + return assetListFileInfoOutcome.GetValue()->m_assetListModel; + } + + bool AssetListFileTableModel::DeleteFile(const QModelIndex& index) + { + auto assetFileInfoOutcome = GetAssetFileInfo(index); + if (!assetFileInfoOutcome.IsSuccess()) + { + return false; + } + + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(assetFileInfoOutcome.GetValue()->m_absolutePath); + if (m_assetListFileInfoMap.find(key) == m_assetListFileInfoMap.end()) + { + return false; + } + + AssetListFileInfoPtr assetFileInfo = m_assetListFileInfoMap[key]; + + // Remove file from disk + const char* absolutePath = assetFileInfo->m_absolutePath.c_str(); + if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath)) + { + if (AZ::IO::FileIOBase::GetInstance()->IsReadOnly(absolutePath)) + { + return false; + } + + auto deleteResult = AZ::IO::FileIOBase::GetInstance()->Remove(absolutePath); + if (!deleteResult) + { + return false; + } + } + + // Remove file from model + m_assetListFileInfoMap.erase(key); + RemoveFileKey(index); + + return true; + } + + void AssetListFileTableModel::LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& /*projectName*/, bool /*isDefaultFile*/) + { + AZStd::string fullFileName; + AzFramework::StringFunc::Path::GetFullFileName(absoluteFilePath.c_str(), fullFileName); + + // Get the file name without the platform for display purposes + AZStd::string baseFileName; + AZStd::string platformIdentifier; + AzToolsFramework::SplitFilename(fullFileName, baseFileName, platformIdentifier); + + // Read the AssetListFile into memory and store it + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + m_assetListFileInfoMap[key].reset(new AssetListFileInfo(absoluteFilePath, QString(baseFileName.c_str()), platformIdentifier)); + AddFileKey(key); + } + + bool AssetListFileTableModel::WriteToDisk(const AZStd::string& /*key*/) + { + return true; + } + + AZStd::string AssetListFileTableModel::GetFileAbsolutePath(const QModelIndex& index) const + { + auto assetFileInfoOutcome = GetAssetFileInfo(index); + if (!assetFileInfoOutcome.IsSuccess()) + { + // Error has already been thrown + return AZStd::string(); + } + return assetFileInfoOutcome.GetValue()->m_absolutePath; + } + + int AssetListFileTableModel::GetFileNameColumnIndex() const + { + return Column::ColumnFileName; + } + + int AssetListFileTableModel::GetTimeStampColumnIndex() const + { + return Column::ColumnFileCreationTime; + } + + int AssetListFileTableModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant AssetListFileTableModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnFileName: + return QString(tr("Asset List File")); + case Column::ColumnPlatform: + return QString(tr("Platform")); + case Column::ColumnFileCreationTime: + return QString(tr("Creation Time")); + default: + break; + } + } + + return QVariant(); + } + + QVariant AssetListFileTableModel::data(const QModelIndex& index, int role) const + { + auto assetListFileInfoOutcome = GetAssetFileInfo(index); + if (!assetListFileInfoOutcome.IsSuccess()) + { + return QVariant(); + } + + int col = index.column(); + + switch (role) + { + case Qt::DisplayRole: + [[fallthrough]]; + case SortRole: + { + if (col == Column::ColumnFileName) + { + return assetListFileInfoOutcome.GetValue()->m_fileName; + } + else if (col == Column::ColumnPlatform) + { + return assetListFileInfoOutcome.GetValue()->m_platform; + } + else if (col == Column::ColumnFileCreationTime) + { + if (role == SortRole) + { + return assetListFileInfoOutcome.GetValue()->m_fileCreationTime; + } + else + { + return assetListFileInfoOutcome.GetValue()->m_fileCreationTime.toString(DateTimeFormat); + } + } + else + { + // Returning an empty QString will ensure the checkboxes do not have any text displayed next to them + return QString(); + } + } + case Qt::FontRole: + { + if (col == Column::ColumnFileName) + { + QFont boldFont; + boldFont.setBold(true); + return boldFont; + } + } + default: + break; + } + + return QVariant(); + } + + AZ::Outcome AssetListFileTableModel::GetAssetFileInfo(const QModelIndex& index) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return AZ::Failure(); + } + + if (m_assetListFileInfoMap.find(key) != m_assetListFileInfoMap.end()) + { + return AZ::Success(m_assetListFileInfoMap.at(key)); + } + else + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot find Asset List File Info"); + return AZ::Failure(); + } + } + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.h b/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.h new file mode 100644 index 0000000000..61f1455130 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetListFileTableModel.h @@ -0,0 +1,88 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include + +namespace AssetBundler +{ + class AssetListTableModel; + + struct AssetListFileInfo + { + /** + * Stores information about an Asset List File on disk. + * + * @param absolutePath The absolute path of the Asset List File + * @param fileName The name of the Asset List File. This does not include the platform ID + * @param platform The platform for the Asset List File + */ + AssetListFileInfo(const AZStd::string& absolutePath, const QString& fileName, const AZStd::string& platform); + + AZStd::string m_absolutePath; + QDateTime m_fileCreationTime; + /// Use QString for display purpose. This can help to avoid losts of string conversion + QString m_fileName; + QString m_platform; + + QSharedPointer m_assetListModel; + }; + + using AssetListFileInfoPtr = AZStd::shared_ptr; + /// Stores AssetListFileInfo, using the absolute path (without the drive letter) of the Asset List file as the key + using AssetListFileInfoMap = AZStd::unordered_map; + + class AssetListFileTableModel + : public AssetBundlerAbstractFileTableModel + { + public: + explicit AssetListFileTableModel(); + virtual ~AssetListFileTableModel() {} + + QSharedPointer GetAssetListFileContents(const QModelIndex& index); + + ////////////////////////////////////////////////////////////////////////// + // AssetBundlerAbstractFileTableModel overrides + AZStd::vector CreateNewFiles(const AZStd::string& /*absoluteFilePath*/, const AzFramework::PlatformFlags& /*platforms*/, const QString& /*project*/) override { return {}; } + bool DeleteFile(const QModelIndex& index) override; + void LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName = "", bool isDefaultFile = false) override; + bool WriteToDisk(const AZStd::string& key) override; + AZStd::string GetFileAbsolutePath(const QModelIndex& index) const override; + int GetFileNameColumnIndex() const override; + int GetTimeStampColumnIndex() const override; + + ////////////////////////////////////////////////////////////////////////// + // QAbstractListModel overrides + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnFileName, + ColumnPlatform, + ColumnFileCreationTime, + Max + }; + + private: + AZ::Outcome GetAssetFileInfo(const QModelIndex& index) const; + + AssetListFileInfoMap m_assetListFileInfoMap; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/AssetListTableModel.cpp b/Code/Tools/AssetBundler/source/models/AssetListTableModel.cpp new file mode 100644 index 0000000000..cec057d38b --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetListTableModel.cpp @@ -0,0 +1,129 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include + +#include + +namespace AssetBundler +{ + ////////////////////////////////////////////////////////////////////////////////////////////////// + // AssetListTableModel + ////////////////////////////////////////////////////////////////////////////////////////////////// + AssetListTableModel::AssetListTableModel(QObject* parent, const AZStd::string& absolutePath, const AZStd::string& platform) + : QAbstractTableModel(parent) + { + m_seedListManager.reset(new AzToolsFramework::AssetSeedManager()); + + if (absolutePath.empty() || platform.empty()) + { + return; + } + + AZ::Outcome outcome = m_seedListManager->LoadAssetFileInfo(absolutePath); + if (!outcome.IsSuccess()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Failed to load the asset file info for %s", absolutePath.c_str()); + return; + } + + m_assetFileInfoList = outcome.TakeValue(); + m_platformId = static_cast(AzFramework::PlatformHelper::GetPlatformIndexFromName(platform.c_str())); + } + + int AssetListTableModel::rowCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : static_cast(m_assetFileInfoList.m_fileInfoList.size()); + } + + int AssetListTableModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant AssetListTableModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnAssetName: + return QString("Asset Name"); + case Column::ColumnRelativePath: + return QString("Relative Path"); + case Column::ColumnAssetId: + return QString("Asset ID"); + default: + break; + } + } + + return QVariant(); + } + + QVariant AssetListTableModel::data(const QModelIndex& index, int role) const + { + auto assetFileInfoOutcome = GetAssetFileInfo(index); + if (!assetFileInfoOutcome.IsSuccess()) + { + return QVariant(); + } + + switch (role) + { + case Qt::DisplayRole: + { + switch (index.column()) + { + case Column::ColumnAssetName: + { + AZStd::string fileName = assetFileInfoOutcome.GetValue().m_assetRelativePath; + AzFramework::StringFunc::Path::GetFullFileName(fileName.c_str(), fileName); + + return fileName.c_str(); + } + case Column::ColumnRelativePath: + { + return assetFileInfoOutcome.GetValue().m_assetRelativePath.c_str(); + } + case Column::ColumnAssetId: + { + AZStd::string assetIdStr; + assetFileInfoOutcome.GetValue().m_assetId.ToString(assetIdStr); + + return assetIdStr.c_str(); + } + default: + break; + } + } + default: + break; + } + + return QVariant(); + } + + AZ::Outcome AssetListTableModel::GetAssetFileInfo(const QModelIndex& index) const + { + int row = index.row(); + int col = index.column(); + if (row >= rowCount() || row < 0 || col >= columnCount() || col < 0) + { + return AZ::Failure(AZStd::string::format("Selected index (%i, %i) is out of range", row, col)); + } + + return AZ::Success(m_assetFileInfoList.m_fileInfoList.at(row)); + } +} diff --git a/Code/Tools/AssetBundler/source/models/AssetListTableModel.h b/Code/Tools/AssetBundler/source/models/AssetListTableModel.h new file mode 100644 index 0000000000..94500f520f --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/AssetListTableModel.h @@ -0,0 +1,54 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include + +#include + +namespace AssetBundler +{ + class AssetListTableModel + : public QAbstractTableModel + { + public: + explicit AssetListTableModel(QObject* parent = nullptr, const AZStd::string& absolutePath = AZStd::string(), const AZStd::string& platform = ""); + virtual ~AssetListTableModel() {} + + AZStd::shared_ptr GetSeedListManager() { return m_seedListManager; } + + ////////////////////////////////////////////////////////////////////////// + // QAbstractListModel overrides + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnAssetName, + ColumnRelativePath, + ColumnAssetId, + Max + }; + + private: + AZ::Outcome GetAssetFileInfo(const QModelIndex& index) const; + + AZStd::shared_ptr m_seedListManager; + AzToolsFramework::AssetFileInfoList m_assetFileInfoList; + AzFramework::PlatformId m_platformId; + }; +} diff --git a/Code/Tools/AssetBundler/source/models/BundleFileListModel.cpp b/Code/Tools/AssetBundler/source/models/BundleFileListModel.cpp new file mode 100644 index 0000000000..d7c0e93279 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/BundleFileListModel.cpp @@ -0,0 +1,236 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include +#include + +#include + +#include + + +namespace AssetBundler +{ + BundleFileInfo::BundleFileInfo(const AZStd::string& absolutePath) + : m_absolutePath(absolutePath) + , m_compressedSize(0u) + { + AzFramework::StringFunc::Path::Normalize(m_absolutePath); + + AZStd::string fileNameStr; + if (AzFramework::StringFunc::Path::GetFileName(m_absolutePath.c_str(), fileNameStr)) + { + m_fileName = QString(fileNameStr.c_str()); + } + else + { + AZ_Error("AssetBundler", false, "Failed to get file name from %s", m_absolutePath.c_str()); + } + + // Modification time will either give us the time the file was last overwritten + // (or the time it was created if it has never been overwritten) + QFileInfo fileInfo(m_absolutePath.c_str()); + m_fileCreationTime = fileInfo.fileTime(QFileDevice::FileModificationTime); + } + + BundleFileListModel::BundleFileListModel() + : AssetBundlerAbstractFileTableModel() + { + } + + bool BundleFileListModel::DeleteFile(const QModelIndex& index) + { + AZStd::string fileKey = GetFileKey(index); + if (fileKey.empty()) + { + // Error has already been thrown + return false; + } + + auto bundleFileInfoIt = m_bundleFileInfoMap.find(fileKey); + if (bundleFileInfoIt == m_bundleFileInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find Bundle Info with key ( %s )", fileKey.c_str()); + return false; + } + + BundleFileInfoPtr bundleFileInfo = bundleFileInfoIt->second; + + // Remove file from disk + const char* absolutePath = bundleFileInfo->m_absolutePath.c_str(); + if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath)) + { + if (AZ::IO::FileIOBase::GetInstance()->IsReadOnly(absolutePath)) + { + AZ_Error(AssetBundler::AppWindowName, false, "File (%s) is Read-Only. Please check your version control and try again.", absolutePath); + return false; + } + + auto deleteResult = AZ::IO::FileIOBase::GetInstance()->Remove(absolutePath); + if (!deleteResult) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to delete: %s", absolutePath); + return false; + } + } + + // Remove file from Model + m_bundleFileInfoMap.erase(fileKey); + RemoveFileKey(index); + + return true; + } + + int BundleFileListModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant BundleFileListModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnFileName: + return QString(tr("Name")); + case Column::ColumnFileCreationTime: + return QString(tr("Creation Time")); + default: + break; + } + } + return QVariant(); + } + + QVariant BundleFileListModel::data(const QModelIndex& index, int role) const + { + auto bundleInfoOutcome = GetBundleInfo(index); + if (!bundleInfoOutcome.IsSuccess()) + { + return QVariant(); + } + + int col = index.column(); + + switch (role) + { + case Qt::DisplayRole: + [[fallthrough]]; + case SortRole: + { + if (col == Column::ColumnFileName) + { + return bundleInfoOutcome.GetValue()->m_fileName; + } + else if (col == Column::ColumnFileCreationTime) + { + if (role == SortRole) + { + return bundleInfoOutcome.GetValue()->m_fileCreationTime; + } + else + { + return bundleInfoOutcome.GetValue()->m_fileCreationTime.toString(DateTimeFormat); + } + } + } + default: + break; + } + + return QVariant(); + } + + AZStd::string BundleFileListModel::GetFileAbsolutePath(const QModelIndex& index) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return key; + } + + return m_bundleFileInfoMap.at(key)->m_absolutePath; + } + + int BundleFileListModel::GetFileNameColumnIndex() const + { + return Column::ColumnFileName; + } + + int BundleFileListModel::GetTimeStampColumnIndex() const + { + return Column::ColumnFileCreationTime; + } + + void BundleFileListModel::LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& /*projectName*/, bool /*isDefaultFile*/) + { + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + + BundleFileInfo* newInfo = new BundleFileInfo(absoluteFilePath); + + QFile bundleFile(absoluteFilePath.c_str()); + if (bundleFile.open(QIODevice::ReadOnly)) + { + newInfo->m_compressedSize = bundleFile.size(); + bundleFile.close(); + } + else + { + AZ_Error("AssetBundler", false, "Failed to open file at %s", absoluteFilePath.c_str()); + } + + auto manifest = AzToolsFramework::AssetBundleComponent::GetManifestFromBundle(absoluteFilePath); + if (manifest != nullptr) + { + for (auto& bundleName : manifest->GetDependentBundleNames()) + { + newInfo->m_relatedBundles.push_back(bundleName.c_str()); + } + } + else + { + AZ_Error("AssetBundler", false, "Failed to get manifest from bundle at %s", absoluteFilePath.c_str()); + } + + m_bundleFileInfoMap[key].reset(newInfo); + // Add it to the list that gets displayed by AssetBundlerAbstractFileTableModel. Make sure that + // AddFileKey is called after m_bundleFileInfoMap is reset since the filterAcceptsRow funtion + // of the filter model will be called when a new row is inserted and it could cause a crash + // without valid data being set + AddFileKey(key); + } + + AZ::Outcome BundleFileListModel::GetBundleInfo(const QModelIndex& index) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return AZ::Failure(); + } + + if (m_bundleFileInfoMap.find(key) != m_bundleFileInfoMap.end()) + { + return AZ::Success(m_bundleFileInfoMap.at(key)); + } + else + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot find Bundle File Info"); + return AZ::Failure(); + } + } + +} diff --git a/Code/Tools/AssetBundler/source/models/BundleFileListModel.h b/Code/Tools/AssetBundler/source/models/BundleFileListModel.h new file mode 100644 index 0000000000..13e5838c57 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/BundleFileListModel.h @@ -0,0 +1,74 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include + +#include + +#include + +namespace AssetBundler +{ + + struct BundleFileInfo + { + AZStd::string m_absolutePath; + QString m_fileName; + QDateTime m_fileCreationTime; + AZ::u64 m_compressedSize; + QStringList m_relatedBundles; + + BundleFileInfo(const AZStd::string& absolutePath); + }; + + using BundleFileInfoPtr = AZStd::shared_ptr; + using BundleFileInfoMap = AZStd::unordered_map; + + class BundleFileListModel + : public AssetBundlerAbstractFileTableModel + { + public: + explicit BundleFileListModel(); + virtual ~BundleFileListModel() {} + + AZStd::vector CreateNewFiles(const AZStd::string& /*absoluteFilePath*/, const AzFramework::PlatformFlags& /*platforms*/, const QString& /*project*/) override { return {}; } + bool DeleteFile(const QModelIndex& index) override; + void LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName = "", bool isDefaultFile = false) override; + bool WriteToDisk(const AZStd::string& /*key*/) override { return true; } + + AZStd::string GetFileAbsolutePath(const QModelIndex& index) const override; + int GetFileNameColumnIndex() const override; + int GetTimeStampColumnIndex() const override; + + AZ::Outcome GetBundleInfo(const QModelIndex& index) const; + + ////////////////////////////////////////////////////////////////////////// + // QAbstractTableModel overrides + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnFileName, + ColumnFileCreationTime, + Max + }; + + private: + BundleFileInfoMap m_bundleFileInfoMap; + }; +} diff --git a/Code/Tools/AssetBundler/source/models/RulesFileTableModel.cpp b/Code/Tools/AssetBundler/source/models/RulesFileTableModel.cpp new file mode 100644 index 0000000000..c7ae384d6e --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/RulesFileTableModel.cpp @@ -0,0 +1,305 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include +#include +#include +#include + +#include +#include + +namespace AssetBundler +{ + RulesFileInfo::RulesFileInfo(const AZStd::string& absolutePath, const QString& fileName, bool loadFromFile) + : m_absolutePath(absolutePath) + , m_fileName(fileName) + { + AzFramework::StringFunc::Path::Normalize(m_absolutePath); + m_comparisonSteps = AZStd::make_shared(); + + if (loadFromFile) + { + auto outcome = AzToolsFramework::AssetFileInfoListComparison::Load(m_absolutePath); + if (!outcome.IsSuccess()) + { + AZ_Error("AssetBundler", false, outcome.GetError().c_str()); + return; + } + m_comparisonSteps = AZStd::make_unique(outcome.TakeValue()); + + QFileInfo fileInfo(m_absolutePath.c_str()); + m_fileModificationTime = fileInfo.fileTime(QFileDevice::FileModificationTime); + } + else + { + m_fileModificationTime = QDateTime::currentDateTime(); + } + } + + bool RulesFileInfo::SaveRulesFile() + { + if (!m_comparisonSteps->Save(m_absolutePath)) + { + return false; + } + + m_hasUnsavedChanges = false; + m_fileModificationTime = QDateTime::currentDateTime(); + return true; + } + + + + RulesFileTableModel::RulesFileTableModel() + : AssetBundlerAbstractFileTableModel() + { + } + + AZStd::vector RulesFileTableModel::CreateNewFiles(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& /*platforms*/, const QString& /*project*/) + { + if (absoluteFilePath.empty()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Input file path is empty"); + return {}; + } + + // Create a platform-specific file path + if (AZ::IO::FileIOBase::GetInstance()->Exists(absoluteFilePath.c_str())) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot Create New File: (%s) already exists", absoluteFilePath.c_str()); + return {}; + } + + // Get the file name without the extension for display purposes + AZStd::string fileName = absoluteFilePath; + AzToolsFramework::RemovePlatformIdentifier(fileName); + AzFramework::StringFunc::Path::GetFileName(fileName.c_str(), fileName); + + // Create a Rules File and save it to disk + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + RulesFileInfoPtr newRulesFile = AZStd::make_shared(absoluteFilePath, QString(fileName.c_str()), false); + + if (!newRulesFile->SaveRulesFile()) + { + return {}; + } + + // Add the new file to the model + m_rulesFileInfoMap[key] = newRulesFile; + AddFileKey(key); + return { absoluteFilePath }; + } + + bool RulesFileTableModel::DeleteFile(const QModelIndex& index) + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + RulesFileInfoPtr rulesFileInfo = m_rulesFileInfoMap[key]; + + // Remove file from disk + if (AZ::IO::FileIOBase::GetInstance()->IsReadOnly(rulesFileInfo->m_absolutePath.c_str())) + { + AZ_Error(AssetBundler::AppWindowName, false, "File (%s) is Read-Only. Please check your version control and try again.", rulesFileInfo->m_absolutePath.c_str()); + return false; + } + + auto deleteResult = AZ::IO::FileIOBase::GetInstance()->Remove(rulesFileInfo->m_absolutePath.c_str()); + if (!deleteResult) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to delete: %s", rulesFileInfo->m_absolutePath.c_str()); + return false; + } + + // Remove file from model + m_rulesFileInfoMap.erase(key); + RemoveFileKey(index); + + return true; + } + + void RulesFileTableModel::LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& /*projectName*/, bool /*isDefaultFile*/) + { + // Get the file name without the extension for display purposes + AZStd::string fileName(absoluteFilePath); + AzFramework::StringFunc::Path::GetFileName(fileName.c_str(), fileName); + + // Read the Rules file into memory and store it + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + auto fileInfoIt = m_rulesFileInfoMap.find(key); + if (fileInfoIt != m_rulesFileInfoMap.end() && fileInfoIt->second->m_hasUnsavedChanges) + { + AZ_Warning(AssetBundler::AppWindowName, false, "Rules File %s has unsaved changes and couldn't be reloaded", absoluteFilePath.c_str()); + return; + } + + m_rulesFileInfoMap[key] = AZStd::make_shared(absoluteFilePath, QString(fileName.c_str()), true); + AddFileKey(key); + } + + bool RulesFileTableModel::WriteToDisk(const AZStd::string& key) + { + auto rulesFileIt = m_rulesFileInfoMap.find(key); + return rulesFileIt != m_rulesFileInfoMap.end() && rulesFileIt->second->SaveRulesFile(); + } + + AZStd::string RulesFileTableModel::GetFileAbsolutePath(const QModelIndex& index) const + { + RulesFileInfoPtr rulesFileInfo = GetRulesFileInfoPtr(index); + if (!rulesFileInfo) + { + return AZStd::string(); + } + + return rulesFileInfo->m_absolutePath; + } + + int RulesFileTableModel::GetFileNameColumnIndex() const + { + return Column::ColumnFileName; + } + + int RulesFileTableModel::GetTimeStampColumnIndex() const + { + return Column::ColumnFileModificationTime; + } + + AZStd::shared_ptr RulesFileTableModel::GetComparisonSteps(const QModelIndex& index) const + { + RulesFileInfoPtr rulesFileInfo = GetRulesFileInfoPtr(index); + if (!rulesFileInfo) + { + return nullptr; + } + + return rulesFileInfo->m_comparisonSteps; + } + + bool RulesFileTableModel::MarkFileChanged(const QModelIndex& index) + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + m_rulesFileInfoMap[key]->m_hasUnsavedChanges = true; + + // Update display so the user knows there are unsaved changes + m_keysWithUnsavedChanges.emplace(key); + QModelIndex changedIndex = QAbstractTableModel::index(index.row(), Column::ColumnFileName); + emit dataChanged(changedIndex, changedIndex, { Qt::DisplayRole, Qt::FontRole }); + + return true; + } + + int RulesFileTableModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant RulesFileTableModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnFileName: + return QString(tr("Name")); + case Column::ColumnFileModificationTime: + return QString(tr("Modification Time")); + default: + break; + } + } + + return QVariant(); + } + + QVariant RulesFileTableModel::data(const QModelIndex& index, int role) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + return QVariant(); + } + + RulesFileInfoPtr rulesFileInfo = m_rulesFileInfoMap.at(key); + + int col = index.column(); + bool hasUnsavedChanges = rulesFileInfo->m_hasUnsavedChanges; + + switch (role) + { + case Qt::DisplayRole: + [[fallthrough]]; + case SortRole: + { + if (col == Column::ColumnFileName) + { + QString displayName = rulesFileInfo->m_fileName; + if (hasUnsavedChanges) + { + displayName.append("*"); + } + return displayName; + } + else if (col == Column::ColumnFileModificationTime) + { + if (role == SortRole) + { + return rulesFileInfo->m_fileModificationTime; + } + else + { + return rulesFileInfo->m_fileModificationTime.toString(DateTimeFormat); + } + } + break; + } + case Qt::FontRole: + { + if (col == Column::ColumnFileName && hasUnsavedChanges) + { + QFont boldFont; + boldFont.setBold(true); + return boldFont; + } + break; + } + default: + break; + } + + return QVariant(); + } + + RulesFileInfoPtr RulesFileTableModel::GetRulesFileInfoPtr(const QModelIndex& index) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + return nullptr; + } + + return m_rulesFileInfoMap.at(key); + } + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/RulesFileTableModel.h b/Code/Tools/AssetBundler/source/models/RulesFileTableModel.h new file mode 100644 index 0000000000..a9931a9819 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/RulesFileTableModel.h @@ -0,0 +1,88 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include + +#include + +#include + +namespace AssetBundler +{ + + //! Stores information about a Rules file on disk. + struct RulesFileInfo + { + RulesFileInfo(const AZStd::string& absolutePath, const QString& fileName, bool loadFromFile); + + bool SaveRulesFile(); + + AZStd::string m_absolutePath; + QString m_fileName; + QDateTime m_fileModificationTime; + bool m_hasUnsavedChanges = false; + + AZStd::shared_ptr m_comparisonSteps; + }; + + using RulesFileInfoPtr = AZStd::shared_ptr; + using RulesFileInfoMap = AZStd::unordered_map; + + + class RulesFileTableModel + : public AssetBundlerAbstractFileTableModel + { + public: + RulesFileTableModel(); + virtual ~RulesFileTableModel() {} + + AZStd::vector CreateNewFiles(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& platforms = AzFramework::PlatformFlags::Platform_NONE, const QString& project = QString()) override; + + bool DeleteFile(const QModelIndex& index) override; + + void LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName = "", bool isDefaultFile = false) override; + + bool WriteToDisk(const AZStd::string& key) override; + + AZStd::string GetFileAbsolutePath(const QModelIndex& index) const override; + + int GetFileNameColumnIndex() const override; + + int GetTimeStampColumnIndex() const override; + + AZStd::shared_ptr GetComparisonSteps(const QModelIndex& index) const; + + bool MarkFileChanged(const QModelIndex& index); + + ////////////////////////////////////////////////////////////////////////// + // QAbstractTableModel overrides + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnFileName, + ColumnFileModificationTime, + Max + }; + + private: + RulesFileInfoMap m_rulesFileInfoMap; + + RulesFileInfoPtr GetRulesFileInfoPtr(const QModelIndex& index) const; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.cpp b/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.cpp new file mode 100644 index 0000000000..1e7ec36bac --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.cpp @@ -0,0 +1,601 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace AssetBundler +{ + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // SeedListFileInfo + ////////////////////////////////////////////////////////////////////////////////////////////////// + SeedListFileInfo::SeedListFileInfo( + const AZStd::string& absolutePath, + const QString& fileName, + const QString& project, + bool loadFromFile, + bool isDefaultSeedList, + const AZStd::vector& defaultSeeds, + const AzFramework::PlatformFlags& platforms) + : m_absolutePath(absolutePath) + , m_fileName(fileName) + , m_project(project) + , m_isDefaultSeedList(isDefaultSeedList) + { + AzFramework::StringFunc::Path::Normalize(m_absolutePath); + + // Load the contents of the seed file into memory + if (loadFromFile) + { + m_seedListModel.reset(new SeedListTableModel(nullptr, absolutePath)); + QFileInfo fileInfo(m_absolutePath.c_str()); + m_fileModificationTime = fileInfo.fileTime(QFileDevice::FileModificationTime); + } + else + { + m_seedListModel.reset(new SeedListTableModel(nullptr, "", defaultSeeds, platforms)); + m_fileModificationTime = QDateTime::currentDateTime(); + } + } + + bool SeedListFileInfo::SaveSeedFile() + { + if (m_seedListModel->Save(m_absolutePath)) + { + m_fileModificationTime = QDateTime::currentDateTime(); + return true; + } + return false; + } + + bool SeedListFileInfo::HasUnsavedChanges() + { + return m_seedListModel->HasUnsavedChanges(); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // SeedListFileTableModel + ////////////////////////////////////////////////////////////////////////////////////////////////// + + SeedListFileTableModel::SeedListFileTableModel(SeedTabWidget* parentSeedTabWidget) + : AssetBundlerAbstractFileTableModel() + , m_seedTabWidget(parentSeedTabWidget) + { + m_inMemoryDefaultSeedList.reset(new SeedListFileInfo("", "", "", false)); + } + + SeedListFileTableModel::~SeedListFileTableModel() + { + m_seedTabWidget = nullptr; + } + + void SeedListFileTableModel::AddDefaultSeedsToInMemoryList(const AZStd::vector& defaultSeeds, const char* projectName, const AzFramework::PlatformFlags& platforms) + { + m_inMemoryDefaultSeedList.reset(new SeedListFileInfo(m_inMemoryDefaultSeedListKey, tr("DefaultSeeds"), QString(projectName), false, true, defaultSeeds, platforms)); + } + + AZStd::vector SeedListFileTableModel::CreateNewFiles(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& /*platforms*/, const QString& project) + { + if (absoluteFilePath.empty()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Input file path is empty."); + return {}; + } + + // Get the file name without the extension for display purposes + AZStd::string fileName; + AzFramework::StringFunc::Path::GetFileName(absoluteFilePath.c_str(), fileName); + + // Create a Seed List File and save it to disk + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + + AZStd::shared_ptr newSeedListFile = AZStd::make_shared(absoluteFilePath, QString(fileName.c_str()), project, false); + newSeedListFile->m_seedListModel->SetHasUnsavedChanges(true); + bool saveResult = newSeedListFile->SaveSeedFile(); + if (!saveResult) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to create Seed List File: %s", absoluteFilePath.c_str()); + return {}; + } + + // Add new file to the model + m_seedListFileInfoMap[key] = newSeedListFile; + AddFileKey(key); + + AZStd::vector createdFiles = { absoluteFilePath }; + return createdFiles; + } + + bool SeedListFileTableModel::DeleteFile(const QModelIndex& index) + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + if (m_seedListFileInfoMap.find(key) == m_seedListFileInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find Seed File Info with key ( %s )", key.c_str()); + return false; + } + + SeedListFileInfoPtr seedFileInfo = m_seedListFileInfoMap[key]; + + // Remove file from disk + const char* absolutePath = seedFileInfo->m_absolutePath.c_str(); + if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath)) + { + if (AZ::IO::FileIOBase::GetInstance()->IsReadOnly(absolutePath)) + { + AZ_Error(AssetBundler::AppWindowName, false, "File (%s) is Read-Only. Please check your version control and try again.", absolutePath); + return false; + } + + auto deleteResult = AZ::IO::FileIOBase::GetInstance()->Remove(absolutePath); + if (!deleteResult) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to delete: %s", absolutePath); + return false; + } + } + + // Remove file from model + m_seedListFileInfoMap.erase(key); + RemoveFileKey(index); + + return true; + } + + void SeedListFileTableModel::Reload(const char* fileExtension, const QSet& watchedFolders, const QSet& watchedFiles, const AZStd::unordered_map& pathToProjectNameMap) + { + // Load in the Seed List files from disk + AssetBundlerAbstractFileTableModel::Reload(fileExtension, watchedFolders, watchedFiles, pathToProjectNameMap); + + // Add the in-memory Default Seed List to the model + m_seedListFileInfoMap[m_inMemoryDefaultSeedListKey] = m_inMemoryDefaultSeedList; + AddFileKey(m_inMemoryDefaultSeedListKey); + } + + void SeedListFileTableModel::LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName, bool isDefaultFile) + { + // Get the file name without the extension for display purposes + AZStd::string fileName; + AzFramework::StringFunc::Path::GetFileName(absoluteFilePath.c_str(), fileName); + + // Read the SeedListFile into memory and store it + AZStd::string key = AssetBundler::GenerateKeyFromAbsolutePath(absoluteFilePath); + auto fileInfoIt = m_seedListFileInfoMap.find(key); + if (fileInfoIt != m_seedListFileInfoMap.end() && fileInfoIt->second->HasUnsavedChanges()) + { + AZ_Warning(AssetBundler::AppWindowName, false, "Seed List File %s has unsaved changes and couldn't be reloaded", absoluteFilePath.c_str()); + return; + } + + AZStd::string projectNameOnDisplay = projectName; + if (projectName.empty()) + { + auto outcome = AssetBundler::GetCurrentProjectName(); + if (!outcome.IsSuccess()) + { + AZ_Error(AssetBundler::AppWindowName, false, outcome.TakeError().c_str()); + return; + } + + projectNameOnDisplay = outcome.TakeValue(); + } + + m_seedListFileInfoMap[key].reset(new SeedListFileInfo(absoluteFilePath, QString(fileName.c_str()), QString(projectNameOnDisplay.c_str()), true, isDefaultFile)); + AddFileKey(key); + } + + void SeedListFileTableModel::SelectDefaultSeedLists(bool setSelected) + { + for (const AZStd::string& key : GetAllFileKeys()) + { + auto seedFileInfo = m_seedListFileInfoMap[key]; + if (!seedFileInfo->m_isDefaultSeedList) + { + continue; + } + + seedFileInfo->m_isChecked = setSelected; + if(setSelected) + { + m_checkedSeedListFiles.insert(key); + } + else + { + m_checkedSeedListFiles.erase(key); + } + } + + m_seedTabWidget->SetGenerateAssetListsButtonEnabled(!m_checkedSeedListFiles.empty()); + + // Update the Check State display of all elements + QModelIndex firstIndex = index(0, 0); + QModelIndex lastIndex = index(rowCount() - 1, columnCount() - 1); + emit dataChanged(firstIndex, lastIndex, { Qt::CheckStateRole }); + } + + AZStd::vector SeedListFileTableModel::GenerateAssetLists(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& platforms) + { + if (!m_checkedSeedListFiles.size()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot Generate Asset List File(s): No Seed List Files are selected"); + return {}; + } + + if (absoluteFilePath.empty()) + { + AZ_Error(AssetBundler::AppWindowName, false, "File path cannot be empty"); + return {}; + } + + if(platforms == AzFramework::PlatformFlags::Platform_NONE) + { + AZ_Error(AssetBundler::AppWindowName, false, "Input platform cannot be empty"); + return {}; + } + + // Get all of the Seeds into one AssetSeedManager + AzToolsFramework::AssetSeedManager assetSeedManager; + + if (m_checkedSeedListFiles.find(m_inMemoryDefaultSeedListKey) != m_checkedSeedListFiles.end()) + { + // The In-Memory Default Seed List can't be loaded from a file on disk, it is a special case + assetSeedManager = *m_inMemoryDefaultSeedList->m_seedListModel->GetSeedListManager().get(); + m_checkedSeedListFiles.erase(m_inMemoryDefaultSeedListKey); + } + + for (const auto& checkedSeedFileKey : m_checkedSeedListFiles) + { + assetSeedManager.Load(m_seedListFileInfoMap[checkedSeedFileKey]->m_absolutePath); + } + + // Generate an AssetList for every input platform + AZStd::vector createdFiles; + for (const auto& platformIndex : AzFramework::PlatformHelper::GetPlatformIndicesInterpreted(platforms)) + { + AZStd::string platformSpecificCachePath = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(platformIndex); + AzFramework::StringFunc::Path::StripFullName(platformSpecificCachePath); + + FilePath platformSpecificPath(absoluteFilePath, AZStd::string(AzFramework::PlatformHelper::GetPlatformName(platformIndex))); + if (assetSeedManager.SaveAssetFileInfo(platformSpecificPath.AbsolutePath(), AzFramework::PlatformHelper::GetPlatformFlagFromPlatformIndex(platformIndex))) + { + createdFiles.emplace_back(platformSpecificPath.AbsolutePath()); + } + } + + return createdFiles; + } + + QSharedPointer SeedListFileTableModel::GetSeedListFileContents(const QModelIndex& index) + { + auto seedFileInfoOutcome = GetSeedFileInfo(index); + if (!seedFileInfoOutcome.IsSuccess()) + { + return QSharedPointer(); + } + + return seedFileInfoOutcome.GetValue()->m_seedListModel; + } + + bool SeedListFileTableModel::SetSeedPlatforms(const QModelIndex& seedFileIndex, const QModelIndex& seedIndex, const AzFramework::PlatformFlags& platforms) + { + AZStd::string key = GetFileKey(seedFileIndex); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + if (m_seedListFileInfoMap.find(key) == m_seedListFileInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find Seed File Info with key ( %s )", key.c_str()); + return false; + } + + SeedListFileInfoPtr seedFileInfo = m_seedListFileInfoMap[key]; + if (!seedFileInfo->m_seedListModel->SetSeedPlatforms(seedIndex, platforms)) + { + // Error has already been thrown + return false; + } + + // Update display so the user knows there are unsaved changes + m_keysWithUnsavedChanges.insert(key); + QModelIndex changedIndex = QAbstractTableModel::index(seedFileIndex.row(), Column::ColumnFileName); + emit dataChanged(changedIndex, changedIndex, { Qt::DisplayRole, Qt::FontRole }); + + return true; + } + + bool SeedListFileTableModel::AddSeed(const QModelIndex& seedFileIndex, const AZStd::string& seedRelativePath, const AzFramework::PlatformFlags& platforms) + { + AZStd::string key = GetFileKey(seedFileIndex); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + if (m_seedListFileInfoMap.find(key) == m_seedListFileInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find Seed File Info with key ( %s )", key.c_str()); + return false; + } + + SeedListFileInfoPtr seedFileInfo = m_seedListFileInfoMap[key]; + if (!seedFileInfo->m_seedListModel->AddSeed(seedRelativePath, platforms)) + { + // Error has already been thrown + return false; + } + + // Update display so the user knows there are unsaved changes + m_keysWithUnsavedChanges.insert(key); + QModelIndex changedIndex = QAbstractTableModel::index(seedFileIndex.row(), Column::ColumnFileName); + emit dataChanged(changedIndex, changedIndex, { Qt::DisplayRole, Qt::FontRole }); + + return true; + } + + bool SeedListFileTableModel::RemoveSeed(const QModelIndex& seedFileIndex, const QModelIndex& seedIndex) + { + AZStd::string key = GetFileKey(seedFileIndex); + if (key.empty()) + { + // Error has already been thrown + return false; + } + + if (m_seedListFileInfoMap.find(key) == m_seedListFileInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find Seed File Info with key ( %s )", key.c_str()); + return false; + } + + SeedListFileInfoPtr seedFileInfo = m_seedListFileInfoMap[key]; + if (!seedFileInfo->m_seedListModel->RemoveSeed(seedIndex)) + { + // Error has already been thrown + return false; + } + + // Update display so the user knows there are unsaved changes + m_keysWithUnsavedChanges.insert(key); + QModelIndex changedIndex = QAbstractTableModel::index(seedFileIndex.row(), Column::ColumnFileName); + emit dataChanged(changedIndex, changedIndex, { Qt::DisplayRole, Qt::FontRole }); + + return true; + } + + bool SeedListFileTableModel::WriteToDisk(const AZStd::string& key) + { + if (key == m_inMemoryDefaultSeedListKey) + { + return true; + } + + auto seedListFileIt = m_seedListFileInfoMap.find(key); + return seedListFileIt != m_seedListFileInfoMap.end() && seedListFileIt->second->SaveSeedFile(); + } + + AZStd::string SeedListFileTableModel::GetFileAbsolutePath(const QModelIndex& index) const + { + auto seedFileInfoOutcome = GetSeedFileInfo(index); + if (!seedFileInfoOutcome.IsSuccess() || seedFileInfoOutcome.GetValue()->m_absolutePath == m_inMemoryDefaultSeedListKey) + { + // Error has already been thrown + return AZStd::string(); + } + return seedFileInfoOutcome.GetValue()->m_absolutePath; + } + + int SeedListFileTableModel::GetFileNameColumnIndex() const + { + return Column::ColumnFileName; + } + + int SeedListFileTableModel::GetTimeStampColumnIndex() const + { + return Column::ColumnFileModificationTime; + } + + int SeedListFileTableModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant SeedListFileTableModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnFileName: + return QString(tr("Seed List File")); + case Column::ColumnProject: + return QString(tr("Project Source")); + case Column::ColumnFileModificationTime: + return QString(tr("Modification Time")); + default: + break; + } + } + + return QVariant(); + } + + QVariant SeedListFileTableModel::data(const QModelIndex& index, int role) const + { + auto seedFileInfoOutcome = GetSeedFileInfo(index); + if (!seedFileInfoOutcome.IsSuccess()) + { + return QVariant(); + } + + int col = index.column(); + bool hasUnsavedChanges = seedFileInfoOutcome.GetValue()->m_seedListModel->HasUnsavedChanges(); + + switch (role) + { + case Qt::DisplayRole: + [[fallthrough]]; + case SortRole: + { + if (col == Column::ColumnFileName) + { + QString displayName = seedFileInfoOutcome.GetValue()->m_fileName; + if (hasUnsavedChanges) + { + displayName.append("*"); + } + return displayName; + } + else if (col == Column::ColumnProject) + { + return seedFileInfoOutcome.GetValue()->m_project; + } + else if (col == Column::ColumnFileModificationTime) + { + if (role == SortRole) + { + return seedFileInfoOutcome.GetValue()->m_fileModificationTime; + } + else + { + return seedFileInfoOutcome.GetValue()->m_fileModificationTime.toString(DateTimeFormat); + } + } + else + { + // Returning an empty QString will ensure the checkboxes do not have any text displayed next to them + return QString(); + } + } + case Qt::FontRole: + { + if (col == Column::ColumnFileName && hasUnsavedChanges) + { + QFont boldFont; + boldFont.setBold(true); + return boldFont; + } + } + case Qt::CheckStateRole: + { + if (col == Column::ColumnCheckBox) + { + if (seedFileInfoOutcome.GetValue()->m_isChecked) + { + return Qt::Checked; + } + else + { + return Qt::Unchecked; + } + } + } + default: + break; + } + + return QVariant(); + } + + bool SeedListFileTableModel::setData(const QModelIndex& index, const QVariant& value, int role) + { + if (role != Qt::CheckStateRole || index.column() != Column::ColumnCheckBox) + { + return false; + } + + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + return false; + } + + auto seedFileInfo = m_seedListFileInfoMap.find(key); + if (seedFileInfo == m_seedListFileInfoMap.end()) + { + return false; + } + + if (value == Qt::CheckState::Unchecked) + { + seedFileInfo->second->m_isChecked = false; + m_checkedSeedListFiles.erase(key); + + if (seedFileInfo->second->m_isDefaultSeedList) + { + m_seedTabWidget->UncheckSelectDefaultSeedListsCheckBox(); + } + } + else + { + seedFileInfo->second->m_isChecked = true; + m_checkedSeedListFiles.insert(key); + } + + m_seedTabWidget->SetGenerateAssetListsButtonEnabled(!m_checkedSeedListFiles.empty()); + + return true; + } + + Qt::ItemFlags SeedListFileTableModel::flags(const QModelIndex& index) const + { + if (index.column() == Column::ColumnCheckBox) + { + return Qt::ItemIsUserCheckable | QAbstractTableModel::flags(index); + } + + return QAbstractTableModel::flags(index); + } + + AZ::Outcome SeedListFileTableModel::GetSeedFileInfo(const QModelIndex& index) const + { + AZStd::string key = GetFileKey(index); + if (key.empty()) + { + // Error has already been thrown + return AZ::Failure(); + } + + if (m_seedListFileInfoMap.find(key) != m_seedListFileInfoMap.end()) + { + return AZ::Success(m_seedListFileInfoMap.at(key)); + } + else + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot find Seed File Info"); + return AZ::Failure(); + } + } + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.h b/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.h new file mode 100644 index 0000000000..3bd8fbd600 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/SeedListFileTableModel.h @@ -0,0 +1,134 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include + +#include +#include + +namespace AssetBundler +{ + class SeedListTableModel; + + class SeedTabWidget; + + struct SeedListFileInfo + { + /** + * Stores information about a Seed List File on disk. + * + * @param absolutePath The absolute path of the Seed List File + * @param fileName The name of the Seed List File. This does not include the ".seed" file extension + * @param project The area of the codebase the Seed List File is from (ex: ProjectName, Engine, Gem) + * @param loadFromFile Set to True if you wish to load an existing Seed List File into memory. Set to False if you are creating a new Seed List File. + */ + SeedListFileInfo( + const AZStd::string& absolutePath, + const QString& fileName, + const QString& project, + bool loadFromFile, + bool isDefaultSeedList = false, + const AZStd::vector& defaultSeeds = AZStd::vector(), + const AzFramework::PlatformFlags& platforms = AzFramework::PlatformFlags::Platform_NONE); + + bool SaveSeedFile(); + + bool HasUnsavedChanges(); + + AZStd::string m_absolutePath; + bool m_isChecked = false; + bool m_isDefaultSeedList = false; + QString m_fileName; + QString m_project; + QDateTime m_fileModificationTime; + + QSharedPointer m_seedListModel; + }; + + using SeedListFileInfoPtr = AZStd::shared_ptr; + /// Stores SeedListFileInfo, using the absolute path (without the drive letter) of the Seed List file as the key + using SeedListFileInfoMap = AZStd::unordered_map; + + class SeedListFileTableModel + : public AssetBundlerAbstractFileTableModel + { + + public: + explicit SeedListFileTableModel(SeedTabWidget* parentSeedTabWidget); + virtual ~SeedListFileTableModel(); + + void AddDefaultSeedsToInMemoryList(const AZStd::vector& defaultSeeds, const char* projectName, const AzFramework::PlatformFlags& platforms); + + AZStd::vector CreateNewFiles(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& platforms, const QString& project) override; + + bool DeleteFile(const QModelIndex& index) override; + + void Reload(const char* fileExtension, const QSet& watchedFolders, const QSet& watchedFiles = QSet(), const AZStd::unordered_map& pathToProjectNameMap = AZStd::unordered_map()) override; + + void LoadFile(const AZStd::string& absoluteFilePath, const AZStd::string& projectName = "", bool isDefaultFile = false) override; + + void SelectDefaultSeedLists(bool setSelected); + + AZStd::vector GenerateAssetLists(const AZStd::string& absoluteFilePath, const AzFramework::PlatformFlags& platforms); + + QSharedPointer GetSeedListFileContents(const QModelIndex& index); + + bool SetSeedPlatforms(const QModelIndex& seedFileIndex, const QModelIndex& seedIndex, const AzFramework::PlatformFlags& platforms); + + bool AddSeed(const QModelIndex& seedFileIndex, const AZStd::string& seedRelativePath, const AzFramework::PlatformFlags& platforms); + + bool RemoveSeed(const QModelIndex& seedFileIndex, const QModelIndex& seedIndex); + + bool WriteToDisk(const AZStd::string& key) override; + + AZStd::string GetFileAbsolutePath(const QModelIndex& index) const override; + + int GetFileNameColumnIndex() const override; + + int GetTimeStampColumnIndex() const override; + + ////////////////////////////////////////////////////////////////////////// + // QAbstractListModel overrides + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::CheckStateRole) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnCheckBox, + ColumnFileName, + ColumnProject, + ColumnFileModificationTime, + Max + }; + + private: + AZ::Outcome GetSeedFileInfo(const QModelIndex& index) const; + + SeedListFileInfoMap m_seedListFileInfoMap; + AZStd::unordered_set m_checkedSeedListFiles; + + AZStd::string m_inMemoryDefaultSeedListKey = "InMemoryDefaultKey"; + SeedListFileInfoPtr m_inMemoryDefaultSeedList; + + SeedTabWidget* m_seedTabWidget = nullptr; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/SeedListTableModel.cpp b/Code/Tools/AssetBundler/source/models/SeedListTableModel.cpp new file mode 100644 index 0000000000..fc04686436 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/SeedListTableModel.cpp @@ -0,0 +1,270 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace AssetBundler +{ + + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // AdditionalSeedInfo + ////////////////////////////////////////////////////////////////////////////////////////////////// + AdditionalSeedInfo::AdditionalSeedInfo(const QString& relativePath, const QString& platformList) + : m_relativePath(relativePath) + , m_platformList(platformList) + { + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // SeedListTableModel + ////////////////////////////////////////////////////////////////////////////////////////////////// + SeedListTableModel::SeedListTableModel(QObject* parent, const AZStd::string& absolutePath, const AZStd::vector& defaultSeeds, const AzFramework::PlatformFlags& platforms) + : QAbstractTableModel(parent) + { + m_seedListManager.reset(new AzToolsFramework::AssetSeedManager()); + + if (absolutePath.empty() && defaultSeeds.empty()) + { + return; + } + + if (!defaultSeeds.empty()) + { + for (const AZStd::string& seed : defaultSeeds) + { + m_seedListManager->AddSeedAssetForValidPlatforms(seed, platforms); + } + + m_isFileOnDisk = false; + } + else + { + m_seedListManager->Load(absolutePath); + } + + AZ::Data::AssetInfo assetInfo; + QString platformList; + for (const auto& seed : m_seedListManager->GetAssetSeedList()) + { + assetInfo = AzToolsFramework::AssetSeedManager::GetAssetInfoById(seed.m_assetId, AzFramework::PlatformHelper::GetPlatformIndicesInterpreted(seed.m_platformFlags)[0], absolutePath); + platformList = QString(m_seedListManager->GetReadablePlatformList(seed).c_str()); + + m_additionalSeedInfoMap[seed.m_assetId].reset(new AdditionalSeedInfo(assetInfo.m_relativePath.c_str(), platformList)); + } + } + + AZ::Outcome SeedListTableModel::GetSeedPlatforms(const QModelIndex& index) const + { + auto seedOutcome = GetSeedInfo(index); + if (!seedOutcome.IsSuccess()) + { + // Error has already been thrown + return AZ::Failure(); + } + + return AZ::Success(seedOutcome.GetValue().m_platformFlags); + } + + bool SeedListTableModel::Save(const AZStd::string& absolutePath) + { + if (!HasUnsavedChanges()) + { + // There are no changes, so there is nothing to save + return true; + } + + SetHasUnsavedChanges(!m_seedListManager->Save(absolutePath)); + return !HasUnsavedChanges(); + } + + bool SeedListTableModel::SetSeedPlatforms(const QModelIndex& index, const AzFramework::PlatformFlags& platforms) + { + auto seedOutcome = GetSeedInfo(index); + if (!seedOutcome.IsSuccess()) + { + // Error has already been thrown + return false; + } + + if (platforms == AzFramework::PlatformFlags::Platform_NONE) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot Edit Platforms: No platforms were selected"); + return false; + } + + auto setPlatformOutcome = m_seedListManager->SetSeedPlatformFlags(index.row(), platforms); + if (!setPlatformOutcome.IsSuccess()) + { + AZ_Error(AssetBundler::AppWindowName, false, setPlatformOutcome.GetError().c_str()); + return false; + } + + // Update the cached display info + auto additionalSeedInfo = m_additionalSeedInfoMap.find(seedOutcome.GetValue().m_assetId); + if (additionalSeedInfo == m_additionalSeedInfoMap.end()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Unable to find additional Seed info"); + return false; + } + additionalSeedInfo->second->m_platformList = QString(AzFramework::PlatformHelper::GetCommaSeparatedPlatformList(platforms).c_str()); + + SetHasUnsavedChanges(true); + + // Update the display + QModelIndex firstChangedIndex = QAbstractTableModel::index(index.row(), Column::ColumnRelativePath); + QModelIndex lastChangedIndex = QAbstractTableModel::index(index.row(), Column::Max - 1); + emit dataChanged(firstChangedIndex, lastChangedIndex, { Qt::DisplayRole }); + + return true; + } + + bool SeedListTableModel::AddSeed(const AZStd::string& seedRelativePath, const AzFramework::PlatformFlags& platforms) + { + AZStd::pair addSeedsResult = m_seedListManager->AddSeedAssetForValidPlatforms(seedRelativePath, platforms); + + if (!addSeedsResult.first.IsValid() || addSeedsResult.second == AzFramework::PlatformFlags::Platform_NONE) + { + // Error has already been thrown + return false; + } + + QString platformList = QString(AzFramework::PlatformHelper::GetCommaSeparatedPlatformList(addSeedsResult.second).c_str()); + + int lastRowIndex = AZStd::max(rowCount() - 1, 0); + beginInsertRows(QModelIndex(), lastRowIndex, lastRowIndex); + + m_additionalSeedInfoMap[addSeedsResult.first].reset(new AdditionalSeedInfo(QString(seedRelativePath.c_str()), platformList)); + + endInsertRows(); + + SetHasUnsavedChanges(true); + return true; + } + + bool SeedListTableModel::RemoveSeed(const QModelIndex& seedIndex) + { + auto seedOutcome = GetSeedInfo(seedIndex); + if (!seedOutcome.IsSuccess()) + { + // Error has already been thrown + return false; + } + + int row = seedIndex.row(); + beginRemoveRows(QModelIndex(), row, row); + m_seedListManager->RemoveSeedAsset(seedOutcome.GetValue().m_assetId, seedOutcome.GetValue().m_platformFlags); + m_additionalSeedInfoMap.erase(seedOutcome.GetValue().m_assetId); + endRemoveRows(); + + SetHasUnsavedChanges(true); + return true; + } + + int SeedListTableModel::rowCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : static_cast(m_additionalSeedInfoMap.size()); + } + + int SeedListTableModel::columnCount(const QModelIndex& parent) const + { + return parent.isValid() ? 0 : Column::Max; + } + + QVariant SeedListTableModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case Column::ColumnRelativePath: + return QString("Seed"); + case Column::ColumnPlatformList: + return QString("Platforms"); + default: + break; + } + } + + return QVariant(); + } + + QVariant SeedListTableModel::data(const QModelIndex& index, int role) const + { + auto additionalSeedInfoOutcome = GetAdditionalSeedInfo(index); + if (!additionalSeedInfoOutcome.IsSuccess()) + { + return QVariant(); + } + + switch (role) + { + case Qt::DisplayRole: + { + if (index.column() == Column::ColumnRelativePath) + { + return additionalSeedInfoOutcome.GetValue()->m_relativePath; + } + else if (index.column() == Column::ColumnPlatformList) + { + return additionalSeedInfoOutcome.GetValue()->m_platformList; + } + } + default: + break; + } + + return QVariant(); + } + + AZ::Outcome SeedListTableModel::GetSeedInfo(const QModelIndex& index) const + { + int row = index.row(); + int col = index.column(); + if (row >= rowCount() || row < 0 || col >= columnCount() || col < 0) + { + AZ_Error(AssetBundler::AppWindowName, false, "Selected index (%i, %i) is out of range", row, col); + return AZ::Failure(); + } + + return AZ::Success(m_seedListManager->GetAssetSeedList().at(row)); + } + + AZ::Outcome SeedListTableModel::GetAdditionalSeedInfo(const QModelIndex& index) const + { + auto seedInfoOutcome = GetSeedInfo(index); + if (!seedInfoOutcome.IsSuccess()) + { + // Error has already been thrown + return AZ::Failure(); + } + + auto additionalSeedInfoIt = m_additionalSeedInfoMap.find(seedInfoOutcome.GetValue().m_assetId); + if (additionalSeedInfoIt == m_additionalSeedInfoMap.end()) + { + return AZ::Failure(); + } + + return AZ::Success(additionalSeedInfoIt->second); + } + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/models/SeedListTableModel.h b/Code/Tools/AssetBundler/source/models/SeedListTableModel.h new file mode 100644 index 0000000000..b8cbe95201 --- /dev/null +++ b/Code/Tools/AssetBundler/source/models/SeedListTableModel.h @@ -0,0 +1,84 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include + +#include + +namespace AssetBundler +{ + struct AdditionalSeedInfo + { + AdditionalSeedInfo(const QString& relativePath, const QString& platformList); + + QString m_relativePath; + QString m_platformList; + }; + + using AdditionalSeedInfoPtr = AZStd::shared_ptr; + using AdditionalSeedInfoMap = AZStd::unordered_map; + + class SeedListTableModel + : public QAbstractTableModel + { + public: + explicit SeedListTableModel(QObject* parent = nullptr, const AZStd::string& absolutePath = AZStd::string(), const AZStd::vector& defaultSeeds = AZStd::vector(), const AzFramework::PlatformFlags& platforms = AzFramework::PlatformFlags::Platform_NONE); + virtual ~SeedListTableModel() {} + + AZStd::shared_ptr GetSeedListManager() { return m_seedListManager; } + + bool HasUnsavedChanges() { return m_hasUnsavedChanges && m_isFileOnDisk; } + + void SetHasUnsavedChanges(bool hasUnsavedChanges) { m_hasUnsavedChanges = hasUnsavedChanges; } + + bool Save(const AZStd::string& absolutePath); + + AZ::Outcome GetSeedPlatforms(const QModelIndex& index) const; + + bool SetSeedPlatforms(const QModelIndex& index, const AzFramework::PlatformFlags& platforms); + + bool AddSeed(const AZStd::string& seedRelativePath, const AzFramework::PlatformFlags& platforms); + + bool RemoveSeed(const QModelIndex& seedIndex); + + ////////////////////////////////////////////////////////////////////////// + // QAbstractListModel overrides + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex& index, int role) const override; + ////////////////////////////////////////////////////////////////////////// + + enum Column + { + ColumnRelativePath, + ColumnPlatformList, + Max + }; + + private: + AZ::Outcome GetSeedInfo(const QModelIndex& index) const; + + AZ::Outcome GetAdditionalSeedInfo(const QModelIndex& index) const; + + AZStd::shared_ptr m_seedListManager; + AdditionalSeedInfoMap m_additionalSeedInfoMap; + + bool m_hasUnsavedChanges = false; + bool m_isFileOnDisk = true; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/AddSeedDialog.cpp b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.cpp new file mode 100644 index 0000000000..be156efde2 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.cpp @@ -0,0 +1,96 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +#include +#include + +const char QtRelativePathPrefix[] = "../"; + +namespace AssetBundler +{ + AddSeedDialog::AddSeedDialog(QWidget* parent, const AzFramework::PlatformFlags& enabledPlatforms, const AZStd::string& platformSpecificCachePath) + : QDialog(parent) + , m_platformSpecificCachePath(platformSpecificCachePath.c_str()) + { + m_ui.reset(new Ui::AddSeedDialog); + m_ui->setupUi(this); + + // Set up Browse File button + m_ui->fileNameLineEdit->setReadOnly(true); + connect(m_ui->browseFileButton, &QPushButton::clicked, this, &AddSeedDialog::OnBrowseFileButtonPressed); + + // Set up Platform selection + m_ui->platformSelectionWidget->Init(enabledPlatforms); + connect(m_ui->platformSelectionWidget, &PlatformSelectionWidget::PlatformsSelected, this, &AddSeedDialog::OnPlatformSelectionChanged); + + // Set up Cancel and Create New File buttons + m_ui->addSeedButton->setEnabled(false); + connect(m_ui->cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(m_ui->addSeedButton, &QPushButton::clicked, this, &QDialog::accept); + } + + AZStd::string AddSeedDialog::GetFileName() + { + return m_fileName; + } + + AzFramework::PlatformFlags AddSeedDialog::GetPlatformFlags() + { + return m_ui->platformSelectionWidget->GetSelectedPlatforms(); + } + + void AddSeedDialog::OnBrowseFileButtonPressed() + { + QString seedAbsolutePath = QFileDialog::getOpenFileName(this, tr("Add New Seed"), m_platformSpecificCachePath); + m_fileNameIsValid = !seedAbsolutePath.isEmpty(); + if (!m_fileNameIsValid) + { + // The user canceled out of the window, do not update anything + return; + } + + // Make sure the path is relative to the platform-specific cache + QDir platformSpecificCacheDir(m_platformSpecificCachePath); + QString seedRelativePath = platformSpecificCacheDir.relativeFilePath(seedAbsolutePath); + + // trim off the "../" from the relative path + FormatRelativePathString(seedRelativePath); + m_fileName = seedRelativePath.toUtf8().data(); + + // Update the ui + m_ui->fileNameLineEdit->setText(seedRelativePath); + m_ui->addSeedButton->setEnabled(m_platformIsValid && m_fileNameIsValid); + } + + void AddSeedDialog::OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms) + { + // Hide the "Create File" button if no platforms are selected + m_platformIsValid = selectedPlatforms != AzFramework::PlatformFlags::Platform_NONE; + m_ui->addSeedButton->setEnabled(m_platformIsValid && m_fileNameIsValid); + } + + void AddSeedDialog::FormatRelativePathString(QString& relativePath) + { + if (relativePath.startsWith(QtRelativePathPrefix)) + { + relativePath.remove(0, static_cast(strlen(QtRelativePathPrefix))); + } + } + +} //namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/AddSeedDialog.h b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.h new file mode 100644 index 0000000000..fed6b031d1 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.h @@ -0,0 +1,58 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#endif + +namespace Ui +{ + class AddSeedDialog; +} + +namespace AssetBundler +{ + class GUIApplicationManager; + + class AddSeedDialog + : public QDialog + { + Q_OBJECT + + public: + explicit AddSeedDialog(QWidget* parent, const AzFramework::PlatformFlags& enabledPlatforms, const AZStd::string& platformSpecificCachePath); + virtual ~AddSeedDialog() {} + + AZStd::string GetFileName(); + AzFramework::PlatformFlags GetPlatformFlags(); + + private: + void OnBrowseFileButtonPressed(); + void OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms); + + void FormatRelativePathString(QString& relativePath); + + QSharedPointer m_ui; + QString m_platformSpecificCachePath; + bool m_isAddSeedDialog = false; + + AZStd::string m_fileName; + + bool m_fileNameIsValid = false; + bool m_platformIsValid = false; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/AddSeedDialog.ui b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.ui new file mode 100644 index 0000000000..e3c0f471aa --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AddSeedDialog.ui @@ -0,0 +1,185 @@ + + + AddSeedDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 450 + 108 + + + + + 0 + 0 + + + + + 450 + 0 + + + + Add Seed Asset + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Select Seed Asset: + + + + + + + + + true + + + + + + + Browse... + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + + + + + + + + + 10 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + false + + + + + + + Add Seed + + + true + + + + + + + + + + AssetBundler::PlatformSelectionWidget + QWidget +
source/ui/PlatformSelectionWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.cpp b/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.cpp new file mode 100644 index 0000000000..2719944af4 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.cpp @@ -0,0 +1,370 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AssetBundler +{ + const char AssetBundlerCommonSettingsFile[] = "AssetBundlerCommonSettings.json"; + const char AssetBundlerUserSettingsFile[] = "AssetBundlerUserSettings.json"; + + const char ScanPathsKey[] = "ScanPaths"; + + const QString AssetBundlingFileTypes[] = + { + "SeedLists", + "AssetLists", + "BundleSettings", + "Bundles", + "Rules" + }; + + const int MarginSize = 10; + + AssetBundlerTabWidget::AssetBundlerTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager) + : QWidget(parent) + , m_guiApplicationManager(guiApplicationManager) + { + connect(m_guiApplicationManager, &GUIApplicationManager::UpdateTab, this, &AssetBundlerTabWidget::OnUpdateTab); + connect(m_guiApplicationManager, &GUIApplicationManager::UpdateFiles, this, &AssetBundlerTabWidget::OnUpdateFiles); + + QAction* deleteFileAction = new QAction(tr("Delete"), this); + addAction(deleteFileAction); + deleteFileAction->setShortcut(QKeySequence::Delete); + connect(deleteFileAction, &QAction::triggered, this, &AssetBundlerTabWidget::OnDeleteSelectedFileRequested); + } + + AssetBundlerTabWidget::~AssetBundlerTabWidget() + { + m_guiApplicationManager = nullptr; + } + + void AssetBundlerTabWidget::Activate() + { + SetActiveProjectLabel(tr("Active Project: %1").arg(m_guiApplicationManager->GetCurrentProjectName().c_str())); + + SetupContextMenu(); + Reload(); + + connect(GetFileTableView()->header(), &QHeaderView::sortIndicatorChanged, m_fileTableFilterModel.get(), &AssetBundlerFileTableFilterModel::sort); + GetFileTableView()->header()->setSortIndicatorShown(true); + // Setting this in descending order will ensure the most recent files are at the top + GetFileTableView()->header()->setSortIndicator(GetFileTableModel()->GetTimeStampColumnIndex(), Qt::DescendingOrder); + GetFileTableView()->setSortingEnabled(true); + } + + void AssetBundlerTabWidget::InitAssetBundlerSettings(const char* currentProjectFolderPath) + { + AZStd::string commonSettingsPath = GetAssetBundlerCommonSettingsFile(currentProjectFolderPath); + if (!AZ::IO::FileIOBase::GetInstance()->Exists(commonSettingsPath.c_str())) + { + CreateEmptyAssetBundlerSettings(commonSettingsPath); + } + + AZStd::string userSettingsPath = GetAssetBundlerUserSettingsFile(currentProjectFolderPath); + if (!AZ::IO::FileIOBase::GetInstance()->Exists(userSettingsPath.c_str())) + { + CreateEmptyAssetBundlerSettings(userSettingsPath); + } + } + + void AssetBundlerTabWidget::OnFileTableContextMenuRequested(const QPoint& pos) + { + AZStd::string selectedFileAbsolutePath(GetFileTableModel()->GetFileAbsolutePath(GetSelectedFileTableIndex())); + + QMenu* contextMenu = new QMenu(this); + contextMenu->setToolTipsVisible(true); + + bool arePathOperationsEnabled = !selectedFileAbsolutePath.empty(); + QString emptyPathToolTip(tr("This file is not present on-disk.")); + + QAction* action = contextMenu->addAction(AzQtComponents::fileBrowserActionName(), [=]() + { + AzQtComponents::ShowFileOnDesktop(selectedFileAbsolutePath.c_str()); + }); + if (arePathOperationsEnabled) + { + action->setToolTip(tr("Shows the location of this file on your computer")); + } + else + { + action->setToolTip(emptyPathToolTip); + action->setEnabled(false); + } + + action = contextMenu->addAction(tr("Copy Path to Clipboard"), [=]() + { + QApplication::clipboard()->setText(QString(selectedFileAbsolutePath.c_str())); + }); + if (arePathOperationsEnabled) + { + action->setToolTip(tr("Copies the absolute path of this file to your Clipboard")); + } + else + { + action->setToolTip(emptyPathToolTip); + action->setEnabled(false); + } + + contextMenu->addSeparator(); + + // We can't use the same Delete action as the constructor because we need to modify a lot of values + action = new QAction(tr("Delete"), this); + action->setShortcutContext(Qt::WidgetWithChildrenShortcut); + action->setShortcut(QKeySequence::Delete); + connect(action, &QAction::triggered, this, &AssetBundlerTabWidget::OnDeleteSelectedFileRequested); + if (arePathOperationsEnabled) + { + action->setToolTip(tr("Deletes the selected file from disk.")); + } + else + { + action->setToolTip(emptyPathToolTip); + action->setEnabled(false); + } + contextMenu->addAction(action); + + contextMenu->exec(GetFileTableView()->mapToGlobal(pos)); + delete contextMenu; + } + + void AssetBundlerTabWidget::OnDeleteSelectedFileRequested() + { + AZStd::string selectedFileAbsolutePath(GetFileTableModel()->GetFileAbsolutePath(GetSelectedFileTableIndex())); + if (selectedFileAbsolutePath.empty()) + { + return; + } + + QString messageBoxText = QString(tr("Are you sure you would like to delete %1? \n\nThis will permanently delete the file.")).arg(QString(selectedFileAbsolutePath.c_str())); + + QMessageBox::StandardButton confirmDeleteFileResult = QMessageBox::question(this, QString(tr("Delete %1")).arg(GetFileTypeDisplayName()), messageBoxText); + if (confirmDeleteFileResult != QMessageBox::StandardButton::Yes) + { + // User canceled out of the confirmation dialog + return; + } + + if (GetFileTableModel()->DeleteFile(GetSelectedFileTableIndex())) + { + RemoveScanPathFromAssetBundlerSettings(GetFileType(), selectedFileAbsolutePath.c_str()); + } + } + + void AssetBundlerTabWidget::ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType fileType) + { + AZStd::string currentProjectFolderPath = m_guiApplicationManager->GetCurrentProjectFolder(); + ReadAssetBundlerSettings(GetAssetBundlerUserSettingsFile(currentProjectFolderPath.c_str()), fileType); + ReadAssetBundlerSettings(GetAssetBundlerCommonSettingsFile(currentProjectFolderPath.c_str()), fileType); + } + + void AssetBundlerTabWidget::AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, AZStd::string filePath) + { + AZStd::string defaultFolderPath; + switch (fileType) + { + case AssetBundlingFileType::SeedListFileType: + defaultFolderPath = m_guiApplicationManager->GetSeedListsFolder(); + break; + case AssetBundlingFileType::AssetListFileType: + defaultFolderPath = m_guiApplicationManager->GetAssetListsFolder(); + break; + case AssetBundlingFileType::RulesFileType: + defaultFolderPath = m_guiApplicationManager->GetRulesFolder(); + break; + case AssetBundlingFileType::BundleSettingsFileType: + defaultFolderPath = m_guiApplicationManager->GetBundleSettingsFolder(); + break; + case AssetBundlingFileType::BundleFileType: + defaultFolderPath = m_guiApplicationManager->GetBundlesFolder(); + break; + default: + AZ_Warning(AssetBundler::AppWindowName, false, "No default folder is defined for AssetBundlingFileType ( %i ).", static_cast(fileType)); + break; + } + + AzFramework::StringFunc::Path::Normalize(filePath); + AzFramework::StringFunc::Path::Normalize(defaultFolderPath); + + if (AzFramework::StringFunc::StartsWith(filePath, defaultFolderPath)) + { + // file is already in a watched folder, no need to add it to the settings file + return; + } + + AddScanPathToAssetBundlerSettings(fileType, QString(filePath.c_str())); + } + + void AssetBundlerTabWidget::AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath) + { + AZStd::string assetBundlerSettingsFileAbsolutePath = GetAssetBundlerUserSettingsFile(m_guiApplicationManager->GetCurrentProjectFolder().c_str()); + QJsonObject assetBundlerSettings = AssetBundler::ReadJson(assetBundlerSettingsFileAbsolutePath); + QJsonObject scanPathsSettings = assetBundlerSettings[ScanPathsKey].toObject(); + QJsonArray scanPaths = scanPathsSettings[AssetBundlingFileTypes[fileType]].toArray(); + + QFileInfo inputFileInfo(filePath); + + for (const QJsonValue scanPath : m_watchedFiles + m_watchedFolders) + { + AZStd::string scanFilePathStr = scanPath.toString().toUtf8().data(); + AzFramework::StringFunc::Path::ConstructFull(GetCachedEngineRoot().c_str(), scanFilePathStr.c_str(), scanFilePathStr); + + // Check whether the file has already been watched + // Get absolute file paths via QFileInfo to keep consistency in the letter case + if (inputFileInfo.absoluteFilePath().startsWith( + QFileInfo(scanFilePathStr.c_str()).absoluteFilePath())) + { + return; + } + } + + scanPaths.push_back(filePath); + scanPathsSettings[AssetBundlingFileTypes[fileType]] = scanPaths; + assetBundlerSettings[ScanPathsKey] = scanPathsSettings; + AssetBundler::SaveJson(assetBundlerSettingsFileAbsolutePath, assetBundlerSettings); + + m_watchedFiles.insert(filePath); + m_guiApplicationManager->AddWatchedPath(filePath); + } + + void AssetBundlerTabWidget::RemoveScanPathFromAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath) + { + AZStd::string assetBundlerSettingsFileAbsolutePath = GetAssetBundlerUserSettingsFile(m_guiApplicationManager->GetCurrentProjectFolder().c_str()); + QJsonObject assetBundlerSettings = AssetBundler::ReadJson(assetBundlerSettingsFileAbsolutePath); + QJsonObject scanPathsSettings = assetBundlerSettings[ScanPathsKey].toObject(); + QJsonArray scanPaths = scanPathsSettings[AssetBundlingFileTypes[fileType]].toArray(); + + for (auto itr = scanPaths.begin(); itr != scanPaths.end(); ++itr) + { + QJsonValueRef scanPathValueRef = *itr; + AZStd::string scanPath = scanPathValueRef.toString().toUtf8().data(); + AzFramework::StringFunc::Path::ConstructFull(GetCachedEngineRoot().c_str(), scanPath.c_str(), scanPath); + + // Check whether the file is being watched + // Get absolute file paths via QFileInfo to keep consistency in the letter case + if (QFileInfo(filePath).absoluteFilePath() == QFileInfo(scanPath.c_str()).absoluteFilePath()) + { + itr = scanPaths.erase(itr); + break; + } + } + + scanPathsSettings[AssetBundlingFileTypes[fileType]] = scanPaths; + assetBundlerSettings[ScanPathsKey] = scanPathsSettings; + AssetBundler::SaveJson(assetBundlerSettingsFileAbsolutePath, assetBundlerSettings); + + m_guiApplicationManager->RemoveWatchedPath(filePath); + } + + void AssetBundlerTabWidget::OnUpdateTab(const AZStd::string& path) + { + if (m_watchedFolders.contains(path.c_str()) || + m_watchedFiles.contains(path.c_str())) + { + Reload(); + } + } + + void AssetBundlerTabWidget::OnUpdateFiles(AssetBundlingFileType fileType, const AZStd::vector& absoluteFilePaths) + { + if (fileType == GetFileType()) + { + GetFileTableModel()->ReloadFiles(absoluteFilePaths, m_filePathToGemNameMap); + m_fileTableFilterModel->sort(m_fileTableFilterModel->sortColumn(), m_fileTableFilterModel->sortOrder()); + FileSelectionChanged(); + } + } + + void AssetBundlerTabWidget::SetupContextMenu() + { + GetFileTableView()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(GetFileTableView(), &QTreeView::customContextMenuRequested, this, &AssetBundlerTabWidget::OnFileTableContextMenuRequested); + } + + void AssetBundlerTabWidget::ReadAssetBundlerSettings(const AZStd::string& filePath, AssetBundlingFileType fileType) + { + // Read the config file which contains the customized scan paths information + AZStd::vector assetBundlerSettingsFileList; + QJsonObject assetBundlerSettings = AssetBundler::ReadJson(filePath); + QJsonObject scanPaths = assetBundlerSettings.find(ScanPathsKey).value().toObject(); + + for (const QJsonValue scanPath : scanPaths[AssetBundlingFileTypes[fileType]].toArray()) + { + AZStd::string absoluteScanPath = scanPath.toString().toUtf8().data(); + AZStd::replace(absoluteScanPath.begin(), absoluteScanPath.end(), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR); + + if (AzFramework::StringFunc::Path::IsRelative(absoluteScanPath.c_str())) + { + AzFramework::StringFunc::Path::ConstructFull(GetCachedEngineRoot().c_str(), absoluteScanPath.c_str(), absoluteScanPath); + } + + if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(absoluteScanPath.c_str())) + { + // The path specified in the config file is a directory + m_watchedFolders.insert(absoluteScanPath.c_str()); + } + else if (AZ::IO::FileIOBase::GetInstance()->Exists(absoluteScanPath.c_str())) + { + // The path specified in the config file is a file + m_watchedFiles.insert(absoluteScanPath.c_str()); + } + } + } + + AZStd::string AssetBundlerTabWidget::GetAssetBundlerUserSettingsFile(const char* currentProjectFolderPath) + { + AZStd::string absoluteFilePath; + AzFramework::StringFunc::Path::ConstructFull(currentProjectFolderPath, AssetBundlerUserSettingsFile, absoluteFilePath); + return absoluteFilePath; + } + + AZStd::string AssetBundlerTabWidget::GetAssetBundlerCommonSettingsFile(const char* currentProjectFolderPath) + { + AZStd::string absoluteFilePath; + AzFramework::StringFunc::Path::ConstructFull(currentProjectFolderPath, AssetBundlerCommonSettingsFile, absoluteFilePath); + return absoluteFilePath; + } + + void AssetBundlerTabWidget::CreateEmptyAssetBundlerSettings(const AZStd::string& filePath) + { + QJsonObject assetBundlerSettings; + QJsonObject scanPathSettings; + + for (const auto& fileType : AssetBundlingFileTypes) + { + scanPathSettings.insert(fileType, QJsonArray()); + } + + assetBundlerSettings.insert(ScanPathsKey, scanPathSettings); + + AssetBundler::SaveJson(filePath, assetBundlerSettings); + } + +} // namespace AssetBundler +#include diff --git a/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.h b/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.h new file mode 100644 index 0000000000..edef5c0437 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.h @@ -0,0 +1,118 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#endif + +namespace AssetBundler +{ + // Keys for the corresponding scan path lists in the asset bundling settings file + extern const QString AssetBundlingFileTypes[]; + + extern const int MarginSize; + + class AssetBundlerTabWidget + : public QWidget + { + Q_OBJECT + + public: + explicit AssetBundlerTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager); + virtual ~AssetBundlerTabWidget(); + + void Activate(); + + virtual QString GetTabTitle() = 0; + + virtual QString GetFileTypeDisplayName() = 0; + + virtual AssetBundlingFileType GetFileType() = 0; + + virtual bool HasUnsavedChanges() = 0; + + //! Reload all the files on display. + virtual void Reload() = 0; + + //! Saves the selected file to disk. + //! @return true on successful save, or throws an error message and returns false on a failed save. + virtual bool SaveCurrentSelection() = 0; + + //! Saves all modified files to disk. + //! @return true on successful save, or throws an error message and returns false on a failed save. + virtual bool SaveAll() = 0; + + //! Set watched folders and files for the model + virtual void SetModelDataSource() = 0; + + virtual AzQtComponents::TableView* GetFileTableView() = 0; + + virtual QModelIndex GetSelectedFileTableIndex() = 0; + + virtual AssetBundlerAbstractFileTableModel* GetFileTableModel() = 0; + + virtual void SetActiveProjectLabel(const QString& labelText) = 0; + + virtual void ApplyConfig() = 0; + + virtual void FileSelectionChanged(const QItemSelection& /*selected*/ = QItemSelection(), const QItemSelection& /*deselected*/ = QItemSelection()) = 0; + + static void InitAssetBundlerSettings(const char* currentProjectFolderPath); + + void AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, AZStd::string filePath); + + GUIApplicationManager* GetGUIApplicationManager() { return m_guiApplicationManager; } + + protected: + void OnFileTableContextMenuRequested(const QPoint& pos); + //! Asks the user to confirm they wish to permanently delete the selected file, then removes it from disk and memory. + void OnDeleteSelectedFileRequested(); + bool ConfirmFileDeleteDialog(const QString& absoluteFilePath); + void ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType fileType); + void RemoveScanPathFromAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath); + + GUIApplicationManager* m_guiApplicationManager = nullptr; + QScopedPointer m_fileTableFilterModel; + + QSet m_watchedFolders; + QSet m_watchedFiles; + AZStd::unordered_map m_filePathToGemNameMap; + + private slots: + void OnUpdateTab(const AZStd::string& path); + void OnUpdateFiles(AssetBundlingFileType fileType, const AZStd::vector& absoluteFilePaths); + + private: + void SetupContextMenu(); + void AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath); + void ReadAssetBundlerSettings(const AZStd::string& filePath, AssetBundlingFileType fileType); + + static AZStd::string GetAssetBundlerUserSettingsFile(const char* currentProjectFolderPath); + static AZStd::string GetAssetBundlerCommonSettingsFile(const char* currentProjectFolderPath); + static void CreateEmptyAssetBundlerSettings(const AZStd::string& filePath); + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.cpp b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.cpp new file mode 100644 index 0000000000..339dc080fa --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.cpp @@ -0,0 +1,182 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace AssetBundler +{ + AssetListTabWidget::AssetListTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager) + : AssetBundlerTabWidget(parent, guiApplicationManager) + , m_fileTableModel(new AssetListFileTableModel()) + , m_assetListContentsModel(new AssetListTableModel()) + { + m_ui.reset(new Ui::AssetListTabWidget); + m_ui->setupUi(this); + + m_ui->mainVerticalLayout->setContentsMargins(10, 10, 10, 10); + + // File view of all Asset List Files + m_fileTableFilterModel.reset(new AssetBundlerFileTableFilterModel(this, m_fileTableModel->GetFileNameColumnIndex(), m_fileTableModel->GetTimeStampColumnIndex())); + + m_fileTableFilterModel->setSourceModel(m_fileTableModel.data()); + m_ui->assetListsTable->setModel(m_fileTableFilterModel.data()); + connect(m_ui->fileFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_fileTableFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + connect(m_ui->assetListsTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AssetListTabWidget::FileSelectionChanged); + + m_ui->fileTableHeaderLayout->setContentsMargins(0, 0, 0, 0); + m_ui->fileTableVerticalLayout->setContentsMargins(0, 0, 0, 0); + m_ui->assetListsTable->setIndentation(0); + + // Generate Bundle Button + m_ui->generateBundleButton->setDefault(true); + m_ui->generateBundleButton->setEnabled(false); + connect(m_ui->generateBundleButton, &QPushButton::clicked, this, &AssetListTabWidget::OnGenerateBundleButtonPressed); + + // Absolute path of selected Asset List file + m_ui->assetListFileAbsolutePathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + + // Table that displays the contents of an Asset List File + m_assetListContentsFilterModel.reset(new AssetBundlerFileTableFilterModel(this, AssetListTableModel::Column::ColumnAssetName)); + + m_assetListContentsFilterModel->setSourceModel(m_assetListContentsModel.data()); + m_ui->assetListContentsTable->setModel(m_assetListContentsFilterModel.data()); + connect(m_ui->assetListContentsFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_assetListContentsFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + + m_ui->fileContentsHeaderLayout->setContentsMargins(0, 0, 0, 0); + m_ui->fileContentsVerticalLayout->setContentsMargins(0, 0, 0, 0); + m_ui->assetListContentsTable->setIndentation(0); + + SetModelDataSource(); + } + + void AssetListTabWidget::SetModelDataSource() + { + // Remove the current watched folders and files + m_guiApplicationManager->RemoveWatchedPaths(m_watchedFolders + m_watchedFiles); + + // Set the new watched folder for the model + m_watchedFolders.clear(); + m_watchedFiles.clear(); + m_watchedFolders.insert(m_guiApplicationManager->GetAssetListsFolder().c_str()); + ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType::AssetListFileType); + + m_guiApplicationManager->AddWatchedPaths(m_watchedFolders + m_watchedFiles); + } + + AzQtComponents::TableView* AssetListTabWidget::GetFileTableView() + { + return m_ui->assetListsTable; + } + + QModelIndex AssetListTabWidget::GetSelectedFileTableIndex() + { + return m_selectedFileTableIndex; + } + + AssetBundlerAbstractFileTableModel* AssetListTabWidget::GetFileTableModel() + { + return m_fileTableModel.get(); + } + + void AssetListTabWidget::SetActiveProjectLabel(const QString& labelText) + { + m_ui->activeProjectLabel->setText(labelText); + } + + void AssetListTabWidget::ApplyConfig() + { + const GUIApplicationManager::Config& config = m_guiApplicationManager->GetConfig(); + + m_ui->fileTableFrame->setFixedWidth(config.fileTableWidth); + + m_ui->assetListsTable->header()->resizeSection(AssetListFileTableModel::Column::ColumnFileName, config.assetListFileNameColumnWidth); + m_ui->assetListsTable->header()->resizeSection(AssetListFileTableModel::Column::ColumnPlatform, config.assetListPlatformColumnWidth); + + m_ui->assetListContentsFilteredSearchWidget->setFixedWidth(config.fileTableWidth); + + m_ui->assetListContentsTable->header()->resizeSection(AssetListTableModel::Column::ColumnAssetName, config.productAssetNameColumnWidth); + m_ui->assetListContentsTable->header()->resizeSection(AssetListTableModel::Column::ColumnRelativePath, config.productAssetRelativePathColumnWidth); + } + + + void AssetListTabWidget::Reload() + { + // Reload all the asset list files + m_fileTableModel->Reload(AzToolsFramework::AssetSeedManager::GetAssetListFileExtension(), m_watchedFolders, m_watchedFiles); + + // Update the selected row + FileSelectionChanged(); + } + + void AssetListTabWidget::FileSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) + { + AZ_UNUSED(selected); + AZ_UNUSED(deselected); + + if (m_ui->assetListsTable->selectionModel()->selectedRows().size() == 0) + { + // Set selected index to an invalid value + m_selectedFileTableIndex = QModelIndex(); + m_ui->assetListFileAbsolutePathLabel->setText(""); + m_assetListContentsModel.reset(new AssetListTableModel()); + m_assetListContentsFilterModel->setSourceModel(m_assetListContentsModel.data()); + m_ui->generateBundleButton->setEnabled(false); + + return; + } + + m_selectedFileTableIndex = m_fileTableFilterModel->mapToSource(m_ui->assetListsTable->selectionModel()->currentIndex()); + + m_ui->assetListFileAbsolutePathLabel->setText(QString(m_fileTableModel->GetFileAbsolutePath(m_selectedFileTableIndex).c_str())); + + m_assetListContentsModel = m_fileTableModel->GetAssetListFileContents(m_selectedFileTableIndex); + m_assetListContentsFilterModel->setSourceModel(m_assetListContentsModel.data()); + + m_ui->generateBundleButton->setEnabled(true); + } + + void AssetListTabWidget::OnGenerateBundleButtonPressed() + { + GenerateBundlesModal generateBundlesModal( + this, + m_fileTableModel->GetFileAbsolutePath(m_selectedFileTableIndex), + m_guiApplicationManager->GetBundlesFolder(), + m_guiApplicationManager->GetBundleSettingsFolder(), + this); + generateBundlesModal.exec(); + } +} //namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.h b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.h new file mode 100644 index 0000000000..890f45a0c9 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.h @@ -0,0 +1,82 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#endif + +namespace Ui +{ + class AssetListTabWidget; +} + +class QFileSystemModel; +class QStringListModel; + +namespace AssetBundler +{ + class GUIApplicationManager; + + class AssetListFileTableModel; + class AssetListTableModel; + + class AssetListTabWidget + : public AssetBundlerTabWidget + { + Q_OBJECT + + public: + explicit AssetListTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager); + virtual ~AssetListTabWidget() {} + + ////////////////////////////////////////////////////////////////////////// + // AssetBundlerTabWidget overrides + QString GetTabTitle() override { return tr("Asset Lists"); } + QString GetFileTypeDisplayName() override { return tr("Asset List file"); } + AssetBundlingFileType GetFileType() override { return AssetBundlingFileType::AssetListFileType; } + bool HasUnsavedChanges() override { return false; }; + void Reload() override; + bool SaveCurrentSelection() override { return true; }; + bool SaveAll() override { return true; }; + void SetModelDataSource() override; + AzQtComponents::TableView* GetFileTableView() override; + QModelIndex GetSelectedFileTableIndex() override; + AssetBundlerAbstractFileTableModel* GetFileTableModel() override; + void SetActiveProjectLabel(const QString& labelText) override; + void ApplyConfig() override; + void FileSelectionChanged(const QItemSelection& /*selected*/ = QItemSelection(), const QItemSelection& /*deselected*/ = QItemSelection()) override; + ////////////////////////////////////////////////////////////////////////// + + private: + void OnGenerateBundleButtonPressed(); + + QSharedPointer m_ui; + + QSharedPointer m_fileTableModel; + QModelIndex m_selectedFileTableIndex; + + QSharedPointer m_assetListContentsFilterModel; + QSharedPointer m_assetListContentsModel; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.ui b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.ui new file mode 100644 index 0000000000..a5bdea227b --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/AssetListTabWidget.ui @@ -0,0 +1,168 @@ + + + AssetListTabWidget + + + + 0 + 0 + 813 + 673 + + + + Asset List Tab Widget + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate Bundle + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Asset List Files + + + + + + + + + + Filter by file name + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Asset List + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Filter by file name + + + + + + + + + + + + + + + + AzQtComponents::TableView + QTreeView +
AzQtComponents/Components/Widgets/TableView.h
+ 1 +
+ + AzQtComponents::FilteredSearchWidget + QTreeView +
AzQtComponents/Components/FilteredSearchWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.cpp b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.cpp new file mode 100644 index 0000000000..b670fd0ebd --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.cpp @@ -0,0 +1,159 @@ +/* +* All or portions of this file Copyright(c) Amazon.com, Inc. or its affiliates or +*its licensors. +* +* For complete copyrightand license terms please see the LICENSE at the root of this +* distribution(the "License").All use of this software is governed by the License, +* or , if provided, by the license below or the license accompanying this file.Do not +*remove or modify any license notices.This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include +#include + +#include +#include + +#include + +#include +#include + +const double BytesToMegabytes = 1024.0 * 1024.0; + +namespace AssetBundler +{ + + BundleListTabWidget::BundleListTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager) + : AssetBundlerTabWidget(parent, guiApplicationManager) + { + m_ui.reset(new Ui::BundleListTabWidget); + m_ui->setupUi(this); + + m_ui->mainVerticalLayout->setContentsMargins(MarginSize, MarginSize, MarginSize, MarginSize); + + m_fileTableModel.reset(new BundleFileListModel); + m_fileTableFilterModel.reset(new AssetBundlerFileTableFilterModel(this, m_fileTableModel->GetFileNameColumnIndex(), m_fileTableModel->GetTimeStampColumnIndex())); + + m_fileTableFilterModel->setSourceModel(m_fileTableModel.data()); + m_ui->fileTableView->setModel(m_fileTableFilterModel.data()); + connect(m_ui->fileFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_fileTableFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + connect(m_ui->fileTableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &BundleListTabWidget::FileSelectionChanged); + + m_ui->fileTableView->setIndentation(0); + + m_relatedBundlesListModel.reset(new QStringListModel); + m_ui->relatedBundlesListView->setModel(m_relatedBundlesListModel.data()); + + m_ui->bundleFileContentsVerticalLayout->setContentsMargins(MarginSize, MarginSize, MarginSize, MarginSize); + + SetModelDataSource(); + } + + void BundleListTabWidget::Reload() + { + // The act of cracking open paks kicks off a DirectoryChanged event. We need to temporarily remove the Bundles + // directory from our watched paths to prevent an infinite loop of events. + m_guiApplicationManager->RemoveWatchedPaths(m_watchedFolders); + + // Reload all the bundle files + m_fileTableModel->Reload(AzToolsFramework::AssetBundleSettings::GetBundleFileExtension(), m_watchedFolders, m_watchedFiles); + + // Update the selected row + FileSelectionChanged(); + + // Start recieving DirectoryChanged events for these folders again + m_guiApplicationManager->AddWatchedPaths(m_watchedFolders); + } + + void BundleListTabWidget::SetModelDataSource() + { + // Remove the current watched folders and files + m_guiApplicationManager->RemoveWatchedPaths(m_watchedFolders + m_watchedFiles); + + // Set the new watched folder for the model + m_watchedFolders.clear(); + m_watchedFiles.clear(); + m_watchedFolders.insert(m_guiApplicationManager->GetBundlesFolder().c_str()); + ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType::BundleFileType); + + m_guiApplicationManager->AddWatchedPaths(m_watchedFolders + m_watchedFiles); + } + + AzQtComponents::TableView* BundleListTabWidget::GetFileTableView() + { + return m_ui->fileTableView; + } + + QModelIndex BundleListTabWidget::GetSelectedFileTableIndex() + { + return m_selectedFileTableIndex; + } + + AssetBundlerAbstractFileTableModel* BundleListTabWidget::GetFileTableModel() + { + return m_fileTableModel.get(); + } + + void BundleListTabWidget::SetActiveProjectLabel(const QString& labelText) + { + m_ui->activeProjectLabel->setText(labelText); + } + + void BundleListTabWidget::ApplyConfig() + { + const GUIApplicationManager::Config& config = m_guiApplicationManager->GetConfig(); + m_ui->fileTableFrame->setFixedWidth(config.fileTableWidth); + m_ui->fileTableView->header()->resizeSection(BundleFileListModel::Column::ColumnFileName, config.fileNameColumnWidth); + } + + + void BundleListTabWidget::FileSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) + { + if (m_ui->fileTableView->selectionModel()->selectedRows().size() == 0) + { + m_selectedFileTableIndex = QModelIndex(); + ClearDisplayedBundleValues(); + return; + } + + m_selectedFileTableIndex = m_fileTableFilterModel->mapToSource(m_ui->fileTableView->selectionModel()->selectedRows()[0]); + AZ::Outcome< BundleFileInfoPtr, void> fileInfoOutcome = m_fileTableModel->GetBundleInfo(m_selectedFileTableIndex); + + if (!fileInfoOutcome.IsSuccess()) + { + ClearDisplayedBundleValues(); + return; + } + + BundleFileInfoPtr fileInfoPtr = fileInfoOutcome.GetValue(); + + m_ui->absolutePathLabel->setText(fileInfoPtr->m_absolutePath.c_str()); + + QString sizeFormat("%1 MB"); + double compressedSize = (double)fileInfoPtr->m_compressedSize / BytesToMegabytes; + m_ui->compressedSizeValueLabel->setText(sizeFormat.arg(compressedSize, 6, 'f', 3)); + + bool hasRelatedBundles = !fileInfoPtr->m_relatedBundles.isEmpty(); + if (hasRelatedBundles) + { + m_relatedBundlesListModel->setStringList(fileInfoPtr->m_relatedBundles); + } + m_ui->relatedBundlesLabel->setVisible(hasRelatedBundles); + m_ui->relatedBundlesListView->setVisible(hasRelatedBundles); + } + + void BundleListTabWidget::ClearDisplayedBundleValues() + { + m_ui->absolutePathLabel->clear(); + m_ui->compressedSizeValueLabel->clear(); + m_ui->relatedBundlesLabel->setVisible(false); + m_ui->relatedBundlesListView->setVisible(false); + m_relatedBundlesListModel->setStringList({}); + } +} + +#include diff --git a/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.h b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.h new file mode 100644 index 0000000000..c05e1078fa --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.h @@ -0,0 +1,67 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#include +#include +#include +#include +#include +#endif + +namespace Ui +{ + class BundleListTabWidget; +} + +namespace AssetBundler +{ + class BundleFileListModel; + + class BundleListTabWidget + : public AssetBundlerTabWidget + { + Q_OBJECT + public: + explicit BundleListTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager); + virtual ~BundleListTabWidget() {} + + QString GetTabTitle() override { return tr("Completed Bundles"); } + QString GetFileTypeDisplayName() override { return tr("Bundle"); } + AssetBundlingFileType GetFileType() override { return AssetBundlingFileType::BundleFileType; } + bool HasUnsavedChanges() override { return false; } + void Reload() override; + bool SaveCurrentSelection() override { return true; } + bool SaveAll() override { return true; } + void SetModelDataSource() override; + AzQtComponents::TableView* GetFileTableView() override; + QModelIndex GetSelectedFileTableIndex() override; + AssetBundlerAbstractFileTableModel* GetFileTableModel() override; + void SetActiveProjectLabel(const QString& labelText) override; + void ApplyConfig() override; + void FileSelectionChanged(const QItemSelection& /*selected*/ = QItemSelection(), const QItemSelection& /*deselected*/ = QItemSelection()) override; + + private: + void ClearDisplayedBundleValues(); + + QSharedPointer m_ui; + QSharedPointer m_fileTableModel; + QModelIndex m_selectedFileTableIndex; + QSharedPointer m_relatedBundlesListModel; + }; + +} diff --git a/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.ui b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.ui new file mode 100644 index 0000000000..9880640f09 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/BundleListTabWidget.ui @@ -0,0 +1,253 @@ + + + BundleListTabWidget + + + + 0 + 0 + 888 + 562 + + + + Bundle List Tab Widget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Bundles + + + + + + + + + + Filter by file name + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Bundle Info + + + + + + + TextLabel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Compressed Size + + + + + + + TextLabel + + + + + + + Related Bundles + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + AzQtComponents::TableView + QTreeView +
AzQtComponents/Components/Widgets/TableView.h
+ 1 +
+ + AzQtComponents::FilteredSearchWidget + QTreeView +
AzQtComponents/Components/FilteredSearchWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.cpp b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.cpp new file mode 100644 index 0000000000..b5c2d78030 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.cpp @@ -0,0 +1,451 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +#include +#include + +namespace AssetBundler +{ + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ComparisonDataWidget + ////////////////////////////////////////////////////////////////////////////////////////////////// + + const QStringList ComparisonTypeStringList = { "Default", "Delta", "Union", "Intersection", "Complement", "Wildcard", "Regex" }; + + ComparisonDataWidget::ComparisonDataWidget( + AZStd::shared_ptr comparisonList, + size_t comparisonDataIndex, + const AZStd::string& defaultAssetListFileDirectory, + QWidget* parent) + : QWidget(parent) + , m_comparisonDataIndex(comparisonDataIndex) + , m_defaultAssetListFileDirectory(defaultAssetListFileDirectory) + { + m_ui.reset(new Ui::ComparisonDataWidget); + m_ui->setupUi(this); + + m_comparisonList = comparisonList; + + if (!IsComparisonDataIndexValid()) + { + AZ_Error("AssetBundler", false, "ComparisonData index ( %u ) is out of bounds. ComparisonData cannot be displayed.", m_comparisonDataIndex); + return; + } + + // Due to initialization order, we need to hard-code this or else the value will be overwritten once this + // ComparisonDataWidget is added to the ComparisonDataCard, and the Card.qss file is applied + QString lineEditStyle("background-color: #CCCCCC;"); + m_ui->nameLineEdit->setStyleSheet(lineEditStyle); + m_ui->firstInputLineEdit->setStyleSheet(lineEditStyle); + m_ui->secondInputLineEdit->setStyleSheet(lineEditStyle); + m_ui->filePatternLineEdit->setStyleSheet(lineEditStyle); + + m_ui->firstInputLineEdit->setReadOnly(true); + m_ui->secondInputLineEdit->setReadOnly(true); + + SetAllDisplayValues(m_comparisonList->GetComparisonList()[m_comparisonDataIndex]); + + MouseWheelEventFilter* mouseWheelEventFilter = new MouseWheelEventFilter(this); + + connect(m_ui->nameLineEdit, &QLineEdit::textEdited, this, &ComparisonDataWidget::OnNameLineEditChanged); + + m_ui->comparisonTypeComboBox->installEventFilter(mouseWheelEventFilter); + connect(m_ui->comparisonTypeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ComparisonDataWidget::OnComparisonTypeComboBoxChanged); + + m_ui->firstInputComboBox->installEventFilter(mouseWheelEventFilter); + connect(m_ui->firstInputComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ComparisonDataWidget::OnFirstInputComboBoxChanged); + connect(m_ui->firstInputBrowseButton, &QPushButton::pressed, this, &ComparisonDataWidget::OnFirstInputBrowseButtonPressed); + + m_ui->secondInputComboBox->installEventFilter(mouseWheelEventFilter); + connect(m_ui->secondInputComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ComparisonDataWidget::OnSecondInputComboBoxChanged); + connect(m_ui->secondInputBrowseButton, &QPushButton::pressed, this, &ComparisonDataWidget::OnSecondInputBrowseButtonPressed); + + connect(m_ui->filePatternLineEdit, &QLineEdit::textEdited, this, &ComparisonDataWidget::OnFilePatternLineEditChanged); + } + + void ComparisonDataWidget::UpdateListOfTokenNames() + { + using namespace AzToolsFramework; + + if (!IsComparisonDataIndexValid()) + { + return; + } + + m_inputTokenNameList.clear(); + m_ui->firstInputComboBox->clear(); + m_ui->secondInputComboBox->clear(); + + // Store info about the current FirstInput and SecondInput values so we may auto-select them later + auto allComparisonDataSteps = m_comparisonList->GetComparisonList(); + AZStd::string selectedFirstInput = allComparisonDataSteps.at(m_comparisonDataIndex).m_firstInput; + int selectedFirstInputIndex = 0; + AZStd::string selectedSecondInput = allComparisonDataSteps.at(m_comparisonDataIndex).m_secondInput; + int selectedSecondInputIndex = 0; + + // Build list of all Token Names that have come before the current Comparison Step + m_inputTokenNameList.append(tr("Choose Asset List...")); + int currentComboBoxIndex = 1; + + AZStd::string tokenName; + for (size_t i = 0; i < m_comparisonDataIndex; ++i) + { + tokenName = allComparisonDataSteps.at(i).m_output; + if (!AssetFileInfoListComparison::IsTokenFile(tokenName)) + { + continue; + } + + if (tokenName == selectedFirstInput) + { + selectedFirstInputIndex = currentComboBoxIndex; + } + if (tokenName == selectedSecondInput) + { + selectedSecondInputIndex = currentComboBoxIndex; + } + + m_inputTokenNameList.append(RemoveTokenCharFromString(tokenName)); + ++currentComboBoxIndex; + } + + // Update display with list of Token Names, and select current Token Name in both input combo boxes + m_ui->firstInputComboBox->insertItems(0, m_inputTokenNameList); + m_ui->firstInputComboBox->setCurrentIndex(selectedFirstInputIndex); + m_isFirstInputFileNameVisible = selectedFirstInputIndex == 0; + SetFirstInputFileVisibility(m_isFirstInputFileNameVisible); + + m_ui->secondInputComboBox->insertItems(0, m_inputTokenNameList); + m_ui->secondInputComboBox->setCurrentIndex(selectedSecondInputIndex); + m_isSecondInputFileNameVisible = selectedSecondInputIndex == 0; + SetSecondInputFileVisibility(m_isSecondInputFileNameVisible); + } + + void ComparisonDataWidget::SetAllDisplayValues(const AzToolsFramework::AssetFileInfoListComparison::ComparisonData& comparisonData) + { + using namespace AzToolsFramework; + + // Name (Token value) + m_ui->nameLineEdit->setText(RemoveTokenCharFromString(comparisonData.m_output)); + + // Comparison Type + InitComparisonTypeComboBox(comparisonData); + + // Inputs + UpdateListOfTokenNames(); + m_ui->firstInputLineEdit->setText(QString(comparisonData.m_cachedFirstInputPath.c_str())); + m_ui->secondInputLineEdit->setText(QString(comparisonData.m_cachedSecondInputPath.c_str())); + + // Update fields that are not always visible + UpdateOnComparisonTypeChanged(comparisonData.m_filePatternType != AssetFileInfoListComparison::FilePatternType::Default); + } + + void ComparisonDataWidget::OnNameLineEditChanged() + { + if (!IsComparisonDataIndexValid()) + { + return; + } + + AZStd::string tokenName = m_ui->nameLineEdit->text().toUtf8().data(); + AzToolsFramework::AssetFileInfoListComparison::FormatOutputToken(tokenName); + m_comparisonList->SetOutput(m_comparisonDataIndex, tokenName); + emit comparisonDataChanged(); + emit comparisonDataTokenNameChanged(m_comparisonDataIndex); + } + + void ComparisonDataWidget::UpdateOnComparisonTypeChanged(bool isFilePatternOperation) + { + using namespace AzToolsFramework; + + if (!IsComparisonDataIndexValid()) + { + return; + } + + AssetFileInfoListComparison::ComparisonData comparisonData = m_comparisonList->GetComparisonList()[m_comparisonDataIndex]; + m_ui->inputBLabel->setVisible(!isFilePatternOperation); + m_ui->secondInputComboBox->setVisible(!isFilePatternOperation); + SetSecondInputFileVisibility(!isFilePatternOperation && m_isSecondInputFileNameVisible); + + m_ui->filePatternLabel->setVisible(isFilePatternOperation); + m_ui->filePatternLineEdit->setVisible(isFilePatternOperation); + m_ui->filePatternLineEdit->setText(comparisonData.m_filePattern.c_str()); + } + + void ComparisonDataWidget::InitComparisonTypeComboBox(const AzToolsFramework::AssetFileInfoListComparison::ComparisonData& comparisonData) + { + using namespace AzToolsFramework; + + m_ui->comparisonTypeComboBox->insertItems(0, ComparisonTypeStringList); + int initialSelectionIndex = ComparisonTypeIndex::Default; + + if (comparisonData.m_filePatternType != AssetFileInfoListComparison::FilePatternType::Default + && comparisonData.m_comparisonType == AssetFileInfoListComparison::ComparisonType::FilePattern) + { + if (comparisonData.m_filePatternType == AssetFileInfoListComparison::FilePatternType::Wildcard) + { + initialSelectionIndex = ComparisonTypeIndex::Wildcard; + } + else + { + initialSelectionIndex = ComparisonTypeIndex::Regex; + } + } + else + { + switch (comparisonData.m_comparisonType) + { + case AssetFileInfoListComparison::ComparisonType::Default: + break; + case AssetFileInfoListComparison::ComparisonType::Delta: + initialSelectionIndex = ComparisonTypeIndex::Delta; + break; + case AssetFileInfoListComparison::ComparisonType::Union: + initialSelectionIndex = ComparisonTypeIndex::Union; + break; + case AssetFileInfoListComparison::ComparisonType::Intersection: + initialSelectionIndex = ComparisonTypeIndex::Intersection; + break; + case AssetFileInfoListComparison::ComparisonType::Complement: + initialSelectionIndex = ComparisonTypeIndex::Complement; + break; + default: + AZ_Warning("AssetBundler", false, "ComparisonType ( %u ) is not supported in the Asset Bundler", comparisonData.m_comparisonType); + } + } + + m_ui->comparisonTypeComboBox->setCurrentIndex(initialSelectionIndex); + } + + void ComparisonDataWidget::OnComparisonTypeComboBoxChanged(int index) + { + using namespace AzToolsFramework; + + if (!IsComparisonDataIndexValid()) + { + return; + } + + bool isFilePattern = false; + + switch (index) + { + case ComparisonTypeIndex::Default: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::Default); + break; + case ComparisonTypeIndex::Delta: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::Delta); + break; + case ComparisonTypeIndex::Union: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::Union); + break; + case ComparisonTypeIndex::Intersection: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::Intersection); + break; + case ComparisonTypeIndex::Complement: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::Complement); + break; + case ComparisonTypeIndex::Wildcard: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::FilePattern); + m_comparisonList->SetFilePatternType(m_comparisonDataIndex, AssetFileInfoListComparison::FilePatternType::Wildcard); + isFilePattern = true; + break; + case ComparisonTypeIndex::Regex: + m_comparisonList->SetComparisonType(m_comparisonDataIndex, AssetFileInfoListComparison::ComparisonType::FilePattern); + m_comparisonList->SetFilePatternType(m_comparisonDataIndex, AssetFileInfoListComparison::FilePatternType::Regex); + isFilePattern = true; + break; + } + + UpdateOnComparisonTypeChanged(isFilePattern); + emit comparisonDataChanged(); + } + + void ComparisonDataWidget::OnFilePatternLineEditChanged() + { + if (!IsComparisonDataIndexValid()) + { + return; + } + + m_comparisonList->SetFilePattern(m_comparisonDataIndex, m_ui->filePatternLineEdit->text().toUtf8().data()); + emit comparisonDataChanged(); + } + + void ComparisonDataWidget::OnFirstInputComboBoxChanged(int index) + { + AZStd::string firstInputValue; + + if (!IsComparisonDataIndexValid()) + { + return; + } + + m_isFirstInputFileNameVisible = index == 0; + + if (!m_isFirstInputFileNameVisible) + { + // The 0th index is the default value, which translates to an empty token string. + firstInputValue = m_ui->firstInputComboBox->currentText().toUtf8().data(); + AzToolsFramework::AssetFileInfoListComparison::FormatOutputToken(firstInputValue); + } + + SetFirstInputFileVisibility(m_isFirstInputFileNameVisible); + + m_comparisonList->SetFirstInput(m_comparisonDataIndex, firstInputValue); + emit comparisonDataChanged(); + } + + void ComparisonDataWidget::SetFirstInputFileVisibility(bool isVisible) + { + m_ui->firstInputLineEdit->setVisible(isVisible); + m_ui->firstInputBrowseButton->setVisible(isVisible); + } + + void ComparisonDataWidget::OnFirstInputBrowseButtonPressed() + { + if (!IsComparisonDataIndexValid()) + { + return; + } + + QString absoluteFilePath = BrowseButtonPressed(); + if (absoluteFilePath.isEmpty()) + { + // User canceled out of the dialog + return; + } + + m_ui->firstInputLineEdit->setText(absoluteFilePath); + m_comparisonList->SetCachedFirstInputPath(m_comparisonDataIndex, absoluteFilePath.toUtf8().data()); + } + + void ComparisonDataWidget::OnSecondInputComboBoxChanged(int index) + { + if (!IsComparisonDataIndexValid()) + { + return; + } + + AZStd::string secondInputValue; + + m_isSecondInputFileNameVisible = index == 0; + + if (!m_isSecondInputFileNameVisible) + { + // The 0th index is the default value, which translates to an empty token string. + secondInputValue = m_ui->secondInputComboBox->currentText().toUtf8().data(); + AzToolsFramework::AssetFileInfoListComparison::FormatOutputToken(secondInputValue); + } + + SetSecondInputFileVisibility(m_isSecondInputFileNameVisible); + + m_comparisonList->SetSecondInput(m_comparisonDataIndex, secondInputValue); + emit comparisonDataChanged(); + } + + void ComparisonDataWidget::SetSecondInputFileVisibility(bool isVisible) + { + m_ui->secondInputLineEdit->setVisible(isVisible); + m_ui->secondInputBrowseButton->setVisible(isVisible); + } + + void ComparisonDataWidget::OnSecondInputBrowseButtonPressed() + { + if (!IsComparisonDataIndexValid()) + { + return; + } + + QString absoluteFilePath = BrowseButtonPressed(); + if (absoluteFilePath.isEmpty()) + { + // User canceled out of the dialog + return; + } + + m_ui->secondInputLineEdit->setText(absoluteFilePath); + m_comparisonList->SetCachedSecondInputPath(m_comparisonDataIndex, absoluteFilePath.toUtf8().data()); + } + + QString ComparisonDataWidget::BrowseButtonPressed() + { + AZStd::string selectedPath = NewFileDialog::OSNewFileDialog( + this, + AzToolsFramework::AssetSeedManager::GetAssetListFileExtension(), + "Asset List", + m_defaultAssetListFileDirectory); + AzToolsFramework::RemovePlatformIdentifier(selectedPath); + return selectedPath.c_str(); + } + + bool ComparisonDataWidget::IsComparisonDataIndexValid() + { + return m_comparisonList->GetComparisonList().size() > m_comparisonDataIndex; + } + + QString ComparisonDataWidget::RemoveTokenCharFromString(const AZStd::string& tokenName) + { + QString displayName = tokenName.c_str(); + if (displayName.startsWith(AzToolsFramework::AssetFileInfoListComparison::GetTokenIdentifier())) + { + displayName.remove(0, 1); + } + + return displayName; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // MouseWheelEventFilter + ////////////////////////////////////////////////////////////////////////////////////////////////// + + bool MouseWheelEventFilter::eventFilter(QObject* obj, QEvent* ev) + { + if (ev->type() == QEvent::Wheel) + { + return true; + } + + return QObject::eventFilter(obj, ev); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ComparisonDataCard + ////////////////////////////////////////////////////////////////////////////////////////////////// + + ComparisonDataCard::ComparisonDataCard( + AZStd::shared_ptr comparisonList, + size_t comparisonDataIndex, + const AZStd::string& defaultAssetListFileDirectory, + QWidget* parent) + : AzQtComponents::Card(parent) + { + m_comparisonDataWidget = new ComparisonDataWidget(comparisonList, comparisonDataIndex, defaultAssetListFileDirectory, this); + setContentWidget(m_comparisonDataWidget); + + connect(this, &AzQtComponents::Card::contextMenuRequested, this, &ComparisonDataCard::OnContextMenuRequested); + } + + void ComparisonDataCard::OnContextMenuRequested(const QPoint& position) + { + emit comparisonDataCardContextMenuRequested(m_comparisonDataWidget->GetComparisonDataIndex(), position); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.h b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.h new file mode 100644 index 0000000000..9728d281a7 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.h @@ -0,0 +1,151 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#include + +#include +#endif + +namespace Ui +{ + class ComparisonDataWidget; +} + +namespace AssetBundler +{ + //! Widget for displaying and editing a single Comparison Step inside a Comparison Rules file. + class ComparisonDataWidget + : public QWidget + { + Q_OBJECT + + public: + explicit ComparisonDataWidget( + AZStd::shared_ptr comparisonList, + size_t comparisonDataIndex, + const AZStd::string& defaultAssetListFileDirectory, + QWidget* parent = nullptr); + virtual ~ComparisonDataWidget() {} + + enum ComparisonTypeIndex : int + { + Default = 0, + Delta, + Union, + Intersection, + Complement, + Wildcard, + Regex, + MAX + }; + + size_t GetComparisonDataIndex() { return m_comparisonDataIndex; } + + void UpdateListOfTokenNames(); + + Q_SIGNALS: + void comparisonDataChanged(); + + void comparisonDataTokenNameChanged(size_t comparisonDataIndex); + + private: + void SetAllDisplayValues(const AzToolsFramework::AssetFileInfoListComparison::ComparisonData& comparisonData); + + void OnNameLineEditChanged(); + + void UpdateOnComparisonTypeChanged(bool isFilePatternOperation); + + void InitComparisonTypeComboBox(const AzToolsFramework::AssetFileInfoListComparison::ComparisonData& comparisonData); + + void OnComparisonTypeComboBoxChanged(int index); + + void OnFilePatternLineEditChanged(); + + void OnFirstInputComboBoxChanged(int index); + + void SetFirstInputFileVisibility(bool isVisible); + + void OnFirstInputBrowseButtonPressed(); + + void OnSecondInputComboBoxChanged(int index); + + void SetSecondInputFileVisibility(bool isVisible); + + void OnSecondInputBrowseButtonPressed(); + + QString BrowseButtonPressed(); + + bool IsComparisonDataIndexValid(); + + QString RemoveTokenCharFromString(const AZStd::string& tokenName); + + QSharedPointer m_ui; + + AZStd::shared_ptr m_comparisonList; + size_t m_comparisonDataIndex; + + AZStd::string m_defaultAssetListFileDirectory; + + QStringList m_inputTokenNameList; + QString m_outputAssetListFileAbsolutePath; + + bool m_isFirstInputFileNameVisible = false; + bool m_isSecondInputFileNameVisible = false; + }; + + class MouseWheelEventFilter + : public QObject + { + Q_OBJECT + + public: + explicit MouseWheelEventFilter(QObject* parent) : QObject(parent) {} + + protected: + bool eventFilter(QObject* obj, QEvent* ev) override; + }; + + + //! Wrapper widget that controls the expansion state and signals that trigger a context menu of a ComparisonDataWidget. + class ComparisonDataCard + : public AzQtComponents::Card + { + Q_OBJECT + + public: + explicit ComparisonDataCard( + AZStd::shared_ptr comparisonList, + size_t comparisonDataIndex, + const AZStd::string& defaultAssetListFileDirectory, + QWidget* parent = nullptr); + virtual ~ComparisonDataCard() {} + + ComparisonDataWidget* GetComparisonDataWidget() { return m_comparisonDataWidget; } + + Q_SIGNALS: + void comparisonDataCardContextMenuRequested(size_t comparisonDataIndex, const QPoint& position); + + private: + ComparisonDataWidget* m_comparisonDataWidget = nullptr; + + private slots: + void OnContextMenuRequested(const QPoint& position); + }; + + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.ui b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.ui new file mode 100644 index 0000000000..4e148e21bb --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/ComparisonDataWidget.ui @@ -0,0 +1,150 @@ + + + ComparisonDataWidget + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + QLayout::SetDefaultConstraint + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + + + + Comparison Type + + + + + + + + + + Input A + + + + + + + + + + 10 + + + + + + + + Browse... + + + + + + + + + Input B + + + + + + + + + + 10 + + + + + + + + Browse... + + + + + + + + + File Pattern + + + + + + + + + + + + + + diff --git a/Code/Tools/AssetBundler/source/ui/EditSeedDialog.cpp b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.cpp new file mode 100644 index 0000000000..a42e0cd0f0 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.cpp @@ -0,0 +1,62 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +#include + +namespace AssetBundler +{ + + EditSeedDialog::EditSeedDialog( + QWidget* parent, + const AzFramework::PlatformFlags& enabledPlatforms, + const AzFramework::PlatformFlags& selectedPlatforms, + const AzFramework::PlatformFlags& partiallySelectedPlatforms) + : QDialog(parent) + { + m_ui.reset(new Ui::EditSeedDialog); + m_ui->setupUi(this); + + m_ui->platformSelectionWidget->Init(enabledPlatforms); + m_ui->platformSelectionWidget->SetSelectedPlatforms(selectedPlatforms, partiallySelectedPlatforms); + connect(m_ui->platformSelectionWidget, &PlatformSelectionWidget::PlatformsSelected, this, &EditSeedDialog::OnPlatformSelectionChanged); + + // Set up confirm and cancel buttons + connect(m_ui->applyChangesButton, &QPushButton::clicked, this, &QDialog::accept); + connect(m_ui->cancelButton, &QPushButton::clicked, this, &QDialog::reject); + } + + AzFramework::PlatformFlags EditSeedDialog::GetPlatformFlags() + { + return m_ui->platformSelectionWidget->GetSelectedPlatforms(); + } + + AzFramework::PlatformFlags EditSeedDialog::GetPartiallySelectedPlatformFlags() + { + return m_ui->platformSelectionWidget->GetPartiallySelectedPlatforms(); + } + + void EditSeedDialog::OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms, const AzFramework::PlatformFlags& partiallySelectedPlatforms) + { + // Disable the "Apply Changes" button if no platforms are selected + bool areAnyPlatformsSelected = selectedPlatforms != AzFramework::PlatformFlags::Platform_NONE || + partiallySelectedPlatforms != AzFramework::PlatformFlags::Platform_NONE; + m_ui->applyChangesButton->setEnabled(areAnyPlatformsSelected); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/EditSeedDialog.h b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.h new file mode 100644 index 0000000000..751c1eae3d --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.h @@ -0,0 +1,52 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#endif + +namespace Ui +{ + class EditSeedDialog; +} + +namespace AssetBundler +{ + class EditSeedDialog + : public QDialog + { + Q_OBJECT + + public: + explicit EditSeedDialog( + QWidget* parent, + const AzFramework::PlatformFlags& enabledPlatforms, + const AzFramework::PlatformFlags& selectedPlatforms, + const AzFramework::PlatformFlags& partiallySelectedPlatforms = AzFramework::PlatformFlags::Platform_NONE); + virtual ~EditSeedDialog() {} + + AzFramework::PlatformFlags GetPlatformFlags(); + AzFramework::PlatformFlags GetPartiallySelectedPlatformFlags(); + + private: + void OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms, const AzFramework::PlatformFlags& partiallySelectedPlatforms); + + QSharedPointer m_ui; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/EditSeedDialog.ui b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.ui new file mode 100644 index 0000000000..7d42d2d252 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/EditSeedDialog.ui @@ -0,0 +1,117 @@ + + + EditSeedDialog + + + + 0 + 0 + 400 + 92 + + + + + 300 + 0 + + + + Edit Selected Platforms + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + 10 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Apply Changes + + + true + + + + + + + + + + AssetBundler::PlatformSelectionWidget + QWidget +
source/ui/PlatformSelectionWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.cpp b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.cpp new file mode 100644 index 0000000000..3724bc8753 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.cpp @@ -0,0 +1,276 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +const char CustomBundleSettingsText[] = "Custom"; + +namespace AssetBundler +{ + GenerateBundlesModal::GenerateBundlesModal( + QWidget* parent, + const AZStd::string& assetListFileAbsolutePath, + const AZStd::string& defaultBundleDirectory, + const AZStd::string& defaultBundleSettingsDirectory, + AssetListTabWidget* assetListTabWidget) + : QDialog(parent) + , m_assetListFileAbsolutePath(assetListFileAbsolutePath) + , m_defaultBundleDirectory(defaultBundleDirectory) + , m_defaultBundleSettingsDirectory(defaultBundleSettingsDirectory) + , m_assetListTabWidget(assetListTabWidget) + { + m_ui.reset(new Ui::GenerateBundlesModal); + m_ui->setupUi(this); + + m_platformName = AzToolsFramework::GetPlatformIdentifier(m_assetListFileAbsolutePath); + + // Selected Asset List + m_ui->selectedAssetListPathLabel->setText(QString(m_assetListFileAbsolutePath.c_str())); + + // Platform + m_ui->platformNameLabel->setText(QString(m_platformName.c_str())); + + // Bundle Output + m_ui->outputBundlePathLineEdit->setReadOnly(true); + connect(m_ui->outputBundlePathBrowseButton, &QPushButton::clicked, this, &GenerateBundlesModal::OnOutputBundleLocationBrowseButtonPressed); + + // Bundle Settings files + m_ui->bundleSettingsFileLineEdit->setReadOnly(true); + m_ui->bundleSettingsFileLineEdit->setText(tr(CustomBundleSettingsText)); + connect(m_ui->bundleSettingsFileBrowseButton, &QPushButton::clicked, this, &GenerateBundlesModal::OnBundleSettingsBrowseButtonPressed); + connect(m_ui->bundleSettingsFileSaveButton, &QPushButton::clicked, this, &GenerateBundlesModal::OnBundleSettingsSaveButtonPressed); + + // Max Bundle Size + m_ui->maxBundleSizeSpinBox->setRange(1, AzToolsFramework::MaxBundleSizeInMB); + m_ui->maxBundleSizeSpinBox->setValue(AzToolsFramework::MaxBundleSizeInMB); + m_ui->maxBundleSizeSpinBox->setButtonSymbols(QAbstractSpinBox::ButtonSymbols::NoButtons); + m_ui->maxBundleSizeSpinBox->setSuffix(" MB"); + connect(m_ui->maxBundleSizeSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &GenerateBundlesModal::OnMaxBundleSizeChanged); + + // Bundle Version + m_ui->bundleVersionSpinBox->setRange(1, AzFramework::AssetBundleManifest::CurrentBundleVersion); + m_ui->bundleVersionSpinBox->setValue(AzFramework::AssetBundleManifest::CurrentBundleVersion); + m_ui->bundleVersionSpinBox->setButtonSymbols(QAbstractSpinBox::ButtonSymbols::NoButtons); + connect(m_ui->bundleVersionSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &GenerateBundlesModal::OnBundleVersionChanged); + + // Cancel and Generate Bundles buttons + m_ui->generateBundlesButton->setEnabled(false); + connect(m_ui->cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(m_ui->generateBundlesButton, &QPushButton::clicked, this, &GenerateBundlesModal::OnGenerateBundlesButtonPressed); + + // In-memory Bundle Settings + m_bundleSettings.m_assetFileInfoListPath = m_assetListFileAbsolutePath; + m_bundleSettings.m_platform = m_platformName; + m_bundleSettings.m_maxBundleSizeInMB = static_cast(m_ui->maxBundleSizeSpinBox->value()); + m_bundleSettings.m_bundleVersion = m_ui->bundleVersionSpinBox->value(); + } + + GenerateBundlesModal::~GenerateBundlesModal() + { + m_assetListTabWidget = nullptr; + } + + void GenerateBundlesModal::OnOutputBundleLocationBrowseButtonPressed() + { + AZStd::string outputBundleAbsolutePath = NewFileDialog::OSNewFileDialog( + this, + AzToolsFramework::AssetBundleSettings::GetBundleFileExtension(), + "Bundle", + m_defaultBundleDirectory); + + if (outputBundleAbsolutePath.empty()) + { + // User canceled out of the dialog + return; + } + + AzToolsFramework::RemovePlatformIdentifier(outputBundleAbsolutePath); + AddPlatformIdentifier(outputBundleAbsolutePath, m_platformName); + + m_bundleSettings.m_bundleFilePath = outputBundleAbsolutePath; + + m_ui->outputBundlePathLineEdit->setText(outputBundleAbsolutePath.c_str()); + m_ui->generateBundlesButton->setEnabled(true); + } + + void GenerateBundlesModal::OnBundleSettingsBrowseButtonPressed() + { + AZStd::string bundleSettingsAbsolutePath = NewFileDialog::OSNewFileDialog( + this, + AzToolsFramework::AssetBundleSettings::GetBundleSettingsFileExtension(), + "Bundle Settings", + m_defaultBundleSettingsDirectory); + + if (bundleSettingsAbsolutePath.empty()) + { + // User canceled out of the dialog + return; + } + + // Read in the values from the Bundle Settings file + bool loadResult = LoadBundleSettingsValues(bundleSettingsAbsolutePath); + if (!loadResult) + { + return; + } + + // Update the display name for our settings + UpdateBundleSettingsDisplayName(bundleSettingsAbsolutePath); + } + + bool GenerateBundlesModal::LoadBundleSettingsValues(const AZStd::string& absoluteBundleSettingsFilePath) + { + using namespace AzToolsFramework; + + auto outcome = AssetBundleSettings::Load(absoluteBundleSettingsFilePath); + if (!outcome.IsSuccess()) + { + AZ_Error("AssetBundler", false, outcome.GetError().c_str()); + return false; + } + + // We don't want to overwrite all of the in-memory bundle settings, so we will just update a few + AssetBundleSettings loadedSettings = outcome.TakeValue(); + + m_bundleSettings.m_maxBundleSizeInMB = loadedSettings.m_maxBundleSizeInMB; + m_ui->maxBundleSizeSpinBox->setValue(static_cast(m_bundleSettings.m_maxBundleSizeInMB)); + + m_bundleSettings.m_bundleVersion = loadedSettings.m_bundleVersion; + m_ui->bundleVersionSpinBox->setValue(m_bundleSettings.m_bundleVersion); + + return true; + } + + void GenerateBundlesModal::UpdateBundleSettingsDisplayName(const AZStd::string& absoluteBundleSettingsFilePath) + { + if (absoluteBundleSettingsFilePath.empty()) + { + m_ui->bundleSettingsFileLineEdit->setText(tr(CustomBundleSettingsText)); + return; + } + + AZStd::string bundleSettingsFileName = absoluteBundleSettingsFilePath; + AzToolsFramework::RemovePlatformIdentifier(bundleSettingsFileName); + AzFramework::StringFunc::Path::GetFileName(bundleSettingsFileName.c_str(), bundleSettingsFileName); + m_ui->bundleSettingsFileLineEdit->setText(bundleSettingsFileName.c_str()); + } + + void GenerateBundlesModal::OnBundleSettingsSaveButtonPressed() + { + using namespace AzToolsFramework; + + // Ask the user where they want to save the Bundle Settings file + AZStd::string bundleSettingsAbsolutePath = NewFileDialog::OSNewFileDialog( + this, + AssetBundleSettings::GetBundleSettingsFileExtension(), + "Bundle Settings", + m_defaultBundleSettingsDirectory); + + if (bundleSettingsAbsolutePath.empty()) + { + // User canceled out of the operation + return; + } + + AzToolsFramework::RemovePlatformIdentifier(bundleSettingsAbsolutePath); + AddPlatformIdentifier(bundleSettingsAbsolutePath, m_platformName); + + if (AZ::IO::FileIOBase::GetInstance()->Exists(bundleSettingsAbsolutePath.c_str())) + { + QString messageBoxText = QString(tr("Bundle Settings ( %1 ) already exists on-disk. Saving the current settings will override the existing settings. \n\nDo you wish to continue?")).arg(bundleSettingsAbsolutePath.c_str()); + + QMessageBox::StandardButton confirmDeleteFileResult = QMessageBox::question(this, QString(tr("Replace Existing Settings")), messageBoxText); + if (confirmDeleteFileResult != QMessageBox::StandardButton::Yes) + { + // User canceled out of the operation + return; + } + } + + bool saveResult = AssetBundleSettings::Save(m_bundleSettings, bundleSettingsAbsolutePath); + if (!saveResult) + { + // Error has already been thrown + return; + } + + m_assetListTabWidget->AddScanPathToAssetBundlerSettings(AssetBundlingFileType::BundleSettingsFileType, bundleSettingsAbsolutePath); + + UpdateBundleSettingsDisplayName(bundleSettingsAbsolutePath); + } + + void GenerateBundlesModal::OnMaxBundleSizeChanged() + { + m_bundleSettings.m_maxBundleSizeInMB = static_cast(m_ui->maxBundleSizeSpinBox->value()); + UpdateBundleSettingsDisplayName(""); + } + + void GenerateBundlesModal::OnBundleVersionChanged() + { + m_bundleSettings.m_bundleVersion = m_ui->bundleVersionSpinBox->value(); + UpdateBundleSettingsDisplayName(""); + } + + void GenerateBundlesModal::OnGenerateBundlesButtonPressed() + { + using namespace AzToolsFramework; + + if (AZ::IO::FileIOBase::GetInstance()->Exists(m_bundleSettings.m_bundleFilePath.c_str())) + { + QString messageBoxText = QString(tr("Asset Bundle ( %1 ) already exists on-disk. Generating a new Bundle will override the existing Bundle. \n\nDo you wish to permanently delete the existing Bundle?")).arg(m_bundleSettings.m_bundleFilePath.c_str()); + + QMessageBox::StandardButton confirmDeleteFileResult = QMessageBox::question(this, QString(tr("Replace Existing Bundle")), messageBoxText); + if (confirmDeleteFileResult != QMessageBox::StandardButton::Yes) + { + // User canceled out of the operation + return; + } + } + + bool result = false; + AssetBundleCommandsBus::BroadcastResult(result, &AssetBundleCommandsBus::Events::CreateAssetBundle, m_bundleSettings); + + // This operation will take long enough that the OS will throw up its own wait cursor, + // but there is a slight delay where the UI is unresponsive but the cursor hasn't changed. + // We are changing the cursor manually to avoid that. + setCursor(Qt::WaitCursor); + + emit QDialog::accept(); + + if (result) + { + m_assetListTabWidget->AddScanPathToAssetBundlerSettings(AssetBundlingFileType::BundleFileType, m_bundleSettings.m_bundleFilePath); + + // The watched files list was updated after the files were created, so we need to force-reload them + m_assetListTabWidget->GetGUIApplicationManager()->UpdateFiles(AssetBundlingFileType::BundleFileType, { m_bundleSettings.m_bundleFilePath }); + } + + AZStd::vector generatedFilePaths = { m_bundleSettings.m_bundleFilePath }; + NewFileDialog::FileGenerationResultMessageBox(this, generatedFilePaths, !result); + + unsetCursor(); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.h b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.h new file mode 100644 index 0000000000..b4edaedb9b --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.h @@ -0,0 +1,75 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#endif + +namespace Ui +{ + class GenerateBundlesModal; +} + +namespace AssetBundler +{ + class AssetListTabWidget; + + class GenerateBundlesModal + : public QDialog + { + Q_OBJECT + + public: + explicit GenerateBundlesModal( + QWidget* parent, + const AZStd::string& assetListFileAbsolutePath, + const AZStd::string& defaultBundleDirectory, + const AZStd::string& defaultBundleSettingsDirectory, + AssetListTabWidget* assetListTabWidget); + virtual ~GenerateBundlesModal(); + + private: + void OnOutputBundleLocationBrowseButtonPressed(); + + void OnBundleSettingsBrowseButtonPressed(); + + bool LoadBundleSettingsValues(const AZStd::string& absoluteBundleSettingsFilePath); + + void UpdateBundleSettingsDisplayName(const AZStd::string& absoluteBundleSettingsFilePath); + + void OnBundleSettingsSaveButtonPressed(); + + void OnMaxBundleSizeChanged(); + + void OnBundleVersionChanged(); + + void OnGenerateBundlesButtonPressed(); + + QSharedPointer m_ui; + + AssetListTabWidget* m_assetListTabWidget = nullptr; + + AZStd::string m_assetListFileAbsolutePath; + AZStd::string m_defaultBundleDirectory; + AZStd::string m_defaultBundleSettingsDirectory; + AZStd::string m_platformName; + + AzToolsFramework::AssetBundleSettings m_bundleSettings; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.ui b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.ui new file mode 100644 index 0000000000..df7ac0f0be --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/GenerateBundlesModal.ui @@ -0,0 +1,292 @@ + + + GenerateBundlesModal + + + Qt::ApplicationModal + + + + 0 + 0 + 800 + 300 + + + + + 0 + 0 + + + + + 750 + 0 + + + + Generate Bundles + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 5 + + + 0 + + + 5 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Asset List + + + + + + + TextLabel + + + + + + + Platform + + + + + + + TextLabel + + + + + + + Output Bundle Name + + + + + + + + + + + + Browse... + + + + + + + + + + + + Qt::Horizontal + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Settings + + + + + + + + + + + + Browse... + + + + + + + Save + + + + + + + + + Max Bundle Size + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Bundle Version + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Generate Bundles + + + true + + + + + + + + + + diff --git a/Code/Tools/AssetBundler/source/ui/MainWindow.cpp b/Code/Tools/AssetBundler/source/ui/MainWindow.cpp new file mode 100644 index 0000000000..c0e30c01f2 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/MainWindow.cpp @@ -0,0 +1,249 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace AssetBundler +{ + + MainWindow::MainWindow(AssetBundler::GUIApplicationManager* guiApplicationManager, QWidget* parent) + : QMainWindow(parent) + , m_guiApplicationManager(guiApplicationManager) + { + m_ui.reset(new Ui::MainWindow); + m_ui->setupUi(this); + + m_ui->verticalLayout->setContentsMargins(0, 0, 0, 0); + + // Set up Log + m_logModel.reset(new AzToolsFramework::Logging::LogTableModel(this)); + m_ui->logTableView->setModel(m_logModel.data()); + m_ui->logTableView->setIndentation(0); + + m_ui->logTableView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui->logTableView, &QWidget::customContextMenuRequested, this, &MainWindow::ShowLogContextMenu); + + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + // Create the Unsaved Changes Popup + m_unsavedChangesMsgBox.reset(new QMessageBox(this)); + m_unsavedChangesMsgBox->setText(tr("There are unsaved changes.")); + m_unsavedChangesMsgBox->setInformativeText(tr("Would you like to save all changes before quitting?")); + m_unsavedChangesMsgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + m_unsavedChangesMsgBox->setDefaultButton(QMessageBox::Save); + + // Set up Quit functionality + m_ui->actionClose->setShortcut(Qt::Key_Q | Qt::CTRL); + m_ui->actionClose->setMenuRole(QAction::QuitRole); + connect(m_ui->actionClose, SIGNAL(triggered()), this, SLOT(close())); + this->addAction(m_ui->actionClose); + + // Set up Tabs + AssetBundlerTabWidget::InitAssetBundlerSettings(m_guiApplicationManager->GetCurrentProjectFolder().c_str()); + + m_seedListTab.reset(new SeedTabWidget(this, m_guiApplicationManager, QString(m_guiApplicationManager->GetAssetBundlingFolder().c_str()))); + m_ui->tabWidget->addTab(m_seedListTab.data(), m_seedListTab->GetTabTitle()); + + m_assetListTab.reset(new AssetListTabWidget(this, m_guiApplicationManager)); + m_ui->tabWidget->addTab(m_assetListTab.data(), m_assetListTab->GetTabTitle()); + + m_rulesTab.reset(new RulesTabWidget(this, m_guiApplicationManager)); + m_ui->tabWidget->addTab(m_rulesTab.data(), m_rulesTab->GetTabTitle()); + + m_bundleListTab.reset(new BundleListTabWidget(this, m_guiApplicationManager)); + m_ui->tabWidget->addTab(m_bundleListTab.data(), m_bundleListTab->GetTabTitle()); + + // Set up link to documentation + QAction* supportAction = new QAction(QIcon(":/stylesheet/img/help.svg"), "", this); + connect(supportAction, &QAction::triggered, this, &MainWindow::OnSupportClicked); + connect(m_ui->actionDocumentation, &QAction::triggered, this, &MainWindow::OnSupportClicked); + m_ui->tabWidget->setActionToolBarVisible(); + m_ui->tabWidget->addAction(supportAction); + + // Set up Save functionality + m_ui->actionSave->setShortcut(Qt::Key_S | Qt::CTRL); + connect(m_ui->actionSave, &QAction::triggered, this, &MainWindow::SaveCurrentSelection); + + m_ui->actionSaveAll->setShortcut(Qt::Key_S | Qt::CTRL | Qt::SHIFT); + connect(m_ui->actionSaveAll, &QAction::triggered, this, &MainWindow::SaveAll); + } + + MainWindow::~MainWindow() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + m_guiApplicationManager = nullptr; + } + + void MainWindow::Activate() + { + m_seedListTab->Activate(); + m_rulesTab->Activate(); + m_assetListTab->Activate(); + m_bundleListTab->Activate(); + } + + void MainWindow::ApplyConfig() + { + const GUIApplicationManager::Config& config = m_guiApplicationManager->GetConfig(); + // Event Log Details + m_ui->logTableView->header()->resizeSection(AzToolsFramework::Logging::LogTableModel::ColumnType, config.logTypeColumnWidth); + m_ui->logTableView->header()->resizeSection(AzToolsFramework::Logging::LogTableModel::ColumnWindow, config.logSourceColumnWidth); + + m_seedListTab->ApplyConfig(); + m_assetListTab->ApplyConfig(); + m_rulesTab->ApplyConfig(); + m_bundleListTab->ApplyConfig(); + } + + void MainWindow::WriteToLog(const AZStd::string& message, AzToolsFramework::Logging::LogLine::LogType logType) + { + WriteToLog(message.c_str(), logType); + } + + void MainWindow::WriteToLog(const QString& message, AzToolsFramework::Logging::LogLine::LogType logType) + { + WriteToLog(message.toUtf8().data(), logType); + } + + void MainWindow::WriteToLog(const char* message, AzToolsFramework::Logging::LogLine::LogType logType) + { + WriteToLog(message, "AssetBundler", logType); + } + + void MainWindow::WriteToLog(const char* message, const char* window, AzToolsFramework::Logging::LogLine::LogType logType) + { + m_logModel->AppendLine( + AzToolsFramework::Logging::LogLine( + message, + window, + logType, + QDateTime::currentMSecsSinceEpoch())); + m_ui->logTableView->scrollToBottom(); + } + + bool MainWindow::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + WriteToLog(message, window, AzToolsFramework::Logging::LogLine::LogType::TYPE_ERROR); + return true; + } + + bool MainWindow::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + WriteToLog(message, window, AzToolsFramework::Logging::LogLine::LogType::TYPE_WARNING); + return true; + } + + bool MainWindow::OnPrintf(const char* /*window*/, const char* /*message*/) + { + return true; + } + + void MainWindow::ShowWindow() + { + AzQtComponents::bringWindowToTop(this); + } + + void MainWindow::closeEvent(QCloseEvent* event) + { + if (!HasUnsavedChanges()) + { + // No need to ask the user if they want to quit when there are no unsaved changes + event->accept(); + return; + } + + int unsavedChangesResult = m_unsavedChangesMsgBox->exec(); + + switch (unsavedChangesResult) + { + case QMessageBox::Save: + // Save All was clicked + SaveAll(); + event->accept(); + break; + case QMessageBox::Discard: + // Don't Save was clicked + event->accept(); + break; + case QMessageBox::Cancel: + // Cancel was clicked + event->ignore(); + break; + default: + // should never be reached + AZ_Assert(false, "No result was returned by the Unsaved Changes Message Box!"); + break; + } + } + + void MainWindow::OnSupportClicked() + { + QDesktopServices::openUrl( + QStringLiteral("https://docs.aws.amazon.com/lumberyard/latest/userguide/asset-bundler-overview.html")); + } + + void MainWindow::ShowLogContextMenu(const QPoint& pos) + { + QModelIndex index = m_ui->logTableView->indexAt(pos); + + QMenu menu; + + QAction* action = menu.addAction(tr("Copy line"), this, [&]() + { + QApplication::clipboard()->setText(index.data(AzToolsFramework::Logging::LogTableModel::LogLineTextRole).toString()); + }); + action->setEnabled(index.isValid()); + + menu.exec(m_ui->logTableView->mapToGlobal(pos)); + } + + bool MainWindow::HasUnsavedChanges() + { + return m_seedListTab->HasUnsavedChanges() || m_rulesTab->HasUnsavedChanges(); + } + + void MainWindow::SaveCurrentSelection() + { + switch (m_ui->tabWidget->currentIndex()) + { + case TabIndex::Seeds: + m_seedListTab->SaveCurrentSelection(); + break; + case TabIndex::Rules: + m_rulesTab->SaveCurrentSelection(); + break; + default: + break; + } + } + + void MainWindow::SaveAll() + { + m_seedListTab->SaveAll(); + m_rulesTab->SaveAll(); + } +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/MainWindow.h b/Code/Tools/AssetBundler/source/ui/MainWindow.h new file mode 100644 index 0000000000..e8c5570649 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/MainWindow.h @@ -0,0 +1,105 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#endif + +namespace Ui +{ + class MainWindow; +} + +namespace AssetBundler +{ + class GUIApplicationManager; + + class MainWindow + : public QMainWindow + , public AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + + explicit MainWindow(AssetBundler::GUIApplicationManager* guiApplicationManager, QWidget* parent = 0); + virtual ~MainWindow(); + + void Activate(); + + void ApplyConfig(); + + void WriteToLog(const AZStd::string& message, AzToolsFramework::Logging::LogLine::LogType logType); + void WriteToLog(const QString& message, AzToolsFramework::Logging::LogLine::LogType logType); + void WriteToLog(const char* message, AzToolsFramework::Logging::LogLine::LogType logType); + void WriteToLog(const char* message, const char* window, AzToolsFramework::Logging::LogLine::LogType logType); + + ///////////////////////////////////////////////////////// + // TraceMessageBus overrides + bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override; + bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; + bool OnPrintf(const char* window, const char* message) override; + ///////////////////////////////////////////////////////// + + public Q_SLOTS: + void ShowWindow(); + + protected: + void closeEvent(QCloseEvent* event) override; + + private: + QSharedPointer m_ui; + + // Tabs + QSharedPointer m_assetListTab; + QSharedPointer m_seedListTab; + QSharedPointer m_rulesTab; + QSharedPointer m_bundleListTab; + + enum TabIndex + { + Seeds, + AssetLists, + Rules, + Bundles, + MAX + }; + + void OnSupportClicked(); + + // Log + QSharedPointer m_logModel; + + void ShowLogContextMenu(const QPoint& pos); + + // Detecting Unsaved Changes + QSharedPointer m_unsavedChangesMsgBox; + + bool HasUnsavedChanges(); + void SaveCurrentSelection(); + void SaveAll(); + + AssetBundler::GUIApplicationManager* m_guiApplicationManager = nullptr; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/MainWindow.ui b/Code/Tools/AssetBundler/source/ui/MainWindow.ui new file mode 100644 index 0000000000..902652459f --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/MainWindow.ui @@ -0,0 +1,112 @@ + + + MainWindow + + + + 0 + 0 + 1200 + 1000 + + + + + 200 + 200 + + + + Asset Bundler + + + + + + + -1 + + + + + + + + 0 + 0 + + + + + + + + + + 0 + 0 + 1200 + 21 + + + + + File + + + + + + + + + Help + + + + + + + + + Save + + + + + Close + + + + + Documentation + + + + + About Asset Bundler + + + + + Save All + + + + + + AzQtComponents::TableView + QTreeView +
AzQtComponents/Components/Widgets/TableView.h
+ 1 +
+ + AzQtComponents::TabWidget + QTabWidget +
AzQtComponents/Components/Widgets/TabWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/NewFileDialog.cpp b/Code/Tools/AssetBundler/source/ui/NewFileDialog.cpp new file mode 100644 index 0000000000..170da4a659 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/NewFileDialog.cpp @@ -0,0 +1,224 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace AssetBundler +{ + NewFileDialog::NewFileDialog( + QWidget* parent, + const QString& dialogTitle, + const QString& startingPath, + const char* fileExtension, + const QString& fileNameFilter, + const AzFramework::PlatformFlags& enabledPlatforms, + bool isRunningRule) + : QDialog(parent) + , m_startingPath(startingPath) + , m_fileExtension(fileExtension) + { + m_ui.reset(new Ui::NewFileDialog); + m_ui->setupUi(this); + + setWindowTitle(dialogTitle); + + // Set up File Name section + m_ui->fileNameLineEdit->setEnabled(false); + connect(m_ui->browseButton, &QPushButton::clicked, this, &NewFileDialog::OnBrowseButtonPressed); + + m_newFileDialog.setFileMode(QFileDialog::AnyFile); + m_newFileDialog.setNameFilter(fileNameFilter); + m_newFileDialog.setViewMode(QFileDialog::Detail); + m_newFileDialog.setDirectory(m_startingPath); + // We are not creating a new file when Qt thinks we are, so we need to block signals or else the file watcher will be triggered too soon + m_newFileDialog.blockSignals(true); + + // Set up Platform selection + QString disabledPatformMessageOverride; + if (isRunningRule) + { + disabledPatformMessageOverride = tr("This platform is not valid for all input Asset Lists."); + } + m_ui->platformSelectionWidget->Init(enabledPlatforms, disabledPatformMessageOverride); + connect(m_ui->platformSelectionWidget, &PlatformSelectionWidget::PlatformsSelected, this, &NewFileDialog::OnPlatformSelectionChanged); + + // Set up Cancel and Create New File buttons + m_ui->createFileButton->setEnabled(false); + connect(m_ui->cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(m_ui->createFileButton, &QPushButton::clicked, this, &NewFileDialog::OnCreateFileButtonPressed); + } + + AZStd::string NewFileDialog::GetAbsoluteFilePath() + { + return m_absoluteFilePath; + } + + AzFramework::PlatformFlags NewFileDialog::GetPlatformFlags() + { + return m_ui->platformSelectionWidget->GetSelectedPlatforms(); + } + + void NewFileDialog::OnBrowseButtonPressed() + { + int result = m_newFileDialog.exec(); + if (result == QDialog::DialogCode::Accepted) + { + m_absoluteFilePath = m_newFileDialog.selectedFiles()[0].toUtf8().data(); + AzToolsFramework::RemovePlatformIdentifier(m_absoluteFilePath); + + if (m_fileExtension && !AzFramework::StringFunc::Path::HasExtension(m_absoluteFilePath.c_str())) + { + m_absoluteFilePath.append("."); + m_absoluteFilePath.append(m_fileExtension); + } + + m_ui->fileNameLineEdit->setEnabled(true); + m_ui->fileNameLineEdit->setText(m_absoluteFilePath.c_str()); + } + + m_fileNameIsValid = !m_absoluteFilePath.empty(); + m_ui->createFileButton->setEnabled(m_platformIsValid && m_fileNameIsValid); + } + + void NewFileDialog::OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms) + { + // Hide the "Create File" button if no platforms are selected + m_platformIsValid = selectedPlatforms != AzFramework::PlatformFlags::Platform_NONE; + m_ui->createFileButton->setEnabled(m_platformIsValid && m_fileNameIsValid); + } + + void NewFileDialog::OnCreateFileButtonPressed() + { + // Check to see if any of the selected platform-specific files already exist on-disk + QString overwriteExistingFilesList; + AZStd::fixed_vector selectedPlatformNames = AzFramework::PlatformHelper::GetPlatforms(GetPlatformFlags()); + for (const AZStd::string& platformName : selectedPlatformNames) + { + FilePath platformSpecificFilePath(GetAbsoluteFilePath(), platformName); + const char* platformSpecificFileAbsolutePath = platformSpecificFilePath.AbsolutePath().c_str(); + if (AZ::IO::FileIOBase::GetInstance()->Exists(platformSpecificFileAbsolutePath)) + { + overwriteExistingFilesList.append(QString("%1\n").arg(platformSpecificFileAbsolutePath)); + } + } + + // Ask the user if they are sure they want to overwrite existing files + if (!overwriteExistingFilesList.isEmpty()) + { + QString messageBoxText = QString(tr("The following files already exist on-disk. Generating new files will overwrite the existing ones.\n\n%1\n\nDo you wish to permanently delete the existing files?")).arg(overwriteExistingFilesList); + + QMessageBox::StandardButton confirmDeleteFileResult = QMessageBox::question(this, QString(tr("Replace Existing Files")), messageBoxText); + if (confirmDeleteFileResult != QMessageBox::StandardButton::Yes) + { + // User canceled out of the operation + return; + } + } + + emit QDialog::accept(); + } + + AZStd::string NewFileDialog::OSNewFileDialog(QWidget* parent, const char* fileExtension, const char* fileTypeDisplayName, const AZStd::string& startingDirectory) + { + QFileDialog filePathDialog(parent); + filePathDialog.setFileMode(QFileDialog::AnyFile); + filePathDialog.setNameFilter(QString(tr("%1 (*.%2)")).arg(fileTypeDisplayName).arg(fileExtension)); + filePathDialog.setViewMode(QFileDialog::Detail); + filePathDialog.setDirectory(startingDirectory.c_str()); + + // Since we handle file creation instead of the OS, we have to block signals or else the model will be reloaded, + // and our in-memory changes will be lost. + filePathDialog.blockSignals(true); + int result = filePathDialog.exec(); + if (result == QDialog::DialogCode::Rejected || filePathDialog.selectedFiles().empty()) + { + // User canceled out of the file dialog, cancel operation + return ""; + } + + AZStd::string absoluteFilePath(filePathDialog.selectedFiles()[0].toUtf8().data()); + if (!AzFramework::StringFunc::Path::HasExtension(absoluteFilePath.c_str())) + { + absoluteFilePath = AZStd::string::format("%s%c%s", absoluteFilePath.c_str(), AZ_FILESYSTEM_EXTENSION_SEPARATOR, fileExtension); + } + + return absoluteFilePath; + } + + int NewFileDialog::FileGenerationResultMessageBox(QWidget* parent, const AZStd::vector& generatedFiles, bool generatedWithErrors) + { + QMessageBox messageBox(parent); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.setDefaultButton(QMessageBox::Ok); + + if (generatedFiles.empty()) + { + messageBox.setText(QString("No files were generated. Please refer to the console for more information.")); + messageBox.setIcon(QMessageBox::Critical); + return messageBox.exec(); + } + + QString messageText; + if (generatedWithErrors) + { + messageText = QString("The following files were generated with errors. Please refer to the console for more information.\n\n"); + messageBox.setIcon(QMessageBox::Warning); + } + else + { + messageText = QString("You have successfully generated:\n\n"); + messageBox.setIcon(QMessageBox::NoIcon); + } + + AZStd::string fileName; + for (const AZStd::string& filePath : generatedFiles) + { + AzFramework::StringFunc::Path::GetFullFileName(filePath.c_str(), fileName); + messageText.append(QString("%1\n").arg(fileName.c_str())); + } + + AZStd::string extension; + AzFramework::StringFunc::Path::GetExtension(fileName.c_str(), extension, false); + if (extension == AzToolsFramework::AssetSeedManager::GetAssetListFileExtension()) + { + messageText.append("\nVisit the Asset Lists tab to see the lists."); + } + else if (extension == AzToolsFramework::AssetBundleSettings::GetBundleFileExtension()) + { + messageText.append("\nVisit the Completed Bundles tab to see the bundles."); + } + + messageBox.setText(messageText); + + // QMessageBoxes try to shrink to the smallest size possible, so we need to make a spacer + QSpacerItem* horizontalSpacer = new QSpacerItem(550, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + QGridLayout* layout = static_cast(messageBox.layout()); + layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount()); + + return messageBox.exec(); + } + +} //namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/NewFileDialog.h b/Code/Tools/AssetBundler/source/ui/NewFileDialog.h new file mode 100644 index 0000000000..5974d3da6a --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/NewFileDialog.h @@ -0,0 +1,73 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#include +#endif + +namespace Ui +{ + class NewFileDialog; +} + +namespace AssetBundler +{ + class GUIApplicationManager; + + class NewFileDialog + : public QDialog + { + Q_OBJECT + + public: + explicit NewFileDialog( + QWidget* parent, + const QString& dialogTitle, + const QString& startingPath, + const char* fileExtension, + const QString& fileNameFilter, + const AzFramework::PlatformFlags& enabledPlatforms, + bool isRunningRule = false); + virtual ~NewFileDialog() {} + + AZStd::string GetAbsoluteFilePath(); + AzFramework::PlatformFlags GetPlatformFlags(); + + //! A standard OS-specific New File Dialog, but blocks all Qt signals from the dialog and does NOT create a new file. + //! Use in place of the static QFileDialog functions to avoid unexpected file watcher updates. + //! Returns the absolute path of the file the user either selected or attempted to create, or an empty string if the user canceled out of the dialog. + static AZStd::string OSNewFileDialog(QWidget* parent, const char* fileExtension, const char* fileTypeDisplayName, const AZStd::string& startingDirectory); + + static int FileGenerationResultMessageBox(QWidget* parent, const AZStd::vector& generatedFiles, bool generatedWithErrors); + + private: + void OnBrowseButtonPressed(); + void OnPlatformSelectionChanged(const AzFramework::PlatformFlags& selectedPlatforms); + void OnCreateFileButtonPressed(); + + QSharedPointer m_ui; + QString m_startingPath; + const char* m_fileExtension; + QFileDialog m_newFileDialog; + + AZStd::string m_absoluteFilePath; + + bool m_fileNameIsValid = false; + bool m_platformIsValid = false; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/NewFileDialog.ui b/Code/Tools/AssetBundler/source/ui/NewFileDialog.ui new file mode 100644 index 0000000000..17d439549c --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/NewFileDialog.ui @@ -0,0 +1,154 @@ + + + NewFileDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 450 + 90 + + + + + 0 + 0 + + + + + 450 + 0 + + + + Dialog + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Save As: + + + + + + + true + + + + + + + Browse... + + + + + + + + + + + + + + + 10 + + + 10 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + false + + + + + + + Create New File + + + true + + + + + + + + + + AssetBundler::PlatformSelectionWidget + QWidget +
source/ui/PlatformSelectionWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.cpp b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.cpp new file mode 100644 index 0000000000..80b4590c3c --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.cpp @@ -0,0 +1,126 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +namespace AssetBundler +{ + + PlatformSelectionWidget::PlatformSelectionWidget(QWidget* parent) + : QWidget(parent) + { + m_ui.reset(new Ui::PlatformSelectionWidget); + m_ui->setupUi(this); + + m_platformHelper.reset(new AzFramework::PlatformHelper); + + m_selectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + m_partiallySelectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + } + + void PlatformSelectionWidget::Init(const AzFramework::PlatformFlags& enabledPlatforms, const QString& disabledPatformMessageOverride) + { + using namespace AzFramework; + + // Set up Platform Checkboxes + for (const AZStd::string_view& platformString : m_platformHelper->GetPlatforms(PlatformFlags::AllNamedPlatforms)) + { + // Create the CheckBox and store what platform it maps to + QSharedPointer platformCheckBox(new QCheckBox(QString::fromUtf8(platformString.data(), platformString.size()))); + m_platformCheckBoxes.push_back(platformCheckBox); + PlatformFlags currentPlatformFlag = m_platformHelper->GetPlatformFlag(platformString); + m_platformList.push_back(currentPlatformFlag); + + // Add the CheckBox to the view + m_ui->platformCheckBoxLayout->addWidget(platformCheckBox.data()); + + // If the platform is not enabled for the current project, disable it and tell the user + if ((enabledPlatforms & currentPlatformFlag) == PlatformFlags::Platform_NONE) + { + platformCheckBox->setEnabled(false); + if (disabledPatformMessageOverride.isEmpty()) + { + platformCheckBox->setToolTip(tr("This platform is not enabled for the current project.")); + } + else + { + platformCheckBox->setToolTip(disabledPatformMessageOverride); + } + } + else + { + connect(platformCheckBox.data(), &QCheckBox::stateChanged, this, &PlatformSelectionWidget::OnPlatformSelectionChanged); + } + } + } + + void PlatformSelectionWidget::SetSelectedPlatforms(const AzFramework::PlatformFlags& selectedPlatforms, const AzFramework::PlatformFlags& partiallySelectedPlatforms) + { + m_selectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + m_partiallySelectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + for (int checkBoxIndex = 0; checkBoxIndex < m_platformCheckBoxes.size(); ++checkBoxIndex) + { + AzFramework::PlatformFlags checkBoxPlatform = m_platformList[checkBoxIndex]; + bool isSelected = (checkBoxPlatform & selectedPlatforms) != AzFramework::PlatformFlags::Platform_NONE; + m_platformCheckBoxes[checkBoxIndex]->setChecked(isSelected); + if (isSelected) + { + m_selectedPlatforms |= checkBoxPlatform; + } + + // Set the check status to Qt::PartiallyChecked when some of the selected items support the current platform + bool isPartiallySelected = (checkBoxPlatform & partiallySelectedPlatforms) != AzFramework::PlatformFlags::Platform_NONE; + if (isPartiallySelected) + { + m_platformCheckBoxes[checkBoxIndex]->setCheckState(Qt::PartiallyChecked); + m_partiallySelectedPlatforms |= checkBoxPlatform; + } + } + + emit PlatformsSelected(m_selectedPlatforms, m_partiallySelectedPlatforms); + } + + AzFramework::PlatformFlags PlatformSelectionWidget::GetSelectedPlatforms() + { + return m_selectedPlatforms; + } + + AzFramework::PlatformFlags PlatformSelectionWidget::GetPartiallySelectedPlatforms() + { + return m_partiallySelectedPlatforms; + } + + void PlatformSelectionWidget::OnPlatformSelectionChanged() + { + m_selectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + m_partiallySelectedPlatforms = AzFramework::PlatformFlags::Platform_NONE; + for (int checkBoxIndex = 0; checkBoxIndex < m_platformCheckBoxes.size(); ++checkBoxIndex) + { + if (m_platformCheckBoxes[checkBoxIndex]->checkState() == Qt::Checked) + { + m_selectedPlatforms |= m_platformList[checkBoxIndex]; + } + else if (m_platformCheckBoxes[checkBoxIndex]->checkState() == Qt::PartiallyChecked) + { + m_partiallySelectedPlatforms |= m_platformList[checkBoxIndex]; + } + } + + emit PlatformsSelected(m_selectedPlatforms, m_partiallySelectedPlatforms); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.h b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.h new file mode 100644 index 0000000000..223044478f --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.h @@ -0,0 +1,62 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#include +#endif + +namespace Ui +{ + class PlatformSelectionWidget; +} + +namespace AssetBundler +{ + class PlatformSelectionWidget + : public QWidget + { + Q_OBJECT + + public: + explicit PlatformSelectionWidget(QWidget* parent); + virtual ~PlatformSelectionWidget() {} + + void Init(const AzFramework::PlatformFlags& enabledPlatforms, const QString& disabledPatformMessageOverride = ""); + + void SetSelectedPlatforms(const AzFramework::PlatformFlags& selectedPlatforms, const AzFramework::PlatformFlags& partiallySelectedPlatforms); + + AzFramework::PlatformFlags GetSelectedPlatforms(); + AzFramework::PlatformFlags GetPartiallySelectedPlatforms(); + + Q_SIGNALS: + void PlatformsSelected(AzFramework::PlatformFlags selectedPlatforms, AzFramework::PlatformFlags partiallySelectedPlatforms); + + private: + void OnPlatformSelectionChanged(); + + QSharedPointer m_ui; + AZStd::unique_ptr m_platformHelper; + + QVector> m_platformCheckBoxes; + AZStd::vector m_platformList; + + AzFramework::PlatformFlags m_selectedPlatforms; + AzFramework::PlatformFlags m_partiallySelectedPlatforms; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.ui b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.ui new file mode 100644 index 0000000000..d2ecb61e64 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/PlatformSelectionWidget.ui @@ -0,0 +1,49 @@ + + + PlatformSelectionWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Platforms: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + diff --git a/Code/Tools/AssetBundler/source/ui/RulesTabWidget.cpp b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.cpp new file mode 100644 index 0000000000..56447f4a57 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.cpp @@ -0,0 +1,432 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace AssetBundler +{ + RulesTabWidget::RulesTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager) + : AssetBundlerTabWidget(parent, guiApplicationManager) + { + m_ui.reset(new Ui::RulesTabWidget); + m_ui->setupUi(this); + + m_ui->mainVerticalLayout->setContentsMargins(MarginSize, MarginSize, MarginSize, MarginSize); + + m_fileTableModel.reset(new RulesFileTableModel); + m_ui->fileTableView->setModel(m_fileTableModel.data()); + + // Table View of all Rules files + m_fileTableFilterModel.reset(new AssetBundlerFileTableFilterModel(this, m_fileTableModel->GetFileNameColumnIndex(), m_fileTableModel->GetTimeStampColumnIndex())); + + m_fileTableFilterModel->setSourceModel(m_fileTableModel.data()); + m_ui->fileTableView->setModel(m_fileTableFilterModel.data()); + connect(m_ui->fileFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_fileTableFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + connect(m_ui->fileTableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RulesTabWidget::FileSelectionChanged); + + m_ui->fileTableView->setIndentation(0); + + // New File Button + connect(m_ui->createNewFileButton, &QPushButton::clicked, this, &RulesTabWidget::OnNewFileButtonPressed); + + // Run Rule Button + m_ui->runRuleButton->setEnabled(false); + connect(m_ui->runRuleButton, &QPushButton::clicked, this, &RulesTabWidget::OnRunRuleButtonPressed); + + // Add Comparison Step button + connect(m_ui->addComparisonStepButton, &QPushButton::clicked, this, &RulesTabWidget::AddNewComparisonStep); + + SetModelDataSource(); + } + + bool RulesTabWidget::HasUnsavedChanges() + { + return m_fileTableModel->HasUnsavedChanges(); + } + + void RulesTabWidget::Reload() + { + m_fileTableModel->Reload(AzToolsFramework::AssetFileInfoListComparison::GetComparisonRulesFileExtension(), m_watchedFolders, m_watchedFiles); + FileSelectionChanged(); + } + + bool RulesTabWidget::SaveCurrentSelection() + { + return m_fileTableModel->Save(m_selectedFileTableIndex); + } + + bool RulesTabWidget::SaveAll() + { + return m_fileTableModel->SaveAll(); + } + + void RulesTabWidget::SetModelDataSource() + { + // Remove the current watched folders and files + m_guiApplicationManager->RemoveWatchedPaths(m_watchedFolders + m_watchedFiles); + + // Set the new watched folder for the model + m_watchedFolders.clear(); + m_watchedFiles.clear(); + m_watchedFolders.insert(m_guiApplicationManager->GetRulesFolder().c_str()); + ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType::RulesFileType); + + m_guiApplicationManager->AddWatchedPaths(m_watchedFolders + m_watchedFiles); + } + + AzQtComponents::TableView* RulesTabWidget::GetFileTableView() + { + return m_ui->fileTableView; + } + + QModelIndex RulesTabWidget::GetSelectedFileTableIndex() + { + return m_selectedFileTableIndex; + } + + AssetBundlerAbstractFileTableModel* RulesTabWidget::GetFileTableModel() + { + return m_fileTableModel.get(); + } + + void RulesTabWidget::SetActiveProjectLabel(const QString& labelText) + { + m_ui->activeProjectLabel->setText(labelText); + } + + void RulesTabWidget::ApplyConfig() + { + const GUIApplicationManager::Config& config = m_guiApplicationManager->GetConfig(); + m_ui->fileTableFrame->setFixedWidth(config.fileTableWidth); + m_ui->fileTableView->header()->resizeSection(RulesFileTableModel::Column::ColumnFileName, config.fileNameColumnWidth); + } + + + void RulesTabWidget::FileSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) + { + if (m_ui->fileTableView->selectionModel()->selectedRows().size() == 0) + { + m_selectedFileTableIndex = QModelIndex(); + m_selectedComparisonRules = nullptr; + m_ui->runRuleButton->setEnabled(false); + m_ui->addComparisonStepButton->setEnabled(false); + m_ui->rulesFileAbsolutePathLabel->clear(); + RemoveAllComparisonDataCards(); + + return; + } + + m_ui->runRuleButton->setEnabled(true); + m_ui->addComparisonStepButton->setEnabled(true); + + m_selectedFileTableIndex = m_fileTableFilterModel->mapToSource(m_ui->fileTableView->selectionModel()->selectedRows()[0]); + m_selectedComparisonRules = m_fileTableModel->GetComparisonSteps(m_selectedFileTableIndex); + + m_ui->rulesFileAbsolutePathLabel->setText(m_fileTableModel->GetFileAbsolutePath(m_selectedFileTableIndex).c_str()); + + RebuildComparisonDataCardList(); + } + + void RulesTabWidget::OnNewFileButtonPressed() + { + AZStd::string absoluteFilePath = NewFileDialog::OSNewFileDialog( + this, + AzToolsFramework::AssetFileInfoListComparison::GetComparisonRulesFileExtension(), + "Comparison Rules", + m_guiApplicationManager->GetRulesFolder()); + + if (absoluteFilePath.empty()) + { + // User canceled out of the dialog + return; + } + + AZStd::vector createdFile = m_fileTableModel->CreateNewFiles(absoluteFilePath); + if (!createdFile.empty()) + { + AddScanPathToAssetBundlerSettings(AssetBundlingFileType::RulesFileType, createdFile.at(0)); + } + } + + void RulesTabWidget::OnRunRuleButtonPressed() + { + using namespace AzToolsFramework; + + // Determine which platforms all of the input Asset List files have in common + AzFramework::PlatformFlags commonPlatforms = AzFramework::PlatformFlags::AllNamedPlatforms; + for (const AssetFileInfoListComparison::ComparisonData& comparisonStep : m_selectedComparisonRules->GetComparisonList()) + { + if (!comparisonStep.m_cachedFirstInputPath.empty()) + { + commonPlatforms = commonPlatforms & GetPlatformsOnDiskForPlatformSpecificFile(comparisonStep.m_cachedFirstInputPath); + } + + if (!comparisonStep.m_cachedSecondInputPath.empty()) + { + commonPlatforms = commonPlatforms & GetPlatformsOnDiskForPlatformSpecificFile(comparisonStep.m_cachedSecondInputPath); + } + } + + if (commonPlatforms == AzFramework::PlatformFlags::Platform_NONE) + { + AZ_Error("AssetBundler", false, "Unable to run Rule: Input Asset List files have no platforms in common."); + return; + } + + // Prompt the user to select an output path and the platforms to run the rule on + NewFileDialog runRuleDialog = NewFileDialog( + this, + "Run Rule", + QString(m_guiApplicationManager->GetAssetListsFolder().c_str()), + AzToolsFramework::AssetSeedManager::GetAssetListFileExtension(), + QString(tr("Asset List (*.%1)")).arg(AzToolsFramework::AssetSeedManager::GetAssetListFileExtension()), + commonPlatforms, + true); + + auto dialogResponse = runRuleDialog.exec(); + if (dialogResponse == QDialog::DialogCode::Rejected) + { + // User canceled the operation + return; + } + + AZStd::vector outputFilePaths; + bool hasFileGenerationErrors = false; + + AZStd::fixed_vector selectedPlatformNames = AzFramework::PlatformHelper::GetPlatforms(runRuleDialog.GetPlatformFlags()); + for (const AZStd::string& platformName : selectedPlatformNames) + { + // We do not want to modify the original Rules file, as we do not save Asset List file paths to disk + AssetFileInfoListComparison currentFileCopy = *m_selectedComparisonRules.get(); + + //Update the first and second input values with any non-token Asset List file paths that have been set, + size_t numComparisonSteps = currentFileCopy.GetNumComparisonSteps(); + for (size_t comparisonStepIndex = 0; comparisonStepIndex < numComparisonSteps; ++comparisonStepIndex) + { + AssetFileInfoListComparison::ComparisonData comparisonStep = currentFileCopy.GetComparisonList()[comparisonStepIndex]; + if (comparisonStep.m_firstInput.empty()) + { + if (comparisonStep.m_cachedFirstInputPath.empty()) + { + AZ_Error("AssetBundler", false, "Unable to run Rule: Comparison Step #%u has no specified first input.", comparisonStepIndex); + return; + } + + FilePath firstInput = FilePath(comparisonStep.m_cachedFirstInputPath, platformName); + currentFileCopy.SetFirstInput(comparisonStepIndex, firstInput.AbsolutePath()); + } + + if (comparisonStep.m_comparisonType != AssetFileInfoListComparison::ComparisonType::FilePattern + && comparisonStep.m_secondInput.empty()) + { + if (comparisonStep.m_cachedSecondInputPath.empty()) + { + AZ_Error("AssetBundler", false, "Unable to run Rule: Comparison Step #%u has no specified second input.", comparisonStepIndex); + return; + } + + FilePath secondInput = FilePath(comparisonStep.m_cachedSecondInputPath, platformName); + currentFileCopy.SetSecondInput(comparisonStepIndex, secondInput.AbsolutePath()); + } + } + + // Set the output location of the Asset List file that will be generated + FilePath finalOutputPath = FilePath(runRuleDialog.GetAbsoluteFilePath(), platformName); + currentFileCopy.SetOutput(numComparisonSteps - 1, finalOutputPath.AbsolutePath()); + + //Run the Rule + auto compareOutcome = currentFileCopy.CompareAndSaveResults(); + if (compareOutcome.IsSuccess()) + { + outputFilePaths.emplace_back(finalOutputPath.AbsolutePath()); + } + else + { + hasFileGenerationErrors = true; + AZ_Error("AssetBundler", false, compareOutcome.GetError().c_str()); + } + } + + // Add created files to the file watcher + for (const AZStd::string& absolutePath : outputFilePaths) + { + AddScanPathToAssetBundlerSettings(AssetBundlingFileType::AssetListFileType, absolutePath); + } + + // The watched files list was updated after the files were created, so we need to force-reload them + m_guiApplicationManager->UpdateFiles(AssetBundlingFileType::AssetListFileType, outputFilePaths); + + NewFileDialog::FileGenerationResultMessageBox(this, outputFilePaths, hasFileGenerationErrors); + } + + void RulesTabWidget::MarkFileChanged() + { + m_fileTableModel->MarkFileChanged(m_selectedFileTableIndex); + } + + void RulesTabWidget::RebuildComparisonDataCardList() + { + RemoveAllComparisonDataCards(); + PopulateComparisonDataCardList(); + } + + void RulesTabWidget::PopulateComparisonDataCardList() + { + if (!m_selectedComparisonRules) + { + return; + } + + for (int index = 0; index < m_selectedComparisonRules->GetComparisonList().size(); ++index) + { + CreateComparisonDataCard(m_selectedComparisonRules, static_cast(index)); + } + } + + void RulesTabWidget::CreateComparisonDataCard(AZStd::shared_ptr comparisonList, size_t comparisonDataIndex) + { + ComparisonDataCard* comparisonDataCard = new ComparisonDataCard(comparisonList, comparisonDataIndex, m_guiApplicationManager->GetAssetListsFolder()); + comparisonDataCard->setTitle(tr("Step %1").arg(static_cast(comparisonDataIndex) + 1)); + m_ui->comparisonDataListLayout->addWidget(comparisonDataCard); + m_comparisonDataCardList.push_back(comparisonDataCard); + + ComparisonDataWidget* comparisonDataWidget = comparisonDataCard->GetComparisonDataWidget(); + connect(comparisonDataCard, &ComparisonDataCard::comparisonDataCardContextMenuRequested, this, &RulesTabWidget::OnComparisonDataCardContextMenuRequested); + connect(comparisonDataWidget, &ComparisonDataWidget::comparisonDataChanged, this, &RulesTabWidget::MarkFileChanged); + connect(comparisonDataWidget, &ComparisonDataWidget::comparisonDataTokenNameChanged, this, &RulesTabWidget::OnAnyTokenNameChanged); + + comparisonDataCard->show(); + } + + void RulesTabWidget::RemoveAllComparisonDataCards() + { + m_comparisonDataCardList.clear(); + + while (!m_ui->comparisonDataListLayout->isEmpty()) + { + QLayoutItem* item = m_ui->comparisonDataListLayout->takeAt(0); + item->widget()->hide(); + delete item; + } + } + + void RulesTabWidget::AddNewComparisonStep() + { + if (!m_selectedComparisonRules) + { + return; + } + + if (!m_selectedComparisonRules->AddComparisonStep(AzToolsFramework::AssetFileInfoListComparison::ComparisonData())) + { + return; + } + + CreateComparisonDataCard(m_selectedComparisonRules, m_selectedComparisonRules->GetComparisonList().size() - 1); + MarkFileChanged(); + } + + void RulesTabWidget::RemoveComparisonStep(size_t comparisonDataIndex) + { + if (!m_selectedComparisonRules) + { + return; + } + + if (m_selectedComparisonRules->RemoveComparisonStep(comparisonDataIndex)) + { + MarkFileChanged(); + RebuildComparisonDataCardList(); + } + } + + void RulesTabWidget::MoveComparisonStep(size_t startingIndex, size_t destinationIndex) + { + if (!m_selectedComparisonRules) + { + return; + } + + if (m_selectedComparisonRules->MoveComparisonStep(startingIndex, destinationIndex)) + { + MarkFileChanged(); + RebuildComparisonDataCardList(); + } + } + + void RulesTabWidget::OnAnyTokenNameChanged(size_t comparisonDataIndex) + { + if (comparisonDataIndex >= m_comparisonDataCardList.size() - 1) + { + return; + } + + for (size_t i = comparisonDataIndex + 1; i < m_comparisonDataCardList.size(); ++i) + { + m_comparisonDataCardList[i]->GetComparisonDataWidget()->UpdateListOfTokenNames(); + } + } + + void RulesTabWidget::OnComparisonDataCardContextMenuRequested(size_t comparisonDataIndex, const QPoint& position) + { + if (!m_selectedComparisonRules) + { + return; + } + + QMenu menu; + + QAction* moveUpAction = new QAction(tr("Move Up"), this); + moveUpAction->setEnabled(comparisonDataIndex > 0); + connect(moveUpAction, &QAction::triggered, this, [=]() { MoveComparisonStep(comparisonDataIndex, comparisonDataIndex - 1); }); + menu.addAction(moveUpAction); + + QAction* moveDownAction = new QAction(tr("Move Down"), this); + moveDownAction->setEnabled(comparisonDataIndex < m_selectedComparisonRules->GetNumComparisonSteps() - 1); + connect(moveDownAction, &QAction::triggered, this, [=]() { MoveComparisonStep(comparisonDataIndex, comparisonDataIndex + 2); }); + menu.addAction(moveDownAction); + + QAction* separator = new QAction(this); + separator->setSeparator(true); + menu.addAction(separator); + + QAction* deleteAction = new QAction(tr("Remove Comparison Step"), this); + connect(deleteAction, &QAction::triggered, this, [=]() { RemoveComparisonStep(comparisonDataIndex); }); + menu.addAction(deleteAction); + + menu.exec(position); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/RulesTabWidget.h b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.h new file mode 100644 index 0000000000..7dedce3efe --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.h @@ -0,0 +1,120 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include + +#include +#include +#include +#include +#include +#endif + +namespace Ui +{ + class RulesTabWidget; +} + +class QItemSelection; + +namespace AzToolsFramework +{ + class AssetFileInfoListComparison; +} + +namespace AssetBundler +{ + class GUIApplicationManager; + + class RulesFileTableModel; + + class ComparisonDataCard; + + class ComparisonDataWidget; + + class RulesTabWidget + : public AssetBundlerTabWidget + { + Q_OBJECT + + public: + explicit RulesTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager); + virtual ~RulesTabWidget() {} + + QString GetTabTitle() override { return tr("Rules"); } + + QString GetFileTypeDisplayName() override { return tr("Rules file"); } + + AssetBundlingFileType GetFileType() override { return AssetBundlingFileType::RulesFileType; } + + bool HasUnsavedChanges() override; + + void Reload() override; + + bool SaveCurrentSelection() override; + + bool SaveAll() override; + + void SetModelDataSource() override; + + AzQtComponents::TableView* GetFileTableView() override; + + QModelIndex GetSelectedFileTableIndex() override; + + AssetBundlerAbstractFileTableModel* GetFileTableModel() override; + + void SetActiveProjectLabel(const QString& labelText) override; + + void ApplyConfig() override; + + void FileSelectionChanged(const QItemSelection& /*selected*/ = QItemSelection(), const QItemSelection& /*deselected*/ = QItemSelection()) override; + + private: + void OnNewFileButtonPressed(); + + void OnRunRuleButtonPressed(); + + void MarkFileChanged(); + + void RebuildComparisonDataCardList(); + + void PopulateComparisonDataCardList(); + + void CreateComparisonDataCard(AZStd::shared_ptr comparisonList, size_t comparisonDataIndex); + + void RemoveAllComparisonDataCards(); + + void AddNewComparisonStep(); + + void RemoveComparisonStep(size_t comparisonDataIndex); + + void MoveComparisonStep(size_t startingIndex, size_t destinationIndex); + + void OnAnyTokenNameChanged(size_t comparisonDataIndex); + + QSharedPointer m_ui; + + QSharedPointer m_fileTableModel; + QModelIndex m_selectedFileTableIndex; + AZStd::shared_ptr m_selectedComparisonRules; + + AZStd::vector m_comparisonDataCardList; + + private slots: + void OnComparisonDataCardContextMenuRequested(size_t comparisonDataIndex, const QPoint& position); + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/RulesTabWidget.ui b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.ui new file mode 100644 index 0000000000..f7eec0a796 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/RulesTabWidget.ui @@ -0,0 +1,312 @@ + + + RulesTabWidget + + + + 0 + 0 + 1180 + 728 + + + + Rules Tab Widget + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run Selected Rule + + + true + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Rules + + + + + + + + + + Filter by file name + + + + + + + 5 + + + 5 + + + + + + Create new Rules file + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Comparison Steps + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 5 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Add Step + + + + + + + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 573 + 614 + + + + + 0 + 0 + + + + + 12 + + + 10 + + + 10 + + + 10 + + + 0 + + + + + + + + Qt::Vertical + + + + 40 + 20 + + + + + + + + + + + + + + + + + + AzQtComponents::TableView + QTreeView +
AzQtComponents/Components/Widgets/TableView.h
+ 1 +
+ + AzQtComponents::FilteredSearchWidget + QTreeView +
AzQtComponents/Components/FilteredSearchWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/SeedTabWidget.cpp b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.cpp new file mode 100644 index 0000000000..b05a2e0c90 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.cpp @@ -0,0 +1,453 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +const char GenerateAssetListFilesDialogName[] = "Generate Asset List Files"; + +const int CheckBoxTableIndentationSize = 2; // when the indentation is 0, the checkboxes are too close to the edge + +namespace AssetBundler +{ + SeedTabWidget::SeedTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager, const QString& assetBundlingDirectory) + : AssetBundlerTabWidget(parent, guiApplicationManager) + , m_fileTableModel(new SeedListFileTableModel(this)) + , m_seedListContentsModel(new SeedListTableModel()) + { + AZ_UNUSED(assetBundlingDirectory); + + m_ui.reset(new Ui::SeedTabWidget); + m_ui->setupUi(this); + + m_ui->mainVerticalLayout->setContentsMargins(MarginSize, MarginSize, MarginSize, MarginSize); + + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + // File view of all Seed List Files + m_fileTableFilterModel.reset(new AssetBundlerFileTableFilterModel(this, m_fileTableModel->GetFileNameColumnIndex(), m_fileTableModel->GetTimeStampColumnIndex())); + + m_fileTableFilterModel->setSourceModel(m_fileTableModel.data()); + m_ui->fileTableView->setModel(m_fileTableFilterModel.data()); + connect(m_ui->fileFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_fileTableFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + connect(m_ui->fileTableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SeedTabWidget::FileSelectionChanged); + + m_ui->fileTableView->setIndentation(CheckBoxTableIndentationSize); + + // New File Button + connect(m_ui->createNewSeedListButton, &QPushButton::clicked, this, &SeedTabWidget::OnNewFileButtonPressed); + + // Select Default Seed Lists + connect(m_ui->selectDefaultSeedListsCheckBox, &QCheckBox::clicked, this, &SeedTabWidget::OnSelectDefaultSeedListsCheckBoxChanged); + + // Generate Asset Lists Button + SetGenerateAssetListsButtonEnabled(false); + connect(m_ui->generateAssetListsButton, &QPushButton::clicked, this, &SeedTabWidget::OnGenerateAssetListsButtonPressed); + + // Table that displays the contents of a Seed List File + m_seedListContentsFilterModel.reset(new AssetBundlerFileTableFilterModel(this, SeedListTableModel::Column::ColumnRelativePath)); + + m_seedListContentsFilterModel->setSourceModel(m_seedListContentsModel.data()); + m_ui->seedFileContentsTable->setModel(m_seedListContentsFilterModel.data()); + connect(m_ui->seedListContentsFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, + m_seedListContentsFilterModel.data(), static_cast(&AssetBundlerFileTableFilterModel::FilterChanged)); + + m_ui->seedFileContentsTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui->seedFileContentsTable, &QTreeView::customContextMenuRequested, this, &SeedTabWidget::OnSeedListContentsTableContextMenuRequested); + + m_ui->seedFileContentsTable->setIndentation(0); + + // Edit All Platforms + connect(m_ui->editAllSeedsButton, &QPushButton::clicked, this, &SeedTabWidget::OnEditAllButtonPressed); + + // Add Seed + connect(m_ui->addSeedButton, &QPushButton::clicked, this, &SeedTabWidget::OnAddSeedButtonPressed); + + SetModelDataSource(); + } + + SeedTabWidget::~SeedTabWidget() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + bool SeedTabWidget::HasUnsavedChanges() + { + return m_fileTableModel->HasUnsavedChanges(); + } + + void SeedTabWidget::Reload() + { + // Reload all the seed list files + m_fileTableModel->Reload(AzToolsFramework::AssetSeedManager::GetSeedFileExtension(), m_watchedFolders, m_watchedFiles, m_filePathToGemNameMap); + + // Update the selected row + FileSelectionChanged(); + } + + bool SeedTabWidget::SaveCurrentSelection() + { + return m_fileTableModel->Save(m_selectedFileTableIndex); + } + + bool SeedTabWidget::SaveAll() + { + return m_fileTableModel->SaveAll(); + } + + void SeedTabWidget::SetModelDataSource() + { + // Remove the current watched folders and files + m_guiApplicationManager->RemoveWatchedPaths(m_watchedFolders + m_watchedFiles); + + // Set the new watched folders for the model + m_watchedFolders.clear(); + m_watchedFolders.insert(m_guiApplicationManager->GetSeedListsFolder().c_str()); + + // Get the list of default Seed List files + m_filePathToGemNameMap = AssetBundler::GetDefaultSeedListFiles(GetCachedEngineRoot().c_str(), m_guiApplicationManager->GetCurrentProjectName(), + m_guiApplicationManager->GetGemInfoList(), m_guiApplicationManager->GetEnabledPlatforms()); + + // Get the list of default Seeds that are not stored in a Seed List file on-disk + AZStd::vector defaultSeeds = GetDefaultSeeds(AZ::Utils::GetProjectPath(), m_guiApplicationManager->GetCurrentProjectName()); + m_fileTableModel->AddDefaultSeedsToInMemoryList(defaultSeeds, m_guiApplicationManager->GetCurrentProjectName().c_str(), m_guiApplicationManager->GetEnabledPlatforms()); + + // Set the new watched filess for the model + m_watchedFiles.clear(); + for (const auto& it : m_filePathToGemNameMap) + { + m_watchedFiles.insert(it.first.c_str()); + } + + ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType::SeedListFileType); + m_guiApplicationManager->AddWatchedPaths(m_watchedFolders + m_watchedFiles); + } + + AzQtComponents::TableView* SeedTabWidget::GetFileTableView() + { + return m_ui->fileTableView; + } + + QModelIndex SeedTabWidget::GetSelectedFileTableIndex() + { + return m_selectedFileTableIndex; + } + + AssetBundlerAbstractFileTableModel* SeedTabWidget::GetFileTableModel() + { + return m_fileTableModel.get(); + } + + void SeedTabWidget::SetActiveProjectLabel(const QString& labelText) + { + m_ui->activeProjectLabel->setText(labelText); + } + + void SeedTabWidget::ApplyConfig() + { + const GUIApplicationManager::Config& config = m_guiApplicationManager->GetConfig(); + + m_ui->fileTableFrame->setFixedWidth(config.fileTableWidth); + m_ui->fileTableView->header()->resizeSection(SeedListFileTableModel::Column::ColumnFileName, config.seedListFileNameColumnWidth); + m_ui->fileTableView->header()->resizeSection(SeedListFileTableModel::Column::ColumnCheckBox, config.checkBoxColumnWidth); + m_ui->fileTableView->header()->resizeSection(SeedListFileTableModel::Column::ColumnProject, config.projectNameColumnWidth); + + m_ui->seedFileContentsTable->header()->resizeSection(SeedListTableModel::Column::ColumnRelativePath, config.seedListContentsNameColumnWidth); + } + + void SeedTabWidget::UncheckSelectDefaultSeedListsCheckBox() + { + m_ui->selectDefaultSeedListsCheckBox->setChecked(false); + } + + void SeedTabWidget::SetGenerateAssetListsButtonEnabled(bool isEnabled) + { + m_ui->generateAssetListsButton->setEnabled(isEnabled); + } + + bool SeedTabWidget::OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) + { + m_hasWarnings = true; + return false; + } + + bool SeedTabWidget::OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) + { + m_hasWarnings = true; + return false; + } + + void SeedTabWidget::FileSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) + { + if (m_ui->fileTableView->selectionModel()->selectedRows().size() == 0) + { + // Set selected index to an invalid value + m_selectedFileTableIndex = QModelIndex(); + m_ui->seedListFileAbsolutePathLabel->clear(); + return; + } + + m_selectedFileTableIndex = m_fileTableFilterModel->mapToSource(m_ui->fileTableView->selectionModel()->currentIndex()); + + m_seedListContentsModel = m_fileTableModel->GetSeedListFileContents(m_selectedFileTableIndex); + m_seedListContentsFilterModel->setSourceModel(m_seedListContentsModel.data()); + + m_ui->seedListFileAbsolutePathLabel->setText(m_fileTableModel->GetFileAbsolutePath(m_selectedFileTableIndex).c_str()); + } + + void SeedTabWidget::OnNewFileButtonPressed() + { + AZStd::string absoluteFilePath = NewFileDialog::OSNewFileDialog( + this, + AzToolsFramework::AssetSeedManager::GetSeedFileExtension(), + "Seed List", + m_guiApplicationManager->GetSeedListsFolder()); + + if (absoluteFilePath.empty()) + { + // User canceled out of the file dialog + return; + } + + AZStd::vector createdFiles = m_fileTableModel->CreateNewFiles( + absoluteFilePath, + AzFramework::PlatformFlags::Platform_NONE, + QString(m_guiApplicationManager->GetCurrentProjectName().c_str())); + + if (!createdFiles.empty()) + { + AddScanPathToAssetBundlerSettings(AssetBundlingFileType::SeedListFileType, createdFiles.at(0)); + } + } + + void SeedTabWidget::OnSelectDefaultSeedListsCheckBoxChanged() + { + m_fileTableModel->SelectDefaultSeedLists(m_ui->selectDefaultSeedListsCheckBox->isChecked()); + } + + void SeedTabWidget::OnGenerateAssetListsButtonPressed() + { + m_generateAssetListsDialog.reset( + new NewFileDialog( + this, + GenerateAssetListFilesDialogName, + QString(m_guiApplicationManager->GetAssetListsFolder().c_str()), + AzToolsFramework::AssetSeedManager::GetAssetListFileExtension(), + QString(tr("Asset List (*.%1)")).arg(AzToolsFramework::AssetSeedManager::GetAssetListFileExtension()), + m_guiApplicationManager->GetEnabledPlatforms())); + auto dialogResponse = m_generateAssetListsDialog->exec(); + if (dialogResponse == QDialog::DialogCode::Rejected) + { + // User canceled the operation + return; + } + + m_hasWarnings = false; + auto createdFiles = m_fileTableModel->GenerateAssetLists(m_generateAssetListsDialog->GetAbsoluteFilePath(), m_generateAssetListsDialog->GetPlatformFlags()); + + // Warnings will not prevent the generation of Asset List files, we must track them separately + NewFileDialog::FileGenerationResultMessageBox(this, createdFiles, m_hasWarnings); + + if (createdFiles.empty()) + { + // Error has already been thrown + return; + } + + // Add created files to the file watcher + for (const AZStd::string& absolutePath : createdFiles) + { + AddScanPathToAssetBundlerSettings(AssetBundlingFileType::AssetListFileType, absolutePath); + } + + // The watched files list was updated after the files were created, so we need to force-reload them + m_guiApplicationManager->UpdateFiles(AssetBundlingFileType::AssetListFileType, createdFiles); + } + + void SeedTabWidget::OnEditSeedButtonPressed() + { + if (!m_selectedFileTableIndex.isValid()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot perform Edit Seed operation: No Seed List File is selected"); + return; + } + + // Get the current platforms of the selected Seed so we can display them as already checked + QModelIndex currentSeedIndex = m_seedListContentsFilterModel->mapToSource(m_ui->seedFileContentsTable->selectionModel()->currentIndex()); + auto getPlatformOutcome = m_seedListContentsModel->GetSeedPlatforms(currentSeedIndex); + if (!getPlatformOutcome.IsSuccess()) + { + // Error has already been thrown + return; + } + + // Create and display the Edit Seed dialog + m_editSeedDialog.reset(new EditSeedDialog(this, m_guiApplicationManager->GetEnabledPlatforms(), getPlatformOutcome.GetValue())); + auto dialogResponse = m_editSeedDialog->exec(); + if (dialogResponse == QDialog::DialogCode::Rejected) + { + // User canceled the operation + return; + } + + // Set the data in the model + m_fileTableModel->SetSeedPlatforms(m_selectedFileTableIndex, currentSeedIndex, m_editSeedDialog->GetPlatformFlags()); + } + + void SeedTabWidget::OnEditAllButtonPressed() + { + if (!m_selectedFileTableIndex.isValid()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot perform Edit All operation: No Seed List File is selected"); + return; + } + + // Get the platforms of all the seeds so we can display them as already checked or partially checked + AzFramework::PlatformFlags selectedPlatformFlags = AzFramework::PlatformFlags::AllNamedPlatforms; + AzFramework::PlatformFlags partiallySelectedPlatformFlags = AzFramework::PlatformFlags::Platform_NONE; + QMap indexToPlatformFlagsMap; + for (int row = 0; row < m_seedListContentsModel->rowCount(); ++row) + { + QModelIndex currentSeedIndex = m_seedListContentsModel->index(row, 0); + auto getPlatformOutcome = m_seedListContentsModel->GetSeedPlatforms(currentSeedIndex); + if (!getPlatformOutcome.IsSuccess()) + { + // Error has already been thrown + return; + } + + indexToPlatformFlagsMap[currentSeedIndex] = getPlatformOutcome.TakeValue(); + selectedPlatformFlags &= indexToPlatformFlagsMap[currentSeedIndex]; + partiallySelectedPlatformFlags |= selectedPlatformFlags ^ indexToPlatformFlagsMap[currentSeedIndex]; + } + + // Create and display the Edit Seed dialog + m_editSeedDialog.reset(new EditSeedDialog( + this, + m_guiApplicationManager->GetEnabledPlatforms(), + selectedPlatformFlags, + partiallySelectedPlatformFlags)); + + auto dialogResponse = m_editSeedDialog->exec(); + if (dialogResponse == QDialog::DialogCode::Rejected) + { + // User canceled the operation + return; + } + + // Set the data in the model + for (int row = 0; row < m_seedListContentsModel->rowCount(); ++row) + { + QModelIndex currentSeedIndex = m_seedListContentsModel->index(row, 0); + AzFramework::PlatformFlags checkedPlatforms = m_editSeedDialog->GetPlatformFlags(); + AzFramework::PlatformFlags partiallyCheckedPlatforms = m_editSeedDialog->GetPartiallySelectedPlatformFlags(); + // If the platform is partially checked, we want to keep its original status when saving the changes + AzFramework::PlatformFlags platformFlags = indexToPlatformFlagsMap[currentSeedIndex] & partiallyCheckedPlatforms | checkedPlatforms; + + m_fileTableModel->SetSeedPlatforms(m_selectedFileTableIndex, currentSeedIndex, platformFlags); + } + } + + void SeedTabWidget::OnAddSeedButtonPressed() + { + if (!m_selectedFileTableIndex.isValid()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot perform Add Seed operation: No Seed List File is selected"); + return; + } + + // Get path to the platform-specific cache folder of one of the enabled platforms + AzFramework::PlatformFlags enabledPlatforms = m_guiApplicationManager->GetEnabledPlatforms(); + AZStd::fixed_vector enabledPlatformIndices = AzFramework::PlatformHelper::GetPlatformIndicesInterpreted(enabledPlatforms); + AZStd::string platformSpecificCachePath = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(enabledPlatformIndices[0]); + + // Create and display the Add Seed Dialog + m_addSeedDialog.reset(new AddSeedDialog(this, enabledPlatforms, platformSpecificCachePath)); + auto dialogResponse = m_addSeedDialog->exec(); + if (dialogResponse == QDialog::DialogCode::Rejected) + { + // User canceled the operation + return; + } + + // Set the data in the model + m_fileTableModel->AddSeed(m_selectedFileTableIndex, m_addSeedDialog->GetFileName(), m_addSeedDialog->GetPlatformFlags()); + } + + void SeedTabWidget::OnRemoveSeedButtonPressed() + { + if (!m_selectedFileTableIndex.isValid()) + { + AZ_Error(AssetBundler::AppWindowName, false, "Cannot perform Remove Seed operation: No Seed List File is selected"); + return; + } + + // Set the data in the model + QModelIndex currentSeedIndex = m_seedListContentsFilterModel->mapToSource(m_ui->seedFileContentsTable->selectionModel()->currentIndex()); + m_fileTableModel->RemoveSeed(m_selectedFileTableIndex, currentSeedIndex); + } + + void SeedTabWidget::OnSeedListContentsTableContextMenuRequested(const QPoint& pos) + { + if (!m_selectedFileTableIndex.isValid()) + { + return; + } + + QMenu* contextMenu = new QMenu(this); + contextMenu->setToolTipsVisible(true); + + QAction* action = contextMenu->addAction(tr("Edit Platforms")); + connect(action, &QAction::triggered, this, &SeedTabWidget::OnEditSeedButtonPressed); + action->setToolTip(tr("Change what platforms are referenced when generating an Asset List file.")); + + contextMenu->addSeparator(); + + action = contextMenu->addAction(tr("Add Seed")); + connect(action, &QAction::triggered, this, &SeedTabWidget::OnAddSeedButtonPressed); + action->setToolTip(tr("Add a new Seed to the Seed List file.")); + + action = contextMenu->addAction(tr("Remove Seed")); + connect(action, &QAction::triggered, this, &SeedTabWidget::OnRemoveSeedButtonPressed); + action->setToolTip(tr("Removes the Seed from the Seed List file.")); + + contextMenu->exec(m_ui->seedFileContentsTable->mapToGlobal(pos)); + delete contextMenu; + } + +} //namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/ui/SeedTabWidget.h b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.h new file mode 100644 index 0000000000..c1383cdd08 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.h @@ -0,0 +1,129 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#endif + +namespace Ui +{ + class SeedTabWidget; +} + +class QFileSystemModel; +class QStringListModel; + +namespace AssetBundler +{ + class GUIApplicationManager; + + class SeedListFileTableModel; + class SeedListTableModel; + + class NewFileDialog; + class EditSeedDialog; + class AddSeedDialog; + + class SeedTabWidget + : public AssetBundlerTabWidget + , public AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + explicit SeedTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager, const QString& assetBundlingDirectory); + virtual ~SeedTabWidget(); + + QString GetTabTitle() override { return tr("Seeds"); } + + QString GetFileTypeDisplayName() override { return tr("Seed List file"); } + + AssetBundlingFileType GetFileType() override { return AssetBundlingFileType::SeedListFileType; } + + bool HasUnsavedChanges() override; + + void Reload() override; + + bool SaveCurrentSelection() override; + + bool SaveAll() override; + + void SetModelDataSource() override; + + AzQtComponents::TableView* GetFileTableView() override; + + QModelIndex GetSelectedFileTableIndex() override; + + AssetBundlerAbstractFileTableModel* GetFileTableModel() override; + + void SetActiveProjectLabel(const QString& labelText) override; + + void ApplyConfig() override; + + void FileSelectionChanged(const QItemSelection& /*selected*/ = QItemSelection(), const QItemSelection& /*deselected*/ = QItemSelection()) override; + + void UncheckSelectDefaultSeedListsCheckBox(); + + void SetGenerateAssetListsButtonEnabled(bool isEnabled); + + ///////////////////////////////////////////////////////// + // TraceMessageBus overrides + bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override; + bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; + ///////////////////////////////////////////////////////// + + private: + + void OnNewFileButtonPressed(); + + void OnSelectDefaultSeedListsCheckBoxChanged(); + + void OnGenerateAssetListsButtonPressed(); + + void OnEditSeedButtonPressed(); + void OnEditAllButtonPressed(); + + void OnAddSeedButtonPressed(); + void OnRemoveSeedButtonPressed(); + + void OnSeedListContentsTableContextMenuRequested(const QPoint& pos); + + QSharedPointer m_ui; + QDir m_assetBundlingFolder; + + QSharedPointer m_fileTableModel; + QModelIndex m_selectedFileTableIndex; + + QSharedPointer m_generateAssetListsDialog; + + QSharedPointer m_seedListContentsFilterModel; + QSharedPointer m_seedListContentsModel; + + QSharedPointer m_editSeedDialog; + QSharedPointer m_addSeedDialog; + + bool m_hasWarnings = false; + }; +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/ui/SeedTabWidget.ui b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.ui new file mode 100644 index 0000000000..b32072696f --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/SeedTabWidget.ui @@ -0,0 +1,297 @@ + + + SeedTabWidget + + + + 0 + 0 + 813 + 673 + + + + Seed Tab Widget + + + + + + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Generate Asset Lists + + + true + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Seed List files + + + + + + + + + + Filter by file name + + + + + + + 5 + + + 5 + + + + + + Create new Seed List file + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Default Seed Lists + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Product Assets + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 5 + + + + + Filter by file name + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Edit All + + + + + + + + Add Asset + + + + + + + + + + + + + + + + + + AzQtComponents::TableView + QTreeView +
AzQtComponents/Components/Widgets/TableView.h
+ 1 +
+ + AzQtComponents::FilteredSearchWidget + QTreeView +
AzQtComponents/Components/FilteredSearchWidget.h
+ 1 +
+
+ + +
diff --git a/Code/Tools/AssetBundler/source/ui/style/AssetBundler-Icon-256x256@x2.ico b/Code/Tools/AssetBundler/source/ui/style/AssetBundler-Icon-256x256@x2.ico new file mode 100644 index 0000000000..ffbf974130 --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/style/AssetBundler-Icon-256x256@x2.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:690a47a0e2ff4c41ce23ad0bc5dd5bd525e03676a26d3367726beaa96babf207 +size 138199 diff --git a/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qrc b/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qrc new file mode 100644 index 0000000000..65ad7f11cd --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qrc @@ -0,0 +1,7 @@ + + + AssetBundler.qss + AssetBundlerConfig.ini + AssetBundler-Icon-256x256@x2.ico + + \ No newline at end of file diff --git a/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qss b/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qss new file mode 100644 index 0000000000..8603bbbb1b --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/style/AssetBundler.qss @@ -0,0 +1,173 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +/************** General (MainWindow) **************/ + +QTabWidget:pane { + border: none; +} + +QLabel#activeProjectLabel { + font-size: 18px; + font-family: "Amazon Ember Light"; + margin-bottom: 5px; +} + +QTreeView#logTableView { + border: 1px solid #1B1B1B; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +QTreeView#logTableView QHeaderView::section { + background-color: #1B1B1B; +} + +/************** General (Tabs) **************/ + +QFrame#fileTableFrame, +QFrame#fileContentsFrame { + border: 1px solid #1B1B1B; +} + +QFrame#fileTableFrame { + margin-right: 10px; +} + +QFrame#fileTableHeaderFrame, +QFrame#fileContentsHeaderFrame, +QFrame#fileTableHeaderFrame QLabel, +QFrame#fileContentsHeaderFrame QLabel{ + background-color: #1B1B1B; +} + +QFrame#fileTableHeaderFrame QLabel, +QFrame#fileContentsHeaderFrame QLabel{ + background-color: #1B1B1B; + margin-left: 5px; +} + +QTabWidget QTreeView QHeaderView::section { + background-color: rgb(77, 77, 77); +} + +QWidget#PlatformSelectionWidget QLabel#platformSelectionLabel { + margin-right: 10px; +} + +QDialog#NewFileDialog QPushButton#browseFileButton, +QDialog#NewFileDialog QPushButton#createFileButton, +QDialog#NewFileDialog QPushButton#cancelButton { + margin-left: 10px; + margin-right: 10px; +} + +/************** Seeds Tab **************/ + +QWidget#SeedTabWidget QPushButton#generateAssetListsButton, +QWidget#SeedTabWidget QPushButton#browseFileButton, +QWidget#SeedTabWidget QPushButton#cancelButton, +QWidget#SeedTabWidget QPushButton#addSeedButton, +QWidget#SeedTabWidget QPushButton#applyChangesButton { + margin-left: 10px; + margin-right: 10px; +} + +QCheckBox#selectDefaultSeedListsCheckBox { + margin: 5px; +} + +QWidget#seedListContentsFilteredSearchWidget { + margin-left: 5px; +} + +QDialog QFrame#inputValuesFrame { + border: none; + margin: 10px; + padding: 10px; +} + +QDialog QFrame#lineFrame { + border: none; + margin-top: 10px; + margin-bottom: 10px; +} + +QDialog QFrame#inputValuesFrame, +QDialog QFrame#lineFrame, +QDialog#AddSeedDialog QLabel, +QDialog#AddSeedDialog QCheckBox, +QDialog#EditSeedDialog QLabel, +QDialog#EditSeedDialog QCheckBox, +QDialog#NewFileDialog QLabel, +QDialog#NewFileDialog QCheckBox { + background-color: #555555; +} + +/************** Asset List Tab **************/ + +QLabel#assetListContentsLabel { + margin-right: 10px; +} + +QWidget#fileFilteredSearchWidget, +QWidget#assetListContentsFilteredSearchWidget { + margin-left: 5px; + margin-right: 5px; +} + +QPushButton#generateBundleButton { + margin-left: 10px; + margin-right: 10px; +} + +/************** Generate Bundles Modal **************/ + +QFrame#bundleSettingsFrame { + border: none; + margin-bottom: 10px; +} + +QFrame#outputBundleLocationFrame, +QFrame#bundleSettingsFileFrame { + border: none; + margin: 10px; +} + +QFrame#bundleSettingsFrame, +QFrame#outputBundleLocationFrame, +QFrame#bundleSettingsFileFrame, +QFrame#bundleSettingsFrame QLabel { + background-color: #555555; +} + +QWidget#GenerateBundlesModal QPushButton { + margin-left: 10px; + margin-right: 10px; +} + +/************** Rules Tab **************/ + +QPushButton#runRuleButton, +QPushButton#addComparisonStepButton { + margin-left: 10px; + margin-right: 10px; +} + +QScrollArea#comparisonDataList { + border: none; +} + +QFrame#comparisonDataFrame { + padding: 10px; +} diff --git a/Code/Tools/AssetBundler/source/ui/style/AssetBundlerConfig.ini b/Code/Tools/AssetBundler/source/ui/style/AssetBundlerConfig.ini new file mode 100644 index 0000000000..876ceea40e --- /dev/null +++ b/Code/Tools/AssetBundler/source/ui/style/AssetBundlerConfig.ini @@ -0,0 +1,19 @@ +[ErrorLogDetails] +LogTypeColumnWidth=180 +LogSourceColumnWidth=130 + +[GeneralFileTableDetails] +FileTableWidth=450 +FileNameColumnWidth=250 + +[SeedsTabDetails] +CheckBoxColumnWidth=22 +SeedListFileNameColumnWidth=150 +ProjectNameColumnWidth=120 +SeedListContentsNameColumnWidth=400 + +[AssetListsTabDetails] +AssetListFileNameColumnWidth=210 +AssetListPlatformColumnWidth=70 +ProductAssetNameColumnWidth=220 +ProductAssetRelativePathColumnWidth=300 \ No newline at end of file diff --git a/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp new file mode 100644 index 0000000000..d278b9bd2c --- /dev/null +++ b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp @@ -0,0 +1,386 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +// Forward declare platform-specific functions +namespace Platform +{ + /// On Windows this will return the setting for our custom title bar + /// On other platforms this will return the setting for using platform default + /// This ensures that functions like Exit, Maximize, and Minimize appear in the right platform-specific style + AzQtComponents::WindowDecorationWrapper::Option GetWindowDecorationWrapperOption(); +} // namespace Platform + +const char AssetBundlingFolderName [] = "AssetBundling"; +const char SeedListsFolderName [] = "SeedLists"; +const char AssetListsFolderName [] = "AssetLists"; +const char RulesFolderName[] = "Rules"; +const char BundleSettingsFolderName [] = "BundleSettings"; +const char BundlesFolderName [] = "Bundles"; + +namespace AssetBundler +{ + + GUIApplicationManager::Config GUIApplicationManager::loadConfig(QSettings& settings) + { + using namespace AzQtComponents; + + Config config = defaultConfig(); + + // Error Log + { + ConfigHelpers::GroupGuard details(&settings, QStringLiteral("ErrorLogDetails")); + ConfigHelpers::read(settings, QStringLiteral("LogTypeColumnWidth"), config.logTypeColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("LogSourceColumnWidth"), config.logSourceColumnWidth); + } + + // General File Tables + { + ConfigHelpers::GroupGuard details(&settings, QStringLiteral("GeneralFileTableDetails")); + ConfigHelpers::read(settings, QStringLiteral("FileTableWidth"), config.fileTableWidth); + ConfigHelpers::read(settings, QStringLiteral("FileNameColumnWidth"), config.fileNameColumnWidth); + } + + // Seeds Tab + { + ConfigHelpers::GroupGuard details(&settings, QStringLiteral("SeedsTabDetails")); + ConfigHelpers::read(settings, QStringLiteral("CheckBoxColumnWidth"), config.checkBoxColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("SeedListFileNameColumnWidth"), config.seedListFileNameColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("ProjectNameColumnWidth"), config.projectNameColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("SeedListContentsNameColumnWidth"), config.seedListContentsNameColumnWidth); + } + + // Asset Lists Tab + { + ConfigHelpers::GroupGuard details(&settings, QStringLiteral("AssetListsTabDetails")); + ConfigHelpers::read(settings, QStringLiteral("AssetListFileNameColumnWidth"), config.assetListFileNameColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("AssetListPlatformColumnWidth"), config.assetListPlatformColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("ProductAssetNameColumnWidth"), config.productAssetNameColumnWidth); + ConfigHelpers::read(settings, QStringLiteral("ProductAssetRelativePathColumnWidth"), config.productAssetRelativePathColumnWidth); + } + + return config; + } + + GUIApplicationManager::Config GUIApplicationManager::defaultConfig() + { + // These are used if the values can't be read from AssetBundlerConfig.ini. + Config config; + + config.logTypeColumnWidth = 150; + config.logSourceColumnWidth = 150; + + config.fileTableWidth = 250; + config.fileNameColumnWidth = 150; + + config.checkBoxColumnWidth = 150; + config.seedListFileNameColumnWidth = 150; + config.projectNameColumnWidth = 150; + config.seedListContentsNameColumnWidth = 150; + + config.assetListFileNameColumnWidth = 150; + config.assetListPlatformColumnWidth = 150; + config.productAssetNameColumnWidth = 150; + config.productAssetRelativePathColumnWidth = 150; + + return config; + } + + GUIApplicationManager::GUIApplicationManager(int* argc, char*** argv, QObject* parent) + : ApplicationManager(argc, argv, parent) + { + } + + GUIApplicationManager::~GUIApplicationManager() + { + // Reset this before DestroyApplication, BusDisconnect needs to happen before Application::Stop() destroys the allocators. + m_platformCatalogManager.reset(); + } + + bool GUIApplicationManager::Init() + { + m_isInitializing = true; + // Initialize Asset Bundler Batch + ApplicationManager::Init(); + + AZ::IO::FixedMaxPath engineRoot = GetEngineRoot(); + + if (engineRoot.empty()) + { + // Error has already been thrown + return false; + } + + // Determine the name of the current project + auto projectOutcome = AssetBundler::GetCurrentProjectName(); + if (!projectOutcome.IsSuccess()) + { + AZ_Error("AssetBundler", false, projectOutcome.GetError().c_str()); + return false; + } + m_currentProjectName = projectOutcome.GetValue(); + + // Set up paths to the Project folder, Project Cache folder, and determine enabled platforms + auto pathOutcome = InitializePaths(); + if (!pathOutcome.IsSuccess()) + { + AZ_Error("AssetBundler", false, pathOutcome.GetError().c_str()); + return false; + } + + // Set up platform-specific Asset Catalogs + m_platformCatalogManager = AZStd::make_unique(); + + // Define some application-level settings + QApplication::setOrganizationName("Amazon"); + QApplication::setOrganizationDomain("amazon.com"); + QApplication::setApplicationName("Asset Bundler"); + + QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); + + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + + m_isInitializing = false; + + // Create the actual Qt Application + m_qApp.reset(new QApplication(*GetArgC(), *GetArgV())); + + // Create the Main Window + m_mainWindow.reset(new MainWindow(this)); + + return true; + } + + bool GUIApplicationManager::Run() + { + // Set up the Style Manager + AzQtComponents::StyleManager styleManager(qApp); + styleManager.initialize(qApp, GetEngineRoot()); + + AZ::IO::FixedMaxPath engineRoot(GetEngineRoot()); + QDir engineRootDir(engineRoot.c_str()); + AzQtComponents::StyleManager::addSearchPaths( + QStringLiteral("style"), + engineRootDir.filePath(QStringLiteral("Code/Tools/AssetBundler/source/ui/style")), + QStringLiteral(":/AssetBundler/style"), + engineRoot); + AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:AssetBundler.qss")); + + AzQtComponents::ConfigHelpers::loadConfig(&m_fileWatcher, &m_config, QStringLiteral("style:AssetBundlerConfig.ini"), this, std::bind(&GUIApplicationManager::ApplyConfig, this)); + ApplyConfig(); + + qApp->setWindowIcon(QIcon("style:AssetBundler-Icon-256x256@x2.ico")); + + // Set up the Main Window + auto wrapper = new AzQtComponents::WindowDecorationWrapper(Platform::GetWindowDecorationWrapperOption()); + wrapper->setGuest(m_mainWindow.data()); + m_mainWindow->Activate(); + wrapper->show(); + m_mainWindow->show(); + + qApp->setQuitOnLastWindowClosed(true); + + // Run the application + return qApp->exec(); + } + + void GUIApplicationManager::AddWatchedPath(const QString& folderPath) + { + m_fileWatcher.addPath(folderPath); + } + + void GUIApplicationManager::AddWatchedPaths(const QSet& folderPaths) + { + m_fileWatcher.addPaths(folderPaths.values()); + } + + void GUIApplicationManager::RemoveWatchedPath(const QString& path) + { + m_fileWatcher.removePath(path); + } + + void GUIApplicationManager::RemoveWatchedPaths(const QSet& paths) + { + // Check whether the list is empty to get rid of the warning from Qt + if (paths.isEmpty()) + { + return; + } + + m_fileWatcher.removePaths(paths.values()); + } + + bool GUIApplicationManager::OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + // We want to display errors during initialization, then let the MainWindow handle errors during runtime + if (m_isInitializing) + { + // These are fatal initialization errors, and the application will shut down after the user closes the message box + m_qApp.reset(new QApplication(*GetArgC(), *GetArgV())); + QMessageBox errorMessageBox; + errorMessageBox.setWindowTitle("Asset Bundler"); + errorMessageBox.setText(message); + errorMessageBox.setStandardButtons(QMessageBox::Ok); + errorMessageBox.setDefaultButton(QMessageBox::Ok); + errorMessageBox.exec(); + + return true; + } + + return false; + } + + bool GUIApplicationManager::OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) + { + // Don't handle warnings, let the MainWindow print them + return false; + } + + + bool GUIApplicationManager::OnPrintf(const char* /*window*/, const char* /*message*/) + { + // This is disabled during initialization to prevent a lot of message spam printed to the CLI that gets generated on setup + return m_isInitializing; + } + + AZ::Outcome GUIApplicationManager::InitializePaths() + { + // Calculate the path to the Cache for the current project + auto pathOutcome = AssetBundler::GetProjectCacheFolderPath(); + if (!pathOutcome.IsSuccess()) + { + return AZ::Failure(pathOutcome.GetError()); + } + + m_currentProjectCacheFolder = pathOutcome.GetValue().String(); + + // Calculate the path to the current project folder + pathOutcome = AssetBundler::GetProjectFolderPath(); + if (!pathOutcome.IsSuccess()) + { + return AZ::Failure(pathOutcome.GetError()); + } + + m_currentProjectFolder = pathOutcome.GetValue().String(); + + // Generate the AssetBundling folder inside the current project + AzFramework::StringFunc::Path::ConstructFull(m_currentProjectFolder.c_str(), AssetBundlingFolderName, m_assetBundlingFolder); + + // Seed Lists folder + AzFramework::StringFunc::Path::ConstructFull(m_assetBundlingFolder.c_str(), SeedListsFolderName, m_seedListsFolder); + AZ::Outcome createPathOutcome = AssetBundler::MakePath(m_seedListsFolder); + if (!createPathOutcome.IsSuccess()) + { + return createPathOutcome; + } + + // Asset Lists folder + AzFramework::StringFunc::Path::ConstructFull(m_assetBundlingFolder.c_str(), AssetListsFolderName, m_assetListsFolder); + createPathOutcome = AssetBundler::MakePath(m_assetListsFolder); + if (!createPathOutcome.IsSuccess()) + { + return createPathOutcome; + } + + // Rules folder + AzFramework::StringFunc::Path::ConstructFull(m_assetBundlingFolder.c_str(), RulesFolderName, m_rulesFolder); + createPathOutcome = AssetBundler::MakePath(m_rulesFolder); + if (!createPathOutcome.IsSuccess()) + { + return createPathOutcome; + } + + // Bundle Settings Folder + AzFramework::StringFunc::Path::ConstructFull(m_assetBundlingFolder.c_str(), BundleSettingsFolderName, m_bundleSettingsFolder); + createPathOutcome = AssetBundler::MakePath(m_bundleSettingsFolder); + if (!createPathOutcome.IsSuccess()) + { + return createPathOutcome; + } + + // Bundles Folder + AzFramework::StringFunc::Path::ConstructFull(m_assetBundlingFolder.c_str(), BundlesFolderName, m_bundlesFolder); + createPathOutcome = AssetBundler::MakePath(m_bundlesFolder); + if (!createPathOutcome.IsSuccess()) + { + return createPathOutcome; + } + + // Determine the enabled platforms + const char* appRoot = nullptr; + AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot); + m_enabledPlatforms = GetEnabledPlatformFlags(GetEngineRoot(), appRoot, AZ::Utils::GetProjectPath().c_str()); + + // Determine which Gems are enabled for the current project + if (!AzFramework::GetGemsInfo(m_gemInfoList, *m_settingsRegistry)) + { + return AZ::Failure(AZStd::string::format("Failed to read Gems for project: %s\n", m_currentProjectName.c_str())); + } + + QObject::connect(&m_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &GUIApplicationManager::DirectoryChanged); + QObject::connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &GUIApplicationManager::FileChanged); + + return AZ::Success(); + } + + void GUIApplicationManager::DirectoryChanged(const QString& directory) + { + UpdateTab(directory.toUtf8().data()); + } + + void GUIApplicationManager::FileChanged(const QString& path) + { + // FileChanged will only be called when engine or gem seed files are updated + // Otherwilse DirectoryChanged should be triggered + AZStd::string extension; + AzFramework::StringFunc::Path::GetExtension(path.toUtf8().data(), extension); + extension = extension.starts_with(".") ? extension.substr(1) : extension; + if (extension == AzToolsFramework::AssetSeedManager::GetSeedFileExtension()) + { + UpdateTab(GetSeedListsFolder()); + } + + // Many applications save an open file by writing a new file and then deleting the old one + // Add the file path back if it has been removed from the watcher file list + if (!m_fileWatcher.files().contains(path)) + { + m_fileWatcher.addPath(path); + } + } + + void GUIApplicationManager::ApplyConfig() + { + m_mainWindow->ApplyConfig(); + } + +} // namespace AssetBundler + +#include diff --git a/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.h b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.h new file mode 100644 index 0000000000..e81607d4a8 --- /dev/null +++ b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.h @@ -0,0 +1,164 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace AssetBundler +{ + enum AssetBundlingFileType : int + { + SeedListFileType = 0, + AssetListFileType, + BundleSettingsFileType, + BundleFileType, + RulesFileType, + NumBundlingFileTypes + }; + + class MainWindow; + + class GUIApplicationManager + : public ApplicationManager + { + Q_OBJECT + + public: + + struct Config + { + // These default values are used if the values can't be read from AssetBundlerConfig.ini, + // and the call to defaultConfig fails. + + // Error Log + int logTypeColumnWidth = -1; + int logSourceColumnWidth = -1; + + // General File Tables + int fileTableWidth = -1; + int fileNameColumnWidth = -1; + + // Seeds Tab + int checkBoxColumnWidth = -1; + int seedListFileNameColumnWidth = -1; + int projectNameColumnWidth = -1; + int seedListContentsNameColumnWidth = -1; + + // Asset Lists Tab + int assetListFileNameColumnWidth = -1; + int assetListPlatformColumnWidth = -1; + int productAssetNameColumnWidth = -1; + int productAssetRelativePathColumnWidth = -1; + }; + + /*! + * Loads the button config data from a settings object. + */ + static Config loadConfig(QSettings& settings); + + /*! + * Returns default button config data. + */ + static Config defaultConfig(); + + explicit GUIApplicationManager(int* argc, char*** argv, QObject* parent = 0); + virtual ~GUIApplicationManager(); + + bool Init() override; + + bool Run() override; + + AZStd::string GetCurrentProjectFolder() { return m_currentProjectFolder; } + AZStd::string GetAssetBundlingFolder() { return m_assetBundlingFolder; } + AZStd::string GetSeedListsFolder() { return m_seedListsFolder; } + AZStd::string GetAssetListsFolder() { return m_assetListsFolder; } + AZStd::string GetRulesFolder() { return m_rulesFolder; } + AZStd::string GetBundleSettingsFolder() { return m_bundleSettingsFolder; } + AZStd::string GetBundlesFolder() { return m_bundlesFolder; } + AZStd::string GetCurrentProjectCacheFolder() { return m_currentProjectCacheFolder; } + + AzFramework::PlatformFlags GetEnabledPlatforms() { return m_enabledPlatforms; } + + void AddWatchedPath(const QString& path); + void AddWatchedPaths(const QSet& paths); + void RemoveWatchedPath(const QString& path); + void RemoveWatchedPaths(const QSet& paths); + + //////////////////////////////////////////////////////////////////////////////////////////// + // Override the ApplicationManager TraceMessageBus methods so that messages go through MainWindow and not the CLI + + bool OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + //////////////////////////////////////////////////////////////////////////////////////////// + + const Config& GetConfig() { return m_config; } + + Q_SIGNALS: + void ShowWindow(); + void UpdateTab(const AZStd::string& directory); + void UpdateFiles(AssetBundlingFileType fileType, const AZStd::vector& absoluteFilePaths); + + protected Q_SLOTS: + void DirectoryChanged(const QString& directory); + void FileChanged(const QString& path); + void ApplyConfig(); + + private: + /** + * Generates directory information for all paths used in this tool + * @return void on success, error message on failure + */ + AZ::Outcome InitializePaths(); + + QSharedPointer m_qApp; + + Config m_config; + + QSharedPointer m_mainWindow; + + AZStd::string m_currentProjectFolder; + AZStd::string m_assetBundlingFolder; + AZStd::string m_seedListsFolder; + AZStd::string m_assetListsFolder; + AZStd::string m_rulesFolder; + AZStd::string m_bundleSettingsFolder; + AZStd::string m_bundlesFolder; + AZStd::string m_currentProjectCacheFolder; + + AzFramework::PlatformFlags m_enabledPlatforms = AzFramework::PlatformFlags::Platform_NONE; + + AZStd::unique_ptr m_platformCatalogManager; + + bool m_isInitializing = false; + + QFileSystemWatcher m_fileWatcher; + }; + +} // namespace AssetBundler diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp index bb4fee2ceb..7aae023e72 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp @@ -45,8 +45,16 @@ namespace AssetBundler { const char compareVariablePrefix = '$'; - ApplicationManager::ApplicationManager(int* argc, char*** argv) - : AzToolsFramework::ToolsApplication(argc, argv) + GemInfo::GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath) + : m_gemName(name) + , m_relativeFilePath(relativeFilePath) + , m_absoluteFilePath(absoluteFilePath) + { + } + + ApplicationManager::ApplicationManager(int* argc, char*** argv, QObject* parent) + : QObject(parent) + , AzToolsFramework::ToolsApplication(argc, argv) { } @@ -55,7 +63,7 @@ namespace AssetBundler DestroyApplication(); } - void ApplicationManager::Init() + bool ApplicationManager::Init() { AZ::Debug::TraceMessageBus::Handler::BusConnect(); Start(AzFramework::Application::Descriptor()); @@ -72,8 +80,11 @@ namespace AssetBundler m_assetSeedManager = AZStd::make_unique(); AZ_TracePrintf(AssetBundler::AppWindowName, "\n"); + g_cachedEngineRoot = AZ::IO::FixedMaxPath(GetEngineRoot()); + // There is no need to update the UserSettings file, so we can avoid a race condition by disabling save on shutdown AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); + return true; } void ApplicationManager::DestroyApplication() @@ -1522,7 +1533,7 @@ namespace AssetBundler } } - AZStd::vector defaultSeeds = GetDefaultSeeds(GetEngineRoot(), AZ::Utils::GetProjectPath(), m_currentProjectName); + AZStd::vector defaultSeeds = GetDefaultSeeds(AZ::Utils::GetProjectPath(), m_currentProjectName); if (defaultSeeds.empty()) { // Error has already been thrown @@ -2847,3 +2858,4 @@ namespace AssetBundler return !m_showVerboseOutput; } } // namespace AssetBundler +#include diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.h b/Code/Tools/AssetBundler/source/utils/applicationManager.h index 01f180a77a..d49707f489 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.h +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.h @@ -11,6 +11,7 @@ */ #pragma once +#if !defined(Q_MOC_RUN) #include #include #include @@ -22,7 +23,7 @@ #include #include #include - +#endif namespace AssetBundler { struct SeedsParams @@ -161,16 +162,18 @@ namespace AssetBundler }; class ApplicationManager - : public AZ::Debug::TraceMessageBus::Handler + : public QObject + , public AZ::Debug::TraceMessageBus::Handler , public AzToolsFramework::ToolsApplication { + Q_OBJECT public: - explicit ApplicationManager(int* argc, char*** argv); - ~ApplicationManager(); + explicit ApplicationManager(int* argc, char*** argv, QObject* parent = 0); + virtual ~ApplicationManager(); - void Init(); + virtual bool Init(); void DestroyApplication(); - bool Run(); + virtual bool Run(); //////////////////////////////////////////////////////////////////////////////////////////// // AzFramework::Application overrides diff --git a/Code/Tools/AssetBundler/source/utils/utils.cpp b/Code/Tools/AssetBundler/source/utils/utils.cpp index b1595f7dd8..6058728a76 100644 --- a/Code/Tools/AssetBundler/source/utils/utils.cpp +++ b/Code/Tools/AssetBundler/source/utils/utils.cpp @@ -31,6 +31,7 @@ AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") #include #include #include +#include AZ_POP_DISABLE_WARNING namespace AssetBundler @@ -108,6 +109,8 @@ namespace AssetBundler const char* AssetCatalogFilename = "assetcatalog.xml"; + AZ::IO::FixedMaxPath g_cachedEngineRoot; + const char EngineDirectoryName[] = "Engine"; const char RestrictedDirectoryName[] = "restricted"; @@ -128,7 +131,7 @@ namespace AssetBundler AZStd::unordered_map& defaultSeedLists, AzFramework::PlatformFlags platformFlags) { - AZ::IO::FixedMaxPath engineRoot(GetEngineRoot()); + AZ::IO::FixedMaxPath engineRoot(GetCachedEngineRoot()); AZ::IO::FixedMaxPath engineRestrictedRoot = engineRoot / RestrictedDirectoryName; AZ::IO::FixedMaxPath engineLocalPath = AZ::IO::PathView(engineDirectory.LexicallyRelative(engineRoot)); @@ -201,15 +204,10 @@ namespace AssetBundler } } - AZ::IO::FixedMaxPath GetEngineRoot() + AZ::IO::FixedMaxPath GetCachedEngineRoot() { - AZ::IO::FixedMaxPath engineRootPath; - if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) - { - settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); - } - - return engineRootPath; + AZ_Error(AppWindowName, !g_cachedEngineRoot.empty(), "Cached Engine Root has not been initialized by the Bundler."); + return g_cachedEngineRoot; } void AddPlatformIdentifier(AZStd::string& filePath, const AZStd::string& platformIdentifier) @@ -276,11 +274,11 @@ namespace AssetBundler return defaultSeedLists; } - AZStd::vector GetDefaultSeeds(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName) + AZStd::vector GetDefaultSeeds(AZStd::string_view projectPath, AZStd::string_view projectName) { AZStd::vector defaultSeeds; - defaultSeeds.emplace_back(GetProjectDependenciesAssetPath(enginePath, projectPath, projectName)); + defaultSeeds.emplace_back(GetProjectDependenciesAssetPath(projectPath, projectName)); return defaultSeeds; } @@ -294,34 +292,12 @@ namespace AssetBundler return projectDependenciesFilePath.LexicallyNormal(); } - AZ::IO::Path GetProjectDependenciesFileTemplate(AZStd::string_view engineRoot) - { - AZ::IO::Path projectDependenciesFileTemplate = engineRoot; - projectDependenciesFileTemplate /= DefaultProjectTemplatePath; - projectDependenciesFileTemplate /= AZStd::string::format("%s%s", ProjectName, DependenciesFileSuffix); - projectDependenciesFileTemplate.ReplaceExtension(DependenciesFileExtension); - return projectDependenciesFileTemplate.LexicallyNormal(); - } - - AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName) + AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view projectPath, AZStd::string_view projectName) { AZ::IO::Path projectDependenciesFile = GetProjectDependenciesFile(projectPath, projectName); if (!AZ::IO::FileIOBase::GetInstance()->Exists(projectDependenciesFile.c_str())) { - AZ_TracePrintf(AssetBundler::AppWindowName, "Project dependencies file %s doesn't exist.\n", projectDependenciesFile.c_str()); - - AZ::IO::Path projectDependenciesFileTemplate = GetProjectDependenciesFileTemplate(enginePath); - if (AZ::IO::FileIOBase::GetInstance()->Copy(projectDependenciesFileTemplate.c_str(), projectDependenciesFile.c_str())) - { - AZ_TracePrintf(AssetBundler::AppWindowName, "Copied project dependencies file template %s to the current project.\n", - projectDependenciesFile.c_str()); - } - else - { - AZ_Error(AppWindowName, false, "Failed to copy project dependencies file template %s from default project" - " template to the current project.\n", projectDependenciesFileTemplate.c_str()); - return {}; - } + AZ_Error(AssetBundler::AppWindowName, false, "Project dependencies file %s doesn't exist.\n", projectDependenciesFile.c_str()); } // Turn the absolute path into a cache-relative path @@ -538,6 +514,14 @@ namespace AssetBundler return platformSpecificCacheFolderPath; } + AZStd::string GenerateKeyFromAbsolutePath(const AZStd::string& absoluteFilePath) + { + AZStd::string key(absoluteFilePath); + AzFramework::StringFunc::Path::Normalize(key); + AzFramework::StringFunc::Path::StripDrive(key); + return key; + } + void ConvertToRelativePath(AZStd::string_view parentFolderPath, AZStd::string& absoluteFilePath) { absoluteFilePath = AZ::IO::PathView(absoluteFilePath).LexicallyRelative(parentFolderPath).String(); @@ -822,4 +806,26 @@ namespace AssetBundler } return false; } + + QJsonObject ReadJson(const AZStd::string& filePath) + { + QByteArray byteArray; + QFile jsonFile; + jsonFile.setFileName(filePath.c_str()); + jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); + byteArray = jsonFile.readAll(); + jsonFile.close(); + + return QJsonDocument::fromJson(byteArray).object(); + } + + void SaveJson(const AZStd::string& filePath, const QJsonObject& jsonObject) + { + QFile jsonFile(filePath.c_str()); + QJsonDocument JsonDocument; + JsonDocument.setObject(jsonObject); + jsonFile.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); + jsonFile.write(JsonDocument.toJson()); + jsonFile.close(); + } } diff --git a/Code/Tools/AssetBundler/source/utils/utils.h b/Code/Tools/AssetBundler/source/utils/utils.h index db6729098a..629fd90a31 100644 --- a/Code/Tools/AssetBundler/source/utils/utils.h +++ b/Code/Tools/AssetBundler/source/utils/utils.h @@ -19,7 +19,9 @@ #include #include #include - +#include +#include +#include namespace AssetBundler { @@ -120,8 +122,20 @@ namespace AssetBundler //////////////////////////////////////////////////////////////////////////////////////////// extern const char* AssetCatalogFilename; + extern AZ::IO::FixedMaxPath g_cachedEngineRoot; static const size_t MaxErrorMessageLength = 4096; + //! This struct stores gem related information + struct GemInfo + { + AZ_CLASS_ALLOCATOR(GemInfo, AZ::SystemAllocator, 0); + GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath); + GemInfo() = default; + AZStd::string m_gemName; + AZStd::string m_relativeFilePath; + AZStd::string m_absoluteFilePath; + }; + // The Warning Absorber is used to absorb warnings // One case that this is being used is during loading of the asset catalog. @@ -137,8 +151,8 @@ namespace AssetBundler bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; }; - // Returns the engine root - AZ::IO::FixedMaxPath GetEngineRoot(); + // Returns the cached engine root. Throws an error if the cached path has not been initialized by the Bundler Application. + AZ::IO::FixedMaxPath GetCachedEngineRoot(); /** * Determines the name of the currently enabled game project @@ -192,6 +206,8 @@ namespace AssetBundler */ AZ::IO::Path GetPlatformSpecificCacheFolderPath(); + AZStd::string GenerateKeyFromAbsolutePath(const AZStd::string& absoluteFilePath); + void ConvertToRelativePath(AZStd::string_view parentFolderPath, AZStd::string& absoluteFilePath); AZ::Outcome MakePath(const AZStd::string& path); @@ -207,16 +223,13 @@ namespace AssetBundler const AZStd::vector& gemInfoList, AzFramework::PlatformFlags platformFlags); //! Returns a vector of relative paths to Assets that should be included as default Seeds, but are not already in a Seed List file. - AZStd::vector GetDefaultSeeds(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName); + AZStd::vector GetDefaultSeeds(AZStd::string_view projectPath, AZStd::string_view projectName); //! Returns the absolute path of {ProjectName}_Dependencies.xml AZ::IO::Path GetProjectDependenciesFile(AZStd::string_view productPath, AZStd::string_view projectName); - //! Returns the absolute path of the project dependencies file in the default project template - AZ::IO::Path GetProjectDependenciesFileTemplate(AZStd::string_view enginePath); - //! Creates the ProjectName_Dependencies.xml file if it does not exist, and adds returns the relative path to the asset in the Cache. - AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName); + AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view projectPath, AZStd::string_view projectName); //! Returns the map from gem seed list file path to gem name AZStd::unordered_map GetGemSeedListFilePathToGemNameMap(const AZStd::vector& gemInfoList, AzFramework::PlatformFlags platformFlags); @@ -229,6 +242,9 @@ namespace AssetBundler //! Please note that the game project could be in a different location to the engine therefore we need the assetRoot param. AzFramework::PlatformFlags GetEnabledPlatformFlags(AZStd::string_view enginePath, AZStd::string_view assetRoot, AZStd::string_view projectPath); + QJsonObject ReadJson(const AZStd::string& filePath); + void SaveJson(const AZStd::string& filePath, const QJsonObject& jsonObject); + //! Filepath is a helper class that is used to find the absolute path of a file //! if the inputted file path is an absolute path than it does nothing //! if the inputted file path is a relative path than based on whether the user diff --git a/Code/Tools/AssetBundler/tests/tests_main.cpp b/Code/Tools/AssetBundler/tests/tests_main.cpp index e1535bf0f5..6d874a3320 100644 --- a/Code/Tools/AssetBundler/tests/tests_main.cpp +++ b/Code/Tools/AssetBundler/tests/tests_main.cpp @@ -113,14 +113,13 @@ namespace AssetBundler AZ::SettingsRegistry::Register(&m_registry); } - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - if (!engineRoot) + AssetBundler::g_cachedEngineRoot = m_data->m_application.get()->GetEngineRoot(); + if (AssetBundler::g_cachedEngineRoot.empty()) { GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to locate engine root.\n").c_str()); } - AzFramework::StringFunc::Path::Join(engineRoot, RelativeTestFolder, m_data->m_testEngineRoot); + AzFramework::StringFunc::Path::Join(AssetBundler::g_cachedEngineRoot.c_str(), RelativeTestFolder, m_data->m_testEngineRoot); m_data->m_localFileIO = aznew AZ::IO::LocalFileIO(); m_data->m_priorFileIO = AZ::IO::FileIOBase::GetInstance();