You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/AssetBundler/source/ui/AssetBundlerTabWidget.cpp

374 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <source/ui/AssetBundlerTabWidget.h>
#include <source/utils/GUIApplicationManager.h>
#include <source/utils/utils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QApplication>
#include <QClipboard>
#include <QHeaderView>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QMenu>
#include <QMessageBox>
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<int>(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)
{
auto scanFilePathStr = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
/ scanPath.toString().toUtf8().data()).LexicallyNormal();
// 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;
auto scanPath = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
/ scanPathValueRef.toString().toUtf8().data()).LexicallyNormal();
// 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<AZStd::string>& 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<AZStd::string> assetBundlerSettingsFileList;
QJsonObject assetBundlerSettings = AssetBundler::ReadJson(filePath);
QJsonObject scanPaths = assetBundlerSettings.find(ScanPathsKey).value().toObject();
for (const QJsonValue scanPath : scanPaths[AssetBundlingFileTypes[fileType]].toArray())
{
auto absoluteScanPath = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
/ scanPath.toString().toUtf8().data()).LexicallyNormal();
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 <source/ui/moc_AssetBundlerTabWidget.cpp>