3
0

AtomToolsAssetBrowser.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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 <AssetBrowser/ui_AtomToolsAssetBrowser.h>
  9. #include <AtomToolsFramework/AssetBrowser/AtomToolsAssetBrowser.h>
  10. #include <AtomToolsFramework/Util/Util.h>
  11. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  12. #include <AzCore/std/sort.h>
  13. #include <AzFramework/StringFunc/StringFunc.h>
  14. #include <AzQtComponents/Utilities/DesktopUtilities.h>
  15. #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
  16. #include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
  17. #include <AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.h>
  18. #include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
  19. #include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
  20. #include <AzToolsFramework/AssetBrowser/Search/Filter.h>
  21. #include <AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h>
  22. AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
  23. #include <QAction>
  24. #include <QCursor>
  25. #include <QMenu>
  26. #include <QMessageBox>
  27. #include <QPushButton>
  28. AZ_POP_DISABLE_WARNING
  29. namespace AtomToolsFramework
  30. {
  31. AtomToolsAssetBrowser::AtomToolsAssetBrowser(QWidget* parent)
  32. : QWidget(parent)
  33. , m_ui(new Ui::AtomToolsAssetBrowser)
  34. {
  35. using namespace AzToolsFramework::AssetBrowser;
  36. m_ui->setupUi(this);
  37. m_ui->m_searchWidget->Setup(true, true);
  38. m_ui->m_searchWidget->setMinimumSize(QSize(150, 0));
  39. m_ui->m_splitter->setSizes(QList<int>() << 400 << 200);
  40. m_ui->m_splitter->setStretchFactor(0, 1);
  41. // Get the asset browser model
  42. AssetBrowserModel* assetBrowserModel = nullptr;
  43. AssetBrowserComponentRequestBus::BroadcastResult(assetBrowserModel, &AssetBrowserComponentRequests::GetAssetBrowserModel);
  44. AZ_Assert(assetBrowserModel, "Failed to get file browser model");
  45. // Hook up the data set to the tree view
  46. m_filterModel = aznew AssetBrowserFilterModel(this);
  47. m_filterModel->setSourceModel(assetBrowserModel);
  48. m_filterModel->SetFilter(CreateFilter());
  49. m_ui->m_assetBrowserTreeViewWidget->setModel(m_filterModel);
  50. m_ui->m_assetBrowserTreeViewWidget->SetShowSourceControlIcons(false);
  51. m_ui->m_assetBrowserTreeViewWidget->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
  52. // Maintains the tree expansion state between runs
  53. m_ui->m_assetBrowserTreeViewWidget->SetName("AssetBrowserTreeView_main");
  54. connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AtomToolsAssetBrowser::UpdateFilter);
  55. connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::activated, this, &AtomToolsAssetBrowser::OpenSelectedEntries);
  56. connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::selectionChangedSignal, this, &AtomToolsAssetBrowser::UpdatePreview);
  57. connect(m_ui->m_searchWidget->GetFilter().data(), &AssetBrowserEntryFilter::updatedSignal, m_filterModel, &AssetBrowserFilterModel::filterUpdatedSlot);
  58. InitOptionsMenu();
  59. InitSettingsHandler();
  60. InitSettings();
  61. }
  62. AtomToolsAssetBrowser::~AtomToolsAssetBrowser()
  63. {
  64. // Disconnect the event handler before saving settings so that it does not get triggered from the destructor.
  65. m_settingsNotifyEventHandler.Disconnect();
  66. // Rewrite any potentially unsaved settings to the registry.
  67. SaveSettings();
  68. // Maintains the tree expansion state between runs
  69. m_ui->m_assetBrowserTreeViewWidget->SaveState();
  70. AZ::SystemTickBus::Handler::BusDisconnect();
  71. }
  72. AzToolsFramework::AssetBrowser::SearchWidget* AtomToolsAssetBrowser::GetSearchWidget()
  73. {
  74. return m_ui->m_searchWidget;
  75. }
  76. void AtomToolsAssetBrowser::SetFileTypeFilters(const FileTypeFilterVec& fileTypeFilters)
  77. {
  78. m_fileTypeFilters = fileTypeFilters;
  79. // Pre sort the file type filters just so that they are organized alphabetically in the menu.
  80. AZStd::sort(
  81. m_fileTypeFilters.begin(),
  82. m_fileTypeFilters.end(),
  83. [](const auto& fileTypeFilter1, const auto& fileTypeFilter2)
  84. {
  85. return fileTypeFilter1.m_name < fileTypeFilter2.m_name;
  86. });
  87. UpdateFileTypeFilters();
  88. }
  89. void AtomToolsAssetBrowser::UpdateFileTypeFilters()
  90. {
  91. m_fileTypeFiltersEnabled = AZStd::any_of(
  92. m_fileTypeFilters.begin(),
  93. m_fileTypeFilters.end(),
  94. [](const auto& fileTypeFilter)
  95. {
  96. return fileTypeFilter.m_enabled;
  97. });
  98. }
  99. void AtomToolsAssetBrowser::SetOpenHandler(AZStd::function<void(const AZStd::string&)> openHandler)
  100. {
  101. m_openHandler = openHandler;
  102. }
  103. void AtomToolsAssetBrowser::SelectEntries(const AZStd::string& absolutePath)
  104. {
  105. AZ::SystemTickBus::Handler::BusDisconnect();
  106. m_pathToSelect = absolutePath;
  107. if (ValidateDocumentPath(m_pathToSelect))
  108. {
  109. // Selecting a new asset in the browser is not guaranteed to happen immediately.
  110. // The asset browser model notifications are sent before the model is updated.
  111. // Instead of relying on the notifications, queue the selection and process it on tick until this change occurs.
  112. AZ::SystemTickBus::Handler::BusConnect();
  113. }
  114. }
  115. void AtomToolsAssetBrowser::OpenSelectedEntries()
  116. {
  117. const AZStd::vector<const AssetBrowserEntry*> entries = m_ui->m_assetBrowserTreeViewWidget->GetSelectedAssets();
  118. const bool promptToOpenMultipleFiles =
  119. GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AssetBrowser/PromptToOpenMultipleFiles", true);
  120. const AZ::u64 promptToOpenMultipleFilesThreshold =
  121. GetSettingsValue<AZ::u64>("/O3DE/AtomToolsFramework/AssetBrowser/PromptToOpenMultipleFilesThreshold", 10);
  122. if (promptToOpenMultipleFiles && promptToOpenMultipleFilesThreshold <= entries.size())
  123. {
  124. QMessageBox::StandardButton result = QMessageBox::question(
  125. GetToolMainWindow(),
  126. tr("Attemptng to open %1 files").arg(entries.size()),
  127. tr("Would you like to open anyway?"),
  128. QMessageBox::Yes | QMessageBox::No);
  129. if (result == QMessageBox::No)
  130. {
  131. return;
  132. }
  133. }
  134. for (const AssetBrowserEntry* entry : entries)
  135. {
  136. if (entry && entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Folder && m_openHandler)
  137. {
  138. m_openHandler(entry->GetFullPath().c_str());
  139. }
  140. }
  141. }
  142. AzToolsFramework::AssetBrowser::FilterConstType AtomToolsAssetBrowser::CreateFilter() const
  143. {
  144. using namespace AzToolsFramework::AssetBrowser;
  145. auto filterFn = [this](const AssetBrowserEntry* entry)
  146. {
  147. switch (entry->GetEntryType())
  148. {
  149. case AssetBrowserEntry::AssetEntryType::Folder:
  150. {
  151. if (const auto& path = entry->GetFullPath(); !path.empty() && !IsPathIgnored(path))
  152. {
  153. return m_showEmptyFolders;
  154. }
  155. // The path is invalid or ignored
  156. return false;
  157. }
  158. case AssetBrowserEntry::AssetEntryType::Source:
  159. {
  160. if (const auto& path = entry->GetFullPath(); !path.empty() && !IsPathIgnored(path))
  161. {
  162. // Filter assets against supported extensions instead of using asset type comparisons
  163. if (m_fileTypeFiltersEnabled)
  164. {
  165. for (const auto& fileTypeFilter : m_fileTypeFilters)
  166. {
  167. if (fileTypeFilter.m_enabled)
  168. {
  169. for (const auto& extension : fileTypeFilter.m_extensions)
  170. {
  171. if (AZ::StringFunc::EndsWith(path, extension))
  172. {
  173. return true;
  174. }
  175. }
  176. }
  177. }
  178. // Filters were enabled but no matching filter was found so exclude this entry.
  179. return false;
  180. }
  181. // Filters were not enabled so automatically include all entries.
  182. return true;
  183. }
  184. // The path is invalid or ignored
  185. return false;
  186. }
  187. }
  188. return false;
  189. };
  190. // The custom filter uses a lambda or function pointer instead of combining complicated filter logic operations. The filter must
  191. // propagate down in order to support showing and hiding empty folders.
  192. CustomFilter* customFilter = new CustomFilter(filterFn);
  193. customFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
  194. QSharedPointer<CompositeFilter> finalFilter(new CompositeFilter(CompositeFilter::LogicOperatorType::AND));
  195. finalFilter->AddFilter(FilterConstType(customFilter));
  196. finalFilter->AddFilter(m_ui->m_searchWidget->GetFilter());
  197. return finalFilter;
  198. }
  199. void AtomToolsAssetBrowser::UpdateFilter()
  200. {
  201. const bool hasFilter = !m_ui->m_searchWidget->GetFilterString().isEmpty();
  202. constexpr bool selectFirstFilteredIndex = true;
  203. m_ui->m_assetBrowserTreeViewWidget->UpdateAfterFilter(hasFilter, selectFirstFilteredIndex);
  204. }
  205. void AtomToolsAssetBrowser::UpdatePreview()
  206. {
  207. const auto& selectedAssets = m_ui->m_assetBrowserTreeViewWidget->GetSelectedAssets();
  208. if (!selectedAssets.empty())
  209. {
  210. m_ui->m_previewerFrame->Display(selectedAssets.front());
  211. }
  212. else
  213. {
  214. m_ui->m_previewerFrame->Clear();
  215. }
  216. }
  217. void AtomToolsAssetBrowser::TogglePreview()
  218. {
  219. const bool isPreviewFrameVisible = m_ui->m_previewerFrame->isVisible();
  220. m_ui->m_previewerFrame->setVisible(!isPreviewFrameVisible);
  221. if (isPreviewFrameVisible)
  222. {
  223. m_browserState = m_ui->m_splitter->saveState();
  224. m_ui->m_splitter->setSizes(QList({ 1, 0 }));
  225. }
  226. else
  227. {
  228. m_ui->m_splitter->restoreState(m_browserState);
  229. }
  230. }
  231. void AtomToolsAssetBrowser::InitOptionsMenu()
  232. {
  233. // Create pop-up menu to toggle the visibility of the asset browser preview window
  234. m_optionsMenu = new QMenu(this);
  235. QMenu::connect(
  236. m_optionsMenu,
  237. &QMenu::aboutToShow,
  238. [this]()
  239. {
  240. // Register action to toggle showing and hiding the asset preview image
  241. m_optionsMenu->clear();
  242. QAction* action = m_optionsMenu->addAction(tr("Show Asset Preview"), this, &AtomToolsAssetBrowser::TogglePreview);
  243. action->setCheckable(true);
  244. action->setChecked(m_ui->m_previewerFrame->isVisible());
  245. // Register action to toggle showing and hiding folders with no visible children
  246. m_optionsMenu->addSeparator();
  247. QAction* emptyFolderAction = m_optionsMenu->addAction(
  248. tr("Show Empty Folders"),
  249. this,
  250. [this]()
  251. {
  252. m_showEmptyFolders = !m_showEmptyFolders;
  253. m_filterModel->filterUpdatedSlot();
  254. });
  255. emptyFolderAction->setCheckable(true);
  256. emptyFolderAction->setChecked(m_showEmptyFolders);
  257. // Register actions to toggle showing and hiding asset browser entries matching supported extensions
  258. if (!m_fileTypeFilters.empty())
  259. {
  260. m_optionsMenu->addSeparator();
  261. m_optionsMenu->addAction(
  262. tr("Enable All File Filters"),
  263. this,
  264. [this]()
  265. {
  266. for (auto& fileTypeFilter : m_fileTypeFilters)
  267. {
  268. fileTypeFilter.m_enabled = true;
  269. }
  270. UpdateFileTypeFilters();
  271. m_filterModel->filterUpdatedSlot();
  272. });
  273. m_optionsMenu->addAction(
  274. tr("Disable All File Filters"),
  275. this,
  276. [this]()
  277. {
  278. for (auto& fileTypeFilter : m_fileTypeFilters)
  279. {
  280. fileTypeFilter.m_enabled = false;
  281. }
  282. UpdateFileTypeFilters();
  283. m_filterModel->filterUpdatedSlot();
  284. });
  285. m_optionsMenu->addSeparator();
  286. for (const auto& fileTypeFilter : m_fileTypeFilters)
  287. {
  288. QAction* extensionAction = m_optionsMenu->addAction(
  289. tr("Show %1 Files").arg(fileTypeFilter.m_name.c_str()),
  290. this,
  291. [this, fileTypeFilterName = fileTypeFilter.m_name]()
  292. {
  293. auto fileTypeFilterItr = AZStd::find_if(
  294. m_fileTypeFilters.begin(),
  295. m_fileTypeFilters.end(),
  296. [fileTypeFilterName](const auto& fileTypeFilter)
  297. {
  298. return fileTypeFilter.m_name == fileTypeFilterName;
  299. });
  300. if (fileTypeFilterItr != m_fileTypeFilters.end())
  301. {
  302. fileTypeFilterItr->m_enabled = !fileTypeFilterItr->m_enabled;
  303. }
  304. UpdateFileTypeFilters();
  305. m_filterModel->filterUpdatedSlot();
  306. });
  307. extensionAction->setCheckable(true);
  308. extensionAction->setChecked(fileTypeFilter.m_enabled);
  309. }
  310. }
  311. });
  312. m_ui->m_viewOptionButton->setMenu(m_optionsMenu);
  313. m_ui->m_viewOptionButton->setIcon(QIcon(":/Icons/menu.svg"));
  314. m_ui->m_viewOptionButton->setPopupMode(QToolButton::InstantPopup);
  315. }
  316. void AtomToolsAssetBrowser::InitSettingsHandler()
  317. {
  318. // Monitor for asset browser related settings changes
  319. if (auto registry = AZ::SettingsRegistry::Get())
  320. {
  321. m_settingsNotifyEventHandler = registry->RegisterNotifier(
  322. [this](const AZ::SettingsRegistryInterface::NotifyEventArgs& notifyEventArgs)
  323. {
  324. // Refresh the asset browser model if any of the filter related settings change.
  325. if (AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(
  326. "/O3DE/AtomToolsFramework/Application/IgnoreCacheFolder", notifyEventArgs.m_jsonKeyPath) ||
  327. AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(
  328. "/O3DE/AtomToolsFramework/Application/IgnoredPathRegexPatterns", notifyEventArgs.m_jsonKeyPath))
  329. {
  330. m_filterModel->filterUpdatedSlot();
  331. }
  332. });
  333. }
  334. }
  335. void AtomToolsAssetBrowser::InitSettings()
  336. {
  337. // Restoring enabled state for registered file type filters.
  338. const auto& fileTypeFilterStateMap =
  339. GetSettingsObject("/O3DE/AtomToolsFramework/AssetBrowser/FileTypeFilterStateMap", AZStd::unordered_map<AZStd::string, bool>{});
  340. for (const auto& fileTypeFilterStatePair : fileTypeFilterStateMap)
  341. {
  342. auto fileTypeFilterItr = AZStd::find_if(
  343. m_fileTypeFilters.begin(),
  344. m_fileTypeFilters.end(),
  345. [fileTypeFilterStatePair](const auto& fileTypeFilter)
  346. {
  347. return fileTypeFilter.m_name == fileTypeFilterStatePair.first;
  348. });
  349. if (fileTypeFilterItr != m_fileTypeFilters.end())
  350. {
  351. fileTypeFilterItr->m_enabled = fileTypeFilterStatePair.second;
  352. }
  353. }
  354. m_showEmptyFolders = GetSettingsValue("/O3DE/AtomToolsFramework/AssetBrowser/ShowEmptyFolders", false);
  355. UpdateFileTypeFilters();
  356. }
  357. void AtomToolsAssetBrowser::SaveSettings()
  358. {
  359. // Record the enabled state for each of the file type filters
  360. AZStd::unordered_map<AZStd::string, bool> fileTypeFilterStateMap;
  361. for (const auto& fileTypeFilter : m_fileTypeFilters)
  362. {
  363. fileTypeFilterStateMap[fileTypeFilter.m_name] = fileTypeFilter.m_enabled;
  364. }
  365. SetSettingsObject("/O3DE/AtomToolsFramework/AssetBrowser/FileTypeFilterStateMap", fileTypeFilterStateMap);
  366. SetSettingsValue("/O3DE/AtomToolsFramework/AssetBrowser/ShowEmptyFolders", m_showEmptyFolders);
  367. }
  368. void AtomToolsAssetBrowser::OnSystemTick()
  369. {
  370. if (!ValidateDocumentPath(m_pathToSelect))
  371. {
  372. AZ::SystemTickBus::Handler::BusDisconnect();
  373. m_pathToSelect.clear();
  374. return;
  375. }
  376. // Attempt to select the new path
  377. m_ui->m_assetBrowserTreeViewWidget->SelectFileAtPath(m_pathToSelect);
  378. // Iterate over the selected entries to verify if the selection was made
  379. for (const AssetBrowserEntry* entry : m_ui->m_assetBrowserTreeViewWidget->GetSelectedAssets())
  380. {
  381. if (entry)
  382. {
  383. AZStd::string sourcePath = entry->GetFullPath();
  384. if (ValidateDocumentPath(sourcePath) && AZ::StringFunc::Equal(m_pathToSelect, sourcePath))
  385. {
  386. // Once the selection is confirmed, cancel the operation and disconnect
  387. AZ::SystemTickBus::Handler::BusDisconnect();
  388. m_pathToSelect.clear();
  389. }
  390. }
  391. }
  392. }
  393. } // namespace AtomToolsFramework
  394. #include <AtomToolsFramework/AssetBrowser/moc_AtomToolsAssetBrowser.cpp>