AssetBundlerTabWidget.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <source/ui/AssetBundlerTabWidget.h>
  9. #include <source/utils/GUIApplicationManager.h>
  10. #include <source/utils/utils.h>
  11. #include <AzCore/Utils/Utils.h>
  12. #include <AzFramework/IO/LocalFileIO.h>
  13. #include <AzQtComponents/Utilities/DesktopUtilities.h>
  14. #include <QApplication>
  15. #include <QClipboard>
  16. #include <QHeaderView>
  17. #include <QJsonArray>
  18. #include <QJsonDocument>
  19. #include <QJsonObject>
  20. #include <QJsonValue>
  21. #include <QMenu>
  22. #include <QMessageBox>
  23. namespace AssetBundler
  24. {
  25. constexpr AZ::IO::PathView AssetBundlerCommonSettingsFile = "AssetBundlerCommonSettings.json";
  26. constexpr AZ::IO::PathView AssetBundlerUserSettingsFile = "AssetBundlerUserSettings.json";
  27. const char ScanPathsKey[] = "ScanPaths";
  28. const QString AssetBundlingFileTypes[] =
  29. {
  30. "SeedLists",
  31. "AssetLists",
  32. "BundleSettings",
  33. "Bundles",
  34. "Rules"
  35. };
  36. const int MarginSize = 10;
  37. AssetBundlerTabWidget::AssetBundlerTabWidget(QWidget* parent, GUIApplicationManager* guiApplicationManager)
  38. : QWidget(parent)
  39. , m_guiApplicationManager(guiApplicationManager)
  40. {
  41. connect(m_guiApplicationManager, &GUIApplicationManager::UpdateTab, this, &AssetBundlerTabWidget::OnUpdateTab);
  42. connect(m_guiApplicationManager, &GUIApplicationManager::UpdateFiles, this, &AssetBundlerTabWidget::OnUpdateFiles);
  43. QAction* deleteFileAction = new QAction(tr("Delete"), this);
  44. addAction(deleteFileAction);
  45. deleteFileAction->setShortcut(QKeySequence::Delete);
  46. connect(deleteFileAction, &QAction::triggered, this, &AssetBundlerTabWidget::OnDeleteSelectedFileRequested);
  47. }
  48. AssetBundlerTabWidget::~AssetBundlerTabWidget()
  49. {
  50. m_guiApplicationManager = nullptr;
  51. }
  52. void AssetBundlerTabWidget::Activate()
  53. {
  54. SetActiveProjectLabel(tr("Active Project: %1").arg(m_guiApplicationManager->GetCurrentProjectName().c_str()));
  55. SetupContextMenu();
  56. Reload();
  57. connect(GetFileTableView()->header(),
  58. &QHeaderView::sortIndicatorChanged,
  59. m_fileTableFilterModel.get(),
  60. &AssetBundlerFileTableFilterModel::sort);
  61. GetFileTableView()->header()->setSortIndicatorShown(true);
  62. // Setting this in descending order will ensure the most recent files are at the top
  63. GetFileTableView()->header()->setSortIndicator(GetFileTableModel()->GetTimeStampColumnIndex(), Qt::DescendingOrder);
  64. GetFileTableView()->setSortingEnabled(true);
  65. }
  66. void AssetBundlerTabWidget::InitAssetBundlerSettings(const char* currentProjectFolderPath)
  67. {
  68. AZStd::string commonSettingsPath = GetAssetBundlerCommonSettingsFile(currentProjectFolderPath);
  69. if (!AZ::IO::FileIOBase::GetInstance()->Exists(commonSettingsPath.c_str()))
  70. {
  71. CreateEmptyAssetBundlerSettings(commonSettingsPath);
  72. }
  73. AZStd::string userSettingsPath = GetAssetBundlerUserSettingsFile(currentProjectFolderPath);
  74. if (!AZ::IO::FileIOBase::GetInstance()->Exists(userSettingsPath.c_str()))
  75. {
  76. CreateEmptyAssetBundlerSettings(userSettingsPath);
  77. }
  78. }
  79. void AssetBundlerTabWidget::OnFileTableContextMenuRequested(const QPoint& pos)
  80. {
  81. AZStd::string selectedFileAbsolutePath(GetFileTableModel()->GetFileAbsolutePath(GetSelectedFileTableIndex()));
  82. QMenu* contextMenu = new QMenu(this);
  83. contextMenu->setToolTipsVisible(true);
  84. bool arePathOperationsEnabled = !selectedFileAbsolutePath.empty();
  85. QString emptyPathToolTip(tr("This file is not present on-disk."));
  86. QAction* action = contextMenu->addAction(AzQtComponents::fileBrowserActionName(), [=]()
  87. {
  88. AzQtComponents::ShowFileOnDesktop(selectedFileAbsolutePath.c_str());
  89. });
  90. if (arePathOperationsEnabled)
  91. {
  92. action->setToolTip(tr("Shows the location of this file on your computer"));
  93. }
  94. else
  95. {
  96. action->setToolTip(emptyPathToolTip);
  97. action->setEnabled(false);
  98. }
  99. action = contextMenu->addAction(tr("Copy Path to Clipboard"), [=]()
  100. {
  101. QApplication::clipboard()->setText(QString(selectedFileAbsolutePath.c_str()));
  102. });
  103. if (arePathOperationsEnabled)
  104. {
  105. action->setToolTip(tr("Copies the absolute path of this file to your Clipboard"));
  106. }
  107. else
  108. {
  109. action->setToolTip(emptyPathToolTip);
  110. action->setEnabled(false);
  111. }
  112. contextMenu->addSeparator();
  113. // We can't use the same Delete action as the constructor because we need to modify a lot of values
  114. action = new QAction(tr("Delete"), this);
  115. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  116. action->setShortcut(QKeySequence::Delete);
  117. connect(action, &QAction::triggered, this, &AssetBundlerTabWidget::OnDeleteSelectedFileRequested);
  118. if (arePathOperationsEnabled)
  119. {
  120. action->setToolTip(tr("Deletes the selected file from disk."));
  121. }
  122. else
  123. {
  124. action->setToolTip(emptyPathToolTip);
  125. action->setEnabled(false);
  126. }
  127. contextMenu->addAction(action);
  128. contextMenu->exec(GetFileTableView()->mapToGlobal(pos));
  129. delete contextMenu;
  130. }
  131. void AssetBundlerTabWidget::OnDeleteSelectedFileRequested()
  132. {
  133. AZStd::string selectedFileAbsolutePath(GetFileTableModel()->GetFileAbsolutePath(GetSelectedFileTableIndex()));
  134. if (selectedFileAbsolutePath.empty())
  135. {
  136. return;
  137. }
  138. QString messageBoxText =
  139. QString(tr("Are you sure you would like to delete %1? \n\nThis will permanently delete the file.")).arg(QString(selectedFileAbsolutePath.c_str()));
  140. QMessageBox::StandardButton confirmDeleteFileResult =
  141. QMessageBox::question(this, QString(tr("Delete %1")).arg(GetFileTypeDisplayName()), messageBoxText);
  142. if (confirmDeleteFileResult != QMessageBox::StandardButton::Yes)
  143. {
  144. // User canceled out of the confirmation dialog
  145. return;
  146. }
  147. if (GetFileTableModel()->DeleteFile(GetSelectedFileTableIndex()))
  148. {
  149. RemoveScanPathFromAssetBundlerSettings(GetFileType(), selectedFileAbsolutePath.c_str());
  150. }
  151. }
  152. void AssetBundlerTabWidget::ReadScanPathsFromAssetBundlerSettings(AssetBundlingFileType fileType)
  153. {
  154. AZStd::string currentProjectFolderPath = m_guiApplicationManager->GetCurrentProjectFolder();
  155. ReadAssetBundlerSettings(GetAssetBundlerUserSettingsFile(currentProjectFolderPath.c_str()), fileType);
  156. ReadAssetBundlerSettings(GetAssetBundlerCommonSettingsFile(currentProjectFolderPath.c_str()), fileType);
  157. }
  158. void AssetBundlerTabWidget::AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, AZStd::string filePath)
  159. {
  160. AZ::IO::Path defaultFolderPath;
  161. switch (fileType)
  162. {
  163. case AssetBundlingFileType::SeedListFileType:
  164. defaultFolderPath = m_guiApplicationManager->GetSeedListsFolder();
  165. break;
  166. case AssetBundlingFileType::AssetListFileType:
  167. defaultFolderPath = m_guiApplicationManager->GetAssetListsFolder();
  168. break;
  169. case AssetBundlingFileType::RulesFileType:
  170. defaultFolderPath = m_guiApplicationManager->GetRulesFolder();
  171. break;
  172. case AssetBundlingFileType::BundleSettingsFileType:
  173. defaultFolderPath = m_guiApplicationManager->GetBundleSettingsFolder();
  174. break;
  175. case AssetBundlingFileType::BundleFileType:
  176. defaultFolderPath = m_guiApplicationManager->GetBundlesFolder();
  177. break;
  178. default:
  179. AZ_Warning(AssetBundler::AppWindowName, false,
  180. "No default folder is defined for AssetBundlingFileType ( %i ).", static_cast<int>(fileType));
  181. break;
  182. }
  183. auto normalizedFilePath = AZ::IO::PathView(filePath).LexicallyNormal();
  184. defaultFolderPath = defaultFolderPath.LexicallyNormal();
  185. if (normalizedFilePath.IsRelativeTo(defaultFolderPath))
  186. {
  187. // file is already in a watched folder, no need to add it to the settings file
  188. return;
  189. }
  190. AddScanPathToAssetBundlerSettings(fileType, QString(filePath.c_str()));
  191. }
  192. void AssetBundlerTabWidget::AddScanPathToAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath)
  193. {
  194. AZStd::string assetBundlerSettingsFileAbsolutePath =
  195. GetAssetBundlerUserSettingsFile(m_guiApplicationManager->GetCurrentProjectFolder().c_str());
  196. QJsonObject assetBundlerSettings = AssetBundler::ReadJson(assetBundlerSettingsFileAbsolutePath);
  197. QJsonObject scanPathsSettings = assetBundlerSettings[ScanPathsKey].toObject();
  198. QJsonArray scanPaths = scanPathsSettings[AssetBundlingFileTypes[fileType]].toArray();
  199. QFileInfo inputFileInfo(filePath);
  200. for (const QJsonValue scanPath : m_watchedFiles + m_watchedFolders)
  201. {
  202. auto scanFilePathStr = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
  203. / scanPath.toString().toUtf8().data()).LexicallyNormal();
  204. // Check whether the file has already been watched
  205. // Get absolute file paths via QFileInfo to keep consistency in the letter case
  206. if (inputFileInfo.absoluteFilePath().startsWith(
  207. QFileInfo(scanFilePathStr.c_str()).absoluteFilePath()))
  208. {
  209. return;
  210. }
  211. }
  212. scanPaths.push_back(filePath);
  213. scanPathsSettings[AssetBundlingFileTypes[fileType]] = scanPaths;
  214. assetBundlerSettings[ScanPathsKey] = scanPathsSettings;
  215. AssetBundler::SaveJson(assetBundlerSettingsFileAbsolutePath, assetBundlerSettings);
  216. m_watchedFiles.insert(filePath);
  217. m_guiApplicationManager->AddWatchedPath(filePath);
  218. }
  219. void AssetBundlerTabWidget::RemoveScanPathFromAssetBundlerSettings(AssetBundlingFileType fileType, const QString& filePath)
  220. {
  221. AZStd::string assetBundlerSettingsFileAbsolutePath =
  222. GetAssetBundlerUserSettingsFile(m_guiApplicationManager->GetCurrentProjectFolder().c_str());
  223. QJsonObject assetBundlerSettings = AssetBundler::ReadJson(assetBundlerSettingsFileAbsolutePath);
  224. QJsonObject scanPathsSettings = assetBundlerSettings[ScanPathsKey].toObject();
  225. QJsonArray scanPaths = scanPathsSettings[AssetBundlingFileTypes[fileType]].toArray();
  226. for (auto itr = scanPaths.begin(); itr != scanPaths.end(); ++itr)
  227. {
  228. QJsonValueRef scanPathValueRef = *itr;
  229. auto scanPath = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
  230. / scanPathValueRef.toString().toUtf8().data()).LexicallyNormal();
  231. // Check whether the file is being watched
  232. // Get absolute file paths via QFileInfo to keep consistency in the letter case
  233. if (QFileInfo(filePath).absoluteFilePath() == QFileInfo(scanPath.c_str()).absoluteFilePath())
  234. {
  235. itr = scanPaths.erase(itr);
  236. break;
  237. }
  238. }
  239. scanPathsSettings[AssetBundlingFileTypes[fileType]] = scanPaths;
  240. assetBundlerSettings[ScanPathsKey] = scanPathsSettings;
  241. AssetBundler::SaveJson(assetBundlerSettingsFileAbsolutePath, assetBundlerSettings);
  242. m_guiApplicationManager->RemoveWatchedPath(filePath);
  243. }
  244. void AssetBundlerTabWidget::OnUpdateTab(const AZStd::string& path)
  245. {
  246. if (m_watchedFolders.contains(path.c_str()) ||
  247. m_watchedFiles.contains(path.c_str()))
  248. {
  249. Reload();
  250. }
  251. }
  252. void AssetBundlerTabWidget::OnUpdateFiles(AssetBundlingFileType fileType, const AZStd::vector<AZStd::string>& absoluteFilePaths)
  253. {
  254. if (fileType == GetFileType())
  255. {
  256. GetFileTableModel()->ReloadFiles(absoluteFilePaths, m_filePathToGemNameMap);
  257. m_fileTableFilterModel->sort(m_fileTableFilterModel->sortColumn(), m_fileTableFilterModel->sortOrder());
  258. FileSelectionChanged();
  259. }
  260. }
  261. void AssetBundlerTabWidget::SetupContextMenu()
  262. {
  263. GetFileTableView()->setContextMenuPolicy(Qt::CustomContextMenu);
  264. connect(GetFileTableView(),
  265. &QTreeView::customContextMenuRequested,
  266. this,
  267. &AssetBundlerTabWidget::OnFileTableContextMenuRequested);
  268. }
  269. void AssetBundlerTabWidget::ReadAssetBundlerSettings(const AZStd::string& filePath, AssetBundlingFileType fileType)
  270. {
  271. // Read the config file which contains the customized scan paths information
  272. AZStd::vector<AZStd::string> assetBundlerSettingsFileList;
  273. QJsonObject assetBundlerSettings = AssetBundler::ReadJson(filePath);
  274. QJsonObject scanPaths = assetBundlerSettings.find(ScanPathsKey).value().toObject();
  275. for (const QJsonValue scanPath : scanPaths[AssetBundlingFileTypes[fileType]].toArray())
  276. {
  277. auto absoluteScanPath = (AZ::IO::Path(AZStd::string_view{ AZ::Utils::GetEnginePath() })
  278. / scanPath.toString().toUtf8().data()).LexicallyNormal();
  279. if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(absoluteScanPath.c_str()))
  280. {
  281. // The path specified in the config file is a directory
  282. m_watchedFolders.insert(absoluteScanPath.c_str());
  283. }
  284. else if (AZ::IO::FileIOBase::GetInstance()->Exists(absoluteScanPath.c_str()))
  285. {
  286. // The path specified in the config file is a file
  287. m_watchedFiles.insert(absoluteScanPath.c_str());
  288. }
  289. }
  290. }
  291. AZStd::string AssetBundlerTabWidget::GetAssetBundlerUserSettingsFile(const char* currentProjectFolderPath)
  292. {
  293. AZ::IO::Path absoluteFilePath = AZ::IO::Path(currentProjectFolderPath)
  294. / AZ::SettingsRegistryConstants::DevUserRegistryFolder
  295. / AssetBundlerUserSettingsFile;
  296. return absoluteFilePath.Native();
  297. }
  298. AZStd::string AssetBundlerTabWidget::GetAssetBundlerCommonSettingsFile(const char* currentProjectFolderPath)
  299. {
  300. AZ::IO::Path absoluteFilePath = AZ::IO::Path(currentProjectFolderPath)
  301. / AZ::SettingsRegistryConstants::RegistryFolder
  302. / AssetBundlerCommonSettingsFile;
  303. return absoluteFilePath.Native();
  304. }
  305. void AssetBundlerTabWidget::CreateEmptyAssetBundlerSettings(const AZStd::string& filePath)
  306. {
  307. QJsonObject assetBundlerSettings;
  308. QJsonObject scanPathSettings;
  309. for (const auto& fileType : AssetBundlingFileTypes)
  310. {
  311. scanPathSettings.insert(fileType, QJsonArray());
  312. }
  313. assetBundlerSettings.insert(ScanPathsKey, scanPathSettings);
  314. AssetBundler::SaveJson(filePath, assetBundlerSettings);
  315. }
  316. } // namespace AssetBundler
  317. #include <source/ui/moc_AssetBundlerTabWidget.cpp>