AzAssetBrowserRequestHandler.cpp 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  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 "EditorDefs.h"
  9. #include <AzAssetBrowser/AzAssetBrowserMultiWindow.h>
  10. #include "AzAssetBrowser/AzAssetBrowserWindow.h"
  11. #include "AzAssetBrowserRequestHandler.h"
  12. // Qt
  13. #include <QDesktopServices>
  14. #include <QMenu>
  15. #include <QObject>
  16. #include <QString>
  17. // AzCore
  18. #include <AzCore/std/string/wildcard.h>
  19. #include <AzCore/std/sort.h>
  20. #include <AzCore/Asset/AssetManager.h>
  21. #include <AzCore/Asset/AssetTypeInfoBus.h>
  22. // AzFramework
  23. #include <AzFramework/API/ApplicationAPI.h>
  24. #include <AzFramework/Asset/GenericAssetHandler.h>
  25. // AzToolsFramework
  26. #include <AzToolsFramework/API/EntityCompositionRequestBus.h>
  27. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryUtils.h>
  28. #include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
  29. #include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
  30. #include <AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h>
  31. #include <AzToolsFramework/AssetBrowser/Views/AssetBrowserListView.h>
  32. #include <AzToolsFramework/AssetBrowser/Views/AssetBrowserThumbnailView.h>
  33. #include <AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h>
  34. #include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
  35. #include <AzToolsFramework/Commands/EntityStateCommand.h>
  36. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  37. #include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
  38. #include <AzToolsFramework/Slice/SliceUtilities.h>
  39. #include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
  40. #include <AzToolsFramework/ToolsComponents/EditorLayerComponent.h>
  41. #include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
  42. #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
  43. #include <AzToolsFramework/UI/Slice/SliceRelationshipBus.h>
  44. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  45. // AzQtComponents
  46. #include <AzQtComponents/DragAndDrop/ViewportDragAndDrop.h>
  47. #include <AzToolsFramework/UI/Outliner/EntityOutlinerDragAndDropContext.h>
  48. // Editor
  49. #include "IEditor.h"
  50. #include "Include/IObjectManager.h"
  51. #include "CryEditDoc.h"
  52. #include "QtViewPaneManager.h"
  53. namespace AzAssetBrowserRequestHandlerPrivate
  54. {
  55. using namespace AzToolsFramework;
  56. using namespace AzToolsFramework::AssetBrowser;
  57. bool ProductHasAssociatedComponent(const ProductAssetBrowserEntry* product);
  58. // given a list of products all belonging to the same parent source file
  59. // select just one that can best represent the entire source file.
  60. // it does so by collecting all of the assets that have valid create-able
  61. // components associated with them and sorts them using
  62. // a heuristic which prefers exact name matches, and operates in alphabetic order otherwise.
  63. // for example in the scenario, where we pass in the products all from bike.fbx:
  64. // bike.fbx
  65. // +--- wheel01.model
  66. // +--- wheel02.model
  67. // +--- chasis.model
  68. // +--- bike_gloss_texture.texture
  69. // +--- bike_gloss_material.material
  70. // +--- bike.model <--- select this one, its an exact match
  71. //
  72. // this behavior can be controlled by an AssetTypeInfo listener, it can add priority
  73. // to the sort order of an asset by type. For example in the above scenario, an asset
  74. // type listener could override the priority of 'material' and thus cause it to end up
  75. // higher up or lower down on the final list of products to choose from.
  76. const ProductAssetBrowserEntry* GetPrimaryProduct(AZStd::vector<const ProductAssetBrowserEntry*>& entries)
  77. {
  78. const SourceAssetBrowserEntry* parentSource = nullptr;
  79. AZStd::vector<const ProductAssetBrowserEntry*> validEntries;
  80. validEntries.reserve(entries.size());
  81. for (const auto entry : entries)
  82. {
  83. if (!ProductHasAssociatedComponent(entry))
  84. {
  85. continue;
  86. }
  87. validEntries.push_back(entry);
  88. }
  89. if (validEntries.empty())
  90. {
  91. return nullptr;
  92. }
  93. // make sure all the valid entries share the same parent source file, and get the source!
  94. for (const auto entry : validEntries)
  95. {
  96. if (entry->GetParent() == nullptr)
  97. {
  98. return entry; // return the first one in the case of a weird disconnected situation
  99. }
  100. if ((parentSource) && (parentSource != entry->GetParent()))
  101. {
  102. return entry; // if they have different parents, something is wrong, return the first one we find.
  103. }
  104. if (entry->GetParent()->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
  105. {
  106. parentSource = static_cast<const SourceAssetBrowserEntry*>(entry->GetParent());
  107. }
  108. }
  109. if (!parentSource)
  110. {
  111. return nullptr;
  112. }
  113. AZ::IO::Path parentName = parentSource->GetName();
  114. AZ::IO::PathView parentNameWithoutExtension = parentName.Stem();
  115. AZ::IO::PathView parentNameWithoutExtensionCaseInsensitive(parentNameWithoutExtension.Native(), AZ::IO::WindowsPathSeparator);
  116. // if we get here, we know that all the entries have the same source parent and are thus related to each other.
  117. // in this case, we can run the heuristic.
  118. // sort the valid entries so that they are arranged from most preferred to least preferred
  119. // asset to create a component for:
  120. AZStd::sort(
  121. validEntries.begin(),
  122. validEntries.end(),
  123. [&](const ProductAssetBrowserEntry* a, const ProductAssetBrowserEntry* b)
  124. {
  125. // first, we use the priority system:
  126. int sortPriorityA = 0;
  127. int sortPriorityB = 0;
  128. AZ::AssetTypeInfoBus::EventResult(
  129. sortPriorityA, a->GetAssetType(),
  130. &AZ::AssetTypeInfo::GetAssetTypeDragAndDropCreationPriority);
  131. AZ::AssetTypeInfoBus::EventResult(
  132. sortPriorityB, b->GetAssetType(),
  133. &AZ::AssetTypeInfo::GetAssetTypeDragAndDropCreationPriority);
  134. if (sortPriorityA > sortPriorityB)
  135. {
  136. return true; // A definitely before B
  137. }
  138. else if (sortPriorityA < sortPriorityB)
  139. {
  140. return false; // B definitely before A
  141. }
  142. // Getting here means their sort priority is equal. When this is the case, we use a
  143. // secondary sort scheme, which is to prefer the assets that have the exact
  144. // same name as the parent source asset:
  145. // Make a temporary PathView that uses a WindowsPathSeparator, to have the path comparisons be case-insensitive on
  146. // non-Windows platforms as well. By default Windows uses the WindowsPathSeparator so it is already case-insensitive on
  147. // Windows
  148. AZ::IO::PathView nameAWithoutExtension = AZ::IO::PathView(a->GetName(), AZ::IO::WindowsPathSeparator).Stem();
  149. AZ::IO::PathView nameBWithoutExtension = AZ::IO::PathView(b->GetName(), AZ::IO::WindowsPathSeparator).Stem();
  150. const bool aMatches = nameAWithoutExtension == parentNameWithoutExtensionCaseInsensitive;
  151. const bool bMatches = nameBWithoutExtension == parentNameWithoutExtensionCaseInsensitive;
  152. if ((aMatches) && (!bMatches))
  153. {
  154. return true; // A definitely before B
  155. }
  156. if ((!aMatches) && (bMatches))
  157. {
  158. return false; // B definitely before A
  159. }
  160. // if nothing else, sort alphabetically. A is before B if
  161. // strcmp A, B < 0. Otherwise A is not before B. Use the extension
  162. // here so as to eliminate ties.
  163. return a->GetName() < b->GetName();
  164. });
  165. // since the valid entries are already sorted, just return the first one.
  166. return validEntries[0];
  167. }
  168. // return true if a given product has an asociated component type.
  169. bool ProductHasAssociatedComponent(const ProductAssetBrowserEntry* product)
  170. {
  171. if (!product)
  172. {
  173. return false;
  174. }
  175. if (product->GetAssetType() == AZ::AzTypeInfo<AZ::SliceAsset>::Uuid())
  176. {
  177. return true; // we can always instantiate slices.
  178. }
  179. bool canCreateComponent = false;
  180. AZ::AssetTypeInfoBus::EventResult(canCreateComponent, product->GetAssetType(), &AZ::AssetTypeInfo::CanCreateComponent, product->GetAssetId());
  181. if (!canCreateComponent)
  182. {
  183. return false;
  184. }
  185. AZ::Uuid componentTypeId = AZ::Uuid::CreateNull();
  186. AZ::AssetTypeInfoBus::EventResult(componentTypeId, product->GetAssetType(), &AZ::AssetTypeInfo::GetComponentTypeId);
  187. if (componentTypeId.IsNull())
  188. {
  189. // we have a component type that handles this asset.
  190. return false;
  191. }
  192. return true;
  193. }
  194. // given a list of product assets, create an entity for each one where
  195. // appropriate. Note that the list of product assets is expected to already have been filtered
  196. // by the above functions, or are expected to be direct user choices, not sources.
  197. void CreateEntitiesAtPoint(
  198. AZStd::vector<const ProductAssetBrowserEntry*> products,
  199. AZ::Vector3 location,
  200. AZ::EntityId parentEntityId, // if valid, will treat the location as a local transform relative to this entity.
  201. EntityIdList& createdEntities,
  202. AzFramework::SliceInstantiationTicket& sliceTicket)
  203. {
  204. if (products.empty())
  205. {
  206. return;
  207. }
  208. ScopedUndoBatch undo("Create entities from assets");
  209. QWidget* mainWindow = nullptr;
  210. EditorRequests::Bus::BroadcastResult(mainWindow, &EditorRequests::GetMainWindow);
  211. const AZ::Transform worldTransform = AZ::Transform::CreateTranslation(location);
  212. struct AssetIdAndComponentTypeId
  213. {
  214. AssetIdAndComponentTypeId(AZ::Uuid componentTypeId, AZ::Data::AssetId assetId)
  215. : m_componentTypeId(componentTypeId)
  216. , m_assetId(assetId)
  217. {
  218. }
  219. AZ::Uuid m_componentTypeId = AZ::Uuid::CreateNull();
  220. AZ::Data::AssetId m_assetId;
  221. };
  222. for (const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* product : products)
  223. {
  224. // Handle instantiation of slices.
  225. if (product->GetAssetType() == AZ::AzTypeInfo<AZ::SliceAsset>::Uuid())
  226. {
  227. // Instantiate the slice at the specified location.
  228. AZ::Data::Asset<AZ::SliceAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::SliceAsset>(
  229. product->GetAssetId(), AZ::Data::AssetLoadBehavior::Default);
  230. if (asset)
  231. {
  232. SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
  233. sliceTicket, &SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, asset, worldTransform);
  234. }
  235. }
  236. else
  237. {
  238. // non-slices, regular entities:
  239. AZ::Uuid componentTypeId = AZ::Uuid::CreateNull();
  240. AZ::AssetTypeInfoBus::EventResult(componentTypeId, product->GetAssetType(), &AZ::AssetTypeInfo::GetComponentTypeId);
  241. if (!componentTypeId.IsNull())
  242. {
  243. AZ::IO::Path entryPath(product->GetName());
  244. AZStd::string entityName = entryPath.Stem().Native();
  245. if (entityName.empty())
  246. {
  247. // if we can't use the file name, use the type of asset like "Model".
  248. AZ::AssetTypeInfoBus::EventResult(entityName, product->GetAssetType(), &AZ::AssetTypeInfo::GetAssetTypeDisplayName);
  249. if (entityName.empty())
  250. {
  251. entityName = "Entity";
  252. }
  253. }
  254. AZ::EntityId targetEntityId;
  255. EditorRequests::Bus::BroadcastResult(
  256. targetEntityId, &EditorRequests::CreateNewEntityAtPosition, worldTransform.GetTranslation(), parentEntityId);
  257. AZ::Entity* newEntity = nullptr;
  258. AZ::ComponentApplicationBus::BroadcastResult(newEntity, &AZ::ComponentApplicationRequests::FindEntity, targetEntityId);
  259. if (newEntity == nullptr)
  260. {
  261. QMessageBox::warning(
  262. mainWindow,
  263. QObject::tr("Asset Drop Failed"),
  264. QStringLiteral("Could not create entity from selected asset(s)."));
  265. return;
  266. }
  267. // Deactivate the entity so the properties on the components can be set.
  268. newEntity->Deactivate();
  269. newEntity->SetName(entityName);
  270. AZ::ComponentTypeList componentsToAdd;
  271. componentsToAdd.push_back(componentTypeId);
  272. // Add the product as components to this entity.
  273. AZStd::vector<AZ::EntityId> entityIds = { targetEntityId };
  274. EntityCompositionRequests::AddComponentsOutcome addComponentsOutcome = AZ::Failure(AZStd::string());
  275. EntityCompositionRequestBus::BroadcastResult(
  276. addComponentsOutcome, &EntityCompositionRequests::AddComponentsToEntities, entityIds, componentsToAdd);
  277. if (!addComponentsOutcome.IsSuccess())
  278. {
  279. AZ_Error(
  280. "AssetBrowser",
  281. false,
  282. "Could not create the requested components from the selected assets: %s",
  283. addComponentsOutcome.GetError().c_str());
  284. EditorEntityContextRequestBus::Broadcast(&EditorEntityContextRequests::DestroyEditorEntity, targetEntityId);
  285. return;
  286. }
  287. // activate the entity first, so that the primary asset change is done in the context of it being awake.
  288. newEntity->Activate();
  289. AZ::Component* componentAdded = newEntity->FindComponent(componentTypeId);
  290. if (componentAdded)
  291. {
  292. Components::EditorComponentBase* editorComponent = GetEditorComponent(componentAdded);
  293. if (editorComponent)
  294. {
  295. editorComponent->SetPrimaryAsset(product->GetAssetId());
  296. }
  297. }
  298. bool isPrefabSystemEnabled = false;
  299. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  300. isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  301. if (!isPrefabSystemEnabled)
  302. {
  303. // Prepare undo command last so it captures the final state of the entity.
  304. EntityCreateCommand* command = aznew EntityCreateCommand(static_cast<AZ::u64>(newEntity->GetId()));
  305. command->Capture(newEntity);
  306. command->SetParent(undo.GetUndoBatch());
  307. }
  308. ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::AddDirtyEntity, newEntity->GetId());
  309. createdEntities.push_back(newEntity->GetId());
  310. }
  311. }
  312. }
  313. // Select the new entity (and deselect others).
  314. if (!createdEntities.empty())
  315. {
  316. ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, createdEntities);
  317. }
  318. }
  319. }
  320. AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler()
  321. {
  322. using namespace AzToolsFramework::AssetBrowser;
  323. AssetBrowserInteractionNotificationBus::Handler::BusConnect();
  324. AzQtComponents::DragAndDropEventsBus::Handler::BusConnect(AzQtComponents::DragAndDropContexts::EditorViewport);
  325. AzQtComponents::DragAndDropItemViewEventsBus::Handler::BusConnect(AzQtComponents::DragAndDropContexts::EntityOutliner);
  326. }
  327. AzAssetBrowserRequestHandler::~AzAssetBrowserRequestHandler()
  328. {
  329. AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
  330. AzQtComponents::DragAndDropEventsBus::Handler::BusDisconnect();
  331. AzQtComponents::DragAndDropItemViewEventsBus::Handler::BusDisconnect();
  332. }
  333. void AzAssetBrowserRequestHandler::CreateSortAction(
  334. QMenu* menu,
  335. AzToolsFramework::AssetBrowser::AssetBrowserThumbnailView* thumbnailView,
  336. AzToolsFramework::AssetBrowser::AssetBrowserTreeView* treeView,
  337. QString name,
  338. AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntrySortMode sortMode)
  339. {
  340. QAction* action = menu->addAction(
  341. name,
  342. [thumbnailView, treeView, sortMode]()
  343. {
  344. if (thumbnailView)
  345. {
  346. thumbnailView->SetSortMode(sortMode);
  347. }
  348. else if (treeView)
  349. {
  350. treeView->SetSortMode(sortMode);
  351. if (treeView->GetAttachedThumbnailView())
  352. {
  353. treeView->GetAttachedThumbnailView()->SetSortMode(sortMode);
  354. }
  355. }
  356. });
  357. action->setCheckable(true);
  358. if (thumbnailView)
  359. {
  360. if (thumbnailView->GetSortMode() == sortMode)
  361. {
  362. action->setChecked(true);
  363. }
  364. }
  365. else if (treeView)
  366. {
  367. // If there is a thumbnailView attached, the sorting will be happening in there.
  368. if (treeView->GetAttachedThumbnailView())
  369. {
  370. if (treeView->GetAttachedThumbnailView()->GetSortMode() == sortMode)
  371. {
  372. action->setChecked(true);
  373. }
  374. }
  375. else if (treeView->GetSortMode() == sortMode)
  376. {
  377. action->setChecked(true);
  378. }
  379. }
  380. }
  381. void AzAssetBrowserRequestHandler::AddSortMenu(
  382. QMenu* menu,
  383. AzToolsFramework::AssetBrowser::AssetBrowserThumbnailView* thumbnailView,
  384. AzToolsFramework::AssetBrowser::AssetBrowserTreeView* treeView,
  385. AzToolsFramework::AssetBrowser::AssetBrowserTableView* tableView)
  386. {
  387. using namespace AzToolsFramework::AssetBrowser;
  388. //TableView handles its own sorting.
  389. if (tableView)
  390. {
  391. return;
  392. }
  393. // Check for the menu being called from the treeview when a tableview is active.
  394. if (treeView && treeView->GetAttachedTableView() && treeView->GetAttachedTableView()->GetTableViewActive())
  395. {
  396. return;
  397. }
  398. QMenu* sortMenu = menu->addMenu(QObject::tr("Sort by"));
  399. CreateSortAction(
  400. sortMenu,
  401. thumbnailView,
  402. treeView,
  403. QObject::tr("Name"), AssetBrowserEntry::AssetEntrySortMode::Name);
  404. CreateSortAction(
  405. sortMenu,
  406. thumbnailView,
  407. treeView,
  408. QObject::tr("Type"), AssetBrowserEntry::AssetEntrySortMode::FileType);
  409. CreateSortAction(
  410. sortMenu,
  411. thumbnailView,
  412. treeView,
  413. QObject::tr("Date"), AssetBrowserEntry::AssetEntrySortMode::LastModified);
  414. CreateSortAction(
  415. sortMenu,
  416. thumbnailView,
  417. treeView,
  418. QObject::tr("Size"), AssetBrowserEntry::AssetEntrySortMode::Size);
  419. }
  420. void AzAssetBrowserRequestHandler::AddCreateMenu(QMenu* menu, AZStd::string fullFilePath)
  421. {
  422. using namespace AzToolsFramework::AssetBrowser;
  423. AZStd::string folderPath;
  424. AzFramework::StringFunc::Path::GetFolderPath(fullFilePath.c_str(), folderPath);
  425. AZ::Uuid sourceID = AZ::Uuid::CreateNull();
  426. SourceFileCreatorList creators;
  427. AssetBrowserInteractionNotificationBus::Broadcast(
  428. &AssetBrowserInteractionNotificationBus::Events::AddSourceFileCreators, folderPath.c_str(), sourceID, creators);
  429. if (!creators.empty())
  430. {
  431. QMenu* createMenu = menu->addMenu(QObject::tr("Create"));
  432. for (const SourceFileCreatorDetails& creatorDetails : creators)
  433. {
  434. if (creatorDetails.m_creator)
  435. {
  436. createMenu->addAction(creatorDetails.m_iconToUse, QObject::tr(creatorDetails.m_displayText.c_str()), [sourceID, fullFilePath, creatorDetails]()
  437. {
  438. creatorDetails.m_creator(fullFilePath.c_str(), sourceID);
  439. });
  440. }
  441. }
  442. }
  443. }
  444. void AzAssetBrowserRequestHandler::AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<const AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
  445. {
  446. using namespace AzToolsFramework::AssetBrowser;
  447. QString callerName = QString();
  448. bool calledFromAssetBrowser = false;
  449. AssetBrowserTreeView* treeView = qobject_cast<AssetBrowserTreeView*>(caller);
  450. if (treeView)
  451. {
  452. calledFromAssetBrowser = treeView->GetIsAssetBrowserMainView();
  453. }
  454. AssetBrowserListView* listView = qobject_cast<AssetBrowserListView*>(caller);
  455. if (listView)
  456. {
  457. calledFromAssetBrowser |= listView->GetIsAssetBrowserMainView();
  458. }
  459. AssetBrowserThumbnailView* thumbnailView = qobject_cast<AssetBrowserThumbnailView*>(caller);
  460. if (thumbnailView)
  461. {
  462. calledFromAssetBrowser |= thumbnailView->GetIsAssetBrowserMainView();
  463. }
  464. AssetBrowserTableView* tableView = qobject_cast<AssetBrowserTableView*>(caller);
  465. if (tableView)
  466. {
  467. calledFromAssetBrowser |= tableView->GetIsAssetBrowserMainView();
  468. }
  469. if (!treeView && !listView && !thumbnailView && !tableView)
  470. {
  471. return;
  472. }
  473. const AssetBrowserEntry* entry = entries.empty() ? nullptr : entries.front();
  474. if (!entry)
  475. {
  476. return;
  477. }
  478. if (calledFromAssetBrowser)
  479. {
  480. AddSortMenu(menu, thumbnailView, treeView, tableView);
  481. }
  482. size_t numOfEntries = entries.size();
  483. AZStd::string fullFilePath;
  484. AZStd::string extension;
  485. bool selectionIsSource{ true };
  486. switch (entry->GetEntryType())
  487. {
  488. case AssetBrowserEntry::AssetEntryType::Product:
  489. // if its a product, we actually want to perform these operations on the source
  490. // which will be the parent of the product.
  491. entry = entry->GetParent();
  492. if ((!entry) || (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source))
  493. {
  494. AZ_Assert(false, "Asset Browser entry product has a non-source parent?");
  495. break; // no valid parent.
  496. }
  497. selectionIsSource = false;
  498. // the fall through to the next case is intentional here.
  499. case AssetBrowserEntry::AssetEntryType::Source:
  500. {
  501. AZ::Uuid sourceID = azrtti_cast<const SourceAssetBrowserEntry*>(entry)->GetSourceUuid();
  502. fullFilePath = entry->GetFullPath();
  503. AzFramework::StringFunc::Path::GetExtension(fullFilePath.c_str(), extension);
  504. // Context menu entries that only make sense when there is only one selection should go in here
  505. // For example, open and rename
  506. if (numOfEntries == 1)
  507. {
  508. // Add the "Open" menu item.
  509. // Note that source file openers are allowed to "veto" the showing of the "Open" menu if it is 100% known that they aren't
  510. // openable! for example, custom data formats that are made by Open 3D Engine that can not have a program associated in the
  511. // operating system to view them. If the only opener that can open that file has no m_opener, then it is not openable.
  512. SourceFileOpenerList openers;
  513. AssetBrowserInteractionNotificationBus::Broadcast(
  514. &AssetBrowserInteractionNotificationBus::Events::AddSourceFileOpeners, fullFilePath.c_str(), sourceID, openers);
  515. bool validOpenersFound = false;
  516. bool vetoOpenerFound = false;
  517. for (const SourceFileOpenerDetails& openerDetails : openers)
  518. {
  519. if (openerDetails.m_opener)
  520. {
  521. // we found a valid opener (non-null). This means that the system is saying that it knows how to internally
  522. // edit this source file and has a custom editor for it.
  523. validOpenersFound = true;
  524. }
  525. else
  526. {
  527. // if we get here it means someone intentionally registered a callback with a null function pointer
  528. // the API treats this as a 'veto' opener - meaning that the system wants us NOT to allow the operating system
  529. // to open this source file as a default fallback.
  530. vetoOpenerFound = true;
  531. }
  532. }
  533. if (validOpenersFound)
  534. {
  535. // if we get here then there is an opener installed for this kind of asset
  536. // and it is not null, meaning that it is not vetoing our ability to open the file.
  537. for (const SourceFileOpenerDetails& openerDetails : openers)
  538. {
  539. // bind that function to the current loop element.
  540. if (openerDetails.m_opener) // only VALID openers with an actual callback.
  541. {
  542. menu->addAction(
  543. openerDetails.m_iconToUse, QObject::tr(openerDetails.m_displayText.c_str()),
  544. [sourceID, fullFilePath, openerDetails]()
  545. {
  546. openerDetails.m_opener(fullFilePath.c_str(), sourceID);
  547. });
  548. }
  549. }
  550. }
  551. // we always add the default "open with your operating system" unless a veto opener is found
  552. if (!vetoOpenerFound)
  553. {
  554. // if we found no valid openers and no veto openers then just allow it to be opened with the operating system itself.
  555. menu->addAction(
  556. QObject::tr("Open with associated application..."),
  557. [fullFilePath]()
  558. {
  559. OpenWithOS(fullFilePath);
  560. });
  561. }
  562. menu->addAction(
  563. QObject::tr("Open in another Asset Browser"),
  564. [fullFilePath, thumbnailView, tableView]()
  565. {
  566. AzAssetBrowserWindow* newAssetBrowser = AzAssetBrowserMultiWindow::OpenNewAssetBrowserWindow();
  567. if (thumbnailView)
  568. {
  569. newAssetBrowser->SetCurrentMode(AssetBrowserMode::ThumbnailView);
  570. }
  571. else if (tableView)
  572. {
  573. newAssetBrowser->SetCurrentMode(AssetBrowserMode::TableView);
  574. }
  575. else
  576. {
  577. newAssetBrowser->SetCurrentMode(AssetBrowserMode::ListView);
  578. }
  579. newAssetBrowser->SelectAsset(fullFilePath.c_str());
  580. });
  581. AZStd::vector<const ProductAssetBrowserEntry*> products;
  582. entry->GetChildrenRecursively<ProductAssetBrowserEntry>(products);
  583. // slice source files need to react by adding additional menu items, regardless of status of compile or presence of products.
  584. if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str(), false))
  585. {
  586. AzToolsFramework::SliceUtilities::CreateSliceAssetContextMenu(menu, fullFilePath);
  587. // SliceUtilities is in AZToolsFramework and can't open viewports, so add the relationship view open command here.
  588. if (!products.empty())
  589. {
  590. const ProductAssetBrowserEntry* productEntry = products[0];
  591. menu->addAction(
  592. "Open in Slice Relationship View",
  593. [productEntry]()
  594. {
  595. QtViewPaneManager::instance()->OpenPane(LyViewPane::SliceRelationships);
  596. const ProductAssetBrowserEntry* product = azrtti_cast<const ProductAssetBrowserEntry*>(productEntry);
  597. AzToolsFramework::SliceRelationshipRequestBus::Broadcast(
  598. &AzToolsFramework::SliceRelationshipRequests::OnSliceRelationshipViewRequested, product->GetAssetId());
  599. });
  600. }
  601. }
  602. else if (AzFramework::StringFunc::Equal(
  603. extension.c_str(), AzToolsFramework::Layers::EditorLayerComponent::GetLayerExtensionWithDot().c_str(), false))
  604. {
  605. QString levelPath = Path::GetPath(GetIEditor()->GetDocument()->GetActivePathName());
  606. AzToolsFramework::Layers::EditorLayerComponent::CreateLayerAssetContextMenu(menu, fullFilePath, levelPath);
  607. }
  608. if (!products.empty() || (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source))
  609. {
  610. CFileUtil::PopulateQMenu(caller, menu, fullFilePath);
  611. }
  612. if (calledFromAssetBrowser && selectionIsSource)
  613. {
  614. // Add Rename option
  615. QAction* action = menu->addAction(
  616. QObject::tr("Rename asset"),
  617. [treeView, listView, thumbnailView, tableView]()
  618. {
  619. if (treeView)
  620. {
  621. treeView->RenameEntry();
  622. }
  623. else if (listView)
  624. {
  625. listView->RenameEntry();
  626. }
  627. else if (thumbnailView)
  628. {
  629. thumbnailView->RenameEntry();
  630. }
  631. else if (tableView)
  632. {
  633. tableView->RenameEntry();
  634. }
  635. });
  636. action->setShortcut(Qt::Key_F2);
  637. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  638. }
  639. }
  640. if (calledFromAssetBrowser && selectionIsSource)
  641. {
  642. // Add Delete option
  643. QAction* action = menu->addAction(
  644. QObject::tr("Delete asset%1").arg(numOfEntries > 1 ? "s" : ""),
  645. [treeView, listView, thumbnailView, tableView]()
  646. {
  647. if (treeView)
  648. {
  649. treeView->DeleteEntries();
  650. }
  651. else if (listView)
  652. {
  653. listView->DeleteEntries();
  654. }
  655. else if (thumbnailView)
  656. {
  657. thumbnailView->DeleteEntries();
  658. }
  659. else if (tableView)
  660. {
  661. tableView->DeleteEntries();
  662. }
  663. });
  664. action->setShortcut(QKeySequence::Delete);
  665. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  666. // Add Duplicate option
  667. action = menu->addAction(
  668. QObject::tr("Duplicate asset%1").arg(numOfEntries > 1 ? "s" : ""),
  669. [treeView, listView, thumbnailView, tableView]()
  670. {
  671. if (treeView)
  672. {
  673. treeView->DuplicateEntries();
  674. }
  675. else if (listView)
  676. {
  677. listView->DuplicateEntries();
  678. }
  679. else if (thumbnailView)
  680. {
  681. thumbnailView->DuplicateEntries();
  682. }
  683. else if (tableView)
  684. {
  685. tableView->DuplicateEntries();
  686. }
  687. });
  688. action->setShortcut(QKeySequence("Ctrl+D"));
  689. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  690. // Add Move to option
  691. menu->addAction(
  692. QObject::tr("Move to"),
  693. [treeView, listView, thumbnailView, tableView]()
  694. {
  695. if (treeView)
  696. {
  697. treeView->MoveEntries();
  698. }
  699. else if (listView)
  700. {
  701. listView->MoveEntries();
  702. }
  703. else if (thumbnailView)
  704. {
  705. thumbnailView->MoveEntries();
  706. }
  707. else if (tableView)
  708. {
  709. tableView->MoveEntries();
  710. }
  711. });
  712. }
  713. }
  714. break;
  715. case AssetBrowserEntry::AssetEntryType::Folder:
  716. {
  717. fullFilePath = entry->GetFullPath();
  718. CFileUtil::PopulateQMenu(caller, menu, fullFilePath);
  719. if (calledFromAssetBrowser)
  720. {
  721. if (numOfEntries == 1)
  722. {
  723. // Add Rename option
  724. QAction* action = menu->addAction(
  725. QObject::tr("Rename Folder"),
  726. [treeView, listView, thumbnailView, tableView]()
  727. {
  728. if (treeView)
  729. {
  730. treeView->RenameEntry();
  731. }
  732. else if (listView)
  733. {
  734. listView->RenameEntry();
  735. }
  736. else if (thumbnailView)
  737. {
  738. thumbnailView->RenameEntry();
  739. }
  740. else if (tableView)
  741. {
  742. tableView->RenameEntry();
  743. }
  744. });
  745. action->setShortcut(Qt::Key_F2);
  746. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  747. // Add Delete option
  748. action = menu->addAction(
  749. QObject::tr("Delete Folder"),
  750. [treeView, listView, thumbnailView, tableView]()
  751. {
  752. if (treeView)
  753. {
  754. treeView->DeleteEntries();
  755. }
  756. else if (listView)
  757. {
  758. listView->DeleteEntries();
  759. }
  760. else if (thumbnailView)
  761. {
  762. thumbnailView->DeleteEntries();
  763. }
  764. else if (tableView)
  765. {
  766. tableView->DeleteEntries();
  767. }
  768. });
  769. action->setShortcut(QKeySequence::Delete);
  770. action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  771. // Add Move to option
  772. menu->addAction(
  773. QObject::tr("Move to"),
  774. [treeView, listView, thumbnailView, tableView]()
  775. {
  776. if (treeView)
  777. {
  778. treeView->MoveEntries();
  779. }
  780. else if (listView)
  781. {
  782. listView->MoveEntries();
  783. }
  784. else if (thumbnailView)
  785. {
  786. thumbnailView->MoveEntries();
  787. }
  788. else if (tableView)
  789. {
  790. tableView->MoveEntries();
  791. }
  792. });
  793. AddCreateMenu(menu, fullFilePath);
  794. }
  795. }
  796. }
  797. break;
  798. default:
  799. break;
  800. }
  801. }
  802. bool AzAssetBrowserRequestHandler::CanAcceptDragAndDropEvent(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) const
  803. {
  804. using namespace AzQtComponents;
  805. using namespace AzToolsFramework;
  806. using namespace AzToolsFramework::AssetBrowser;
  807. using namespace AzAssetBrowserRequestHandlerPrivate;
  808. // if a listener with a higher priority already claimed this event, do not touch it.
  809. ViewportDragContext* viewportDragContext = azrtti_cast<ViewportDragContext*>(&context);
  810. if ((!event) || (!event->mimeData()) || (event->isAccepted()) || (!viewportDragContext))
  811. {
  812. return false;
  813. }
  814. return DecodeDragMimeData(event->mimeData());
  815. }
  816. bool AzAssetBrowserRequestHandler::DecodeDragMimeData(const QMimeData* mimeData,
  817. AZStd::vector<const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry*>* outProducts) const
  818. {
  819. using namespace AzToolsFramework;
  820. using namespace AzToolsFramework::AssetBrowser;
  821. using namespace AzAssetBrowserRequestHandlerPrivate;
  822. // what we'd like to do with drop events is create an entity per selected logical asset
  823. // Note that some types of source files (FBX,...) produce more than one product. In this case,
  824. // this default fallback handler will choose the most likely representitive one using a heuristic
  825. // and only return that one.
  826. // once AB supports multi-select, the data might contain a mixture of
  827. // selected source files, selected products. Selecting a source file should evaluate its products
  828. // selecting a product should always use the product.
  829. bool canAcceptEvent = false;
  830. AZStd::vector<const AssetBrowserEntry*> allEntries;
  831. if (!AzToolsFramework::AssetBrowser::Utils::FromMimeData(mimeData, allEntries))
  832. {
  833. return false;
  834. }
  835. // all entries now contains the actual list of actually selected entries (not their children)
  836. for (const AssetBrowserEntry* entry : allEntries)
  837. {
  838. if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
  839. {
  840. // This default fallback handler doesn't care about source files.
  841. // If you want to support source file drag operations, implement a listener with a higher priority
  842. // in your gem, and connect to the drag and drop busses. You can then intercept it and use code similar
  843. // to the above to decode the mime data and do whatever you need to do, consuming the event so that
  844. // we don't reach this point.
  845. // Because of that, this handler only cares about products, so we convert any source entries to the most
  846. // reasonable target product candidate.
  847. AZStd::vector<const ProductAssetBrowserEntry*> candidateProducts;
  848. entry->GetChildrenRecursively<ProductAssetBrowserEntry>(candidateProducts);
  849. const ProductAssetBrowserEntry* mostReasonable = GetPrimaryProduct(candidateProducts);
  850. if (mostReasonable)
  851. {
  852. canAcceptEvent = true;
  853. if (outProducts)
  854. {
  855. if (AZStd::ranges::find(*outProducts, mostReasonable) == outProducts->end())
  856. {
  857. outProducts->push_back(mostReasonable);
  858. }
  859. }
  860. }
  861. }
  862. else if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product)
  863. {
  864. const ProductAssetBrowserEntry* product = static_cast<const ProductAssetBrowserEntry*>(entry);
  865. // a product is directly selected - this overrides any rules we have about heuristics
  866. // since its an explicit user action
  867. if (ProductHasAssociatedComponent(product))
  868. {
  869. // its a creatable one.
  870. canAcceptEvent = true;
  871. if (outProducts)
  872. {
  873. if (AZStd::ranges::find(*outProducts, product) == outProducts->end())
  874. {
  875. outProducts->push_back(product);
  876. }
  877. }
  878. }
  879. }
  880. }
  881. return canAcceptEvent;
  882. }
  883. void AzAssetBrowserRequestHandler::DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context)
  884. {
  885. if (CanAcceptDragAndDropEvent(event, context))
  886. {
  887. event->setDropAction(Qt::CopyAction);
  888. event->setAccepted(true);
  889. }
  890. }
  891. void AzAssetBrowserRequestHandler::DragMove(QDragMoveEvent* event, AzQtComponents::DragAndDropContextBase& context)
  892. {
  893. if (CanAcceptDragAndDropEvent(event, context))
  894. {
  895. event->setDropAction(Qt::CopyAction);
  896. event->setAccepted(true);
  897. }
  898. }
  899. void AzAssetBrowserRequestHandler::DragLeave(QDragLeaveEvent* /*event*/)
  900. {
  901. // opportunities to clean up any preview objects here.
  902. }
  903. // listview/outliner dragging:
  904. void AzAssetBrowserRequestHandler::CanDropItemView(bool& accepted, AzQtComponents::DragAndDropContextBase& context)
  905. {
  906. using namespace AzToolsFramework;
  907. if (accepted)
  908. {
  909. return; // someone else already took this!
  910. }
  911. if (EntityOutlinerDragAndDropContext* outlinerContext = azrtti_cast<EntityOutlinerDragAndDropContext*>(&context))
  912. {
  913. if (DecodeDragMimeData(outlinerContext->m_dataBeingDropped))
  914. {
  915. accepted = true;
  916. }
  917. }
  918. }
  919. void AzAssetBrowserRequestHandler::DoDropItemView(bool& accepted, AzQtComponents::DragAndDropContextBase& context)
  920. {
  921. using namespace AzToolsFramework;
  922. using namespace AzToolsFramework::AssetBrowser;
  923. using namespace AzAssetBrowserRequestHandlerPrivate;
  924. if (accepted)
  925. {
  926. return; // someone else already took this!
  927. }
  928. if (EntityOutlinerDragAndDropContext* outlinerContext = azrtti_cast<EntityOutlinerDragAndDropContext*>(&context))
  929. {
  930. // drop the item(s)
  931. AZStd::vector<const ProductAssetBrowserEntry*> products;
  932. if (DecodeDragMimeData(outlinerContext->m_dataBeingDropped, &products))
  933. {
  934. accepted = true;
  935. // in this case, it should behave just like dropping the entity into the world at world origin.
  936. // Make a scoped undo that covers the ENTIRE operation.
  937. AZ::Vector3 createLocation = AZ::Vector3::CreateZero();
  938. if (!outlinerContext->m_parentEntity.IsValid())
  939. {
  940. EditorRequestBus::BroadcastResult(createLocation, &EditorRequestBus::Events::GetWorldPositionAtViewportCenter);
  941. }
  942. EntityIdList createdEntities;
  943. AzFramework::SliceInstantiationTicket sliceTicket;
  944. CreateEntitiesAtPoint(products, createLocation, outlinerContext->m_parentEntity, createdEntities, sliceTicket);
  945. }
  946. }
  947. }
  948. // There are two paths for generating entities by dragging and dropping from the asset browser.
  949. // This logic handles dropping them into the viewport. Dropping them in the outliner is handled by OutlinerListModel::DropMimeDataAssets.
  950. void AzAssetBrowserRequestHandler::Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context)
  951. {
  952. using namespace AzToolsFramework;
  953. using namespace AzToolsFramework::AssetBrowser;
  954. using namespace AzQtComponents;
  955. using namespace AzAssetBrowserRequestHandlerPrivate;
  956. if (event->isAccepted())
  957. {
  958. return; // don't double handle drop events.
  959. }
  960. AZStd::vector<const ProductAssetBrowserEntry*> products;
  961. ViewportDragContext* viewportDragContext = azrtti_cast<ViewportDragContext*>(&context);
  962. if ((!viewportDragContext)||(!DecodeDragMimeData(event->mimeData(), &products)))
  963. {
  964. return;
  965. }
  966. event->setDropAction(Qt::CopyAction);
  967. event->setAccepted(true);
  968. EntityIdList createdEntities;
  969. AzFramework::SliceInstantiationTicket sliceTicket;
  970. CreateEntitiesAtPoint(products, viewportDragContext->m_hitLocation, AZ::EntityId(), createdEntities, sliceTicket);
  971. }
  972. void AzAssetBrowserRequestHandler::AddSourceFileOpeners(
  973. [[maybe_unused]] const char* fullSourceFileName,
  974. const AZ::Uuid& sourceUUID,
  975. AzToolsFramework::AssetBrowser::SourceFileOpenerList& openers)
  976. {
  977. using namespace AzToolsFramework;
  978. //Get asset group to support a variety of file extensions
  979. const AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry* fullDetails =
  980. AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::GetSourceByUuid(sourceUUID);
  981. if (!fullDetails)
  982. {
  983. return;
  984. }
  985. // check to see if it is a default "generic" serializable asset
  986. // and open the asset editor if so. Check whether the Generic Asset handler handles this kind of asset.
  987. // to do so we need the actual type of that asset, which requires an asset type, not a source type.
  988. AZ::Data::AssetManager& manager = AZ::Data::AssetManager::Instance();
  989. // find a product type to query against.
  990. AZStd::vector<const AssetBrowser::ProductAssetBrowserEntry*> candidates;
  991. fullDetails->GetChildrenRecursively<AssetBrowser::ProductAssetBrowserEntry>(candidates);
  992. // find the first one that is handled by something:
  993. for (const AssetBrowser::ProductAssetBrowserEntry* productEntry : candidates)
  994. {
  995. // is there a Generic Asset Handler for it?
  996. AZ::Data::AssetType productAssetType = productEntry->GetAssetType();
  997. if ((productAssetType == AZ::Data::s_invalidAssetType) || (!productEntry->GetAssetId().IsValid()))
  998. {
  999. continue;
  1000. }
  1001. if (const AZ::Data::AssetHandler* assetHandler = manager.GetHandler(productAssetType))
  1002. {
  1003. if (!azrtti_istypeof<AzFramework::GenericAssetHandlerBase*>(assetHandler))
  1004. {
  1005. // it is not the generic asset handler.
  1006. continue;
  1007. }
  1008. // yes, it is the generic asset handler, so install an opener that sends it to the Asset Editor.
  1009. AZ::Data::AssetId assetId = productEntry->GetAssetId();
  1010. AZ::Data::AssetType assetType = productEntry->GetAssetType();
  1011. openers.push_back(
  1012. {
  1013. "Open_In_Asset_Editor",
  1014. "Open in Asset Editor...",
  1015. QIcon(),
  1016. [assetId, assetType](const char* /*fullSourceFileNameInCallback*/, const AZ::Uuid& /*sourceUUID*/)
  1017. {
  1018. AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
  1019. AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Broadcast(&AzToolsFramework::AssetEditor::AssetEditorRequests::OpenAssetEditor, asset);
  1020. }
  1021. });
  1022. break; // no need to proceed further
  1023. }
  1024. }
  1025. }
  1026. void AzAssetBrowserRequestHandler::AddSourceFileCreators([[maybe_unused]] const char* fullSourceFileName, [[maybe_unused]] const AZ::Uuid& sourceUUID, [[maybe_unused]] AzToolsFramework::AssetBrowser::SourceFileCreatorList& creators)
  1027. {
  1028. }
  1029. void AzAssetBrowserRequestHandler::OpenAssetInAssociatedEditor(const AZ::Data::AssetId& assetId, bool& alreadyHandled)
  1030. {
  1031. using namespace AzToolsFramework::AssetBrowser;
  1032. if (alreadyHandled)
  1033. {
  1034. // a higher priority listener has already taken this request.
  1035. return;
  1036. }
  1037. const SourceAssetBrowserEntry* source = SourceAssetBrowserEntry::GetSourceByUuid(assetId.m_guid);
  1038. if (!source)
  1039. {
  1040. return;
  1041. }
  1042. AZStd::string fullEntryPath = source->GetFullPath();
  1043. AZ::Uuid sourceID = source->GetSourceUuid();
  1044. if (fullEntryPath.empty())
  1045. {
  1046. return;
  1047. }
  1048. QWidget* mainWindow = nullptr;
  1049. AzToolsFramework::EditorRequestBus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::GetMainWindow);
  1050. SourceFileOpenerList openers;
  1051. AssetBrowserInteractionNotificationBus::Broadcast(&AssetBrowserInteractionNotificationBus::Events::AddSourceFileOpeners, fullEntryPath.c_str(), sourceID, openers);
  1052. // did anyone actually accept it?
  1053. if (!openers.empty())
  1054. {
  1055. // yes, call the opener and return.
  1056. // are there more than one opener(s)?
  1057. const SourceFileOpenerDetails* openerToUse = nullptr;
  1058. // a function which reassigns openerToUse to be the selected one.
  1059. AZStd::function<void(const SourceFileOpenerDetails*)> switchToOpener = [&openerToUse](const SourceFileOpenerDetails* switchTo)
  1060. {
  1061. openerToUse = switchTo;
  1062. };
  1063. // callers are allowed to add nullptr to openers. So we only evaluate the valid ones.
  1064. // and if there is only one valid one, we use that one.
  1065. const SourceFileOpenerDetails* firstValidOpener = nullptr;
  1066. int numValidOpeners = 0;
  1067. QMenu menu(mainWindow);
  1068. for (const SourceFileOpenerDetails& openerDetails : openers)
  1069. {
  1070. // bind that function to the current loop element.
  1071. if (openerDetails.m_opener) // only VALID openers with an actual callback.
  1072. {
  1073. ++numValidOpeners;
  1074. if (!firstValidOpener)
  1075. {
  1076. firstValidOpener = &openerDetails;
  1077. }
  1078. // bind a callback such that when the menu item is clicked, it sets that as the opener to use.
  1079. menu.addAction(openerDetails.m_iconToUse, QObject::tr(openerDetails.m_displayText.c_str()), mainWindow, [switchToOpener, details = &openerDetails] { return switchToOpener(details); });
  1080. }
  1081. }
  1082. if (numValidOpeners > 1) // more than one option was added
  1083. {
  1084. menu.addSeparator();
  1085. menu.addAction(QObject::tr("Cancel"), [switchToOpener] { return switchToOpener(nullptr); }); // just something to click on to avoid doing anything.
  1086. menu.exec(QCursor::pos());
  1087. }
  1088. else if (numValidOpeners == 1)
  1089. {
  1090. openerToUse = firstValidOpener;
  1091. }
  1092. // did we select one and did it have a function to call?
  1093. if ((openerToUse) && (openerToUse->m_opener))
  1094. {
  1095. openerToUse->m_opener(fullEntryPath.c_str(), sourceID);
  1096. }
  1097. alreadyHandled = true;
  1098. return; // an opener handled this, no need to proceed further.
  1099. }
  1100. // if we get here, nothing handled it, so try the operating system.
  1101. alreadyHandled = OpenWithOS(fullEntryPath);
  1102. }
  1103. bool AzAssetBrowserRequestHandler::OpenWithOS(const AZStd::string& fullEntryPath)
  1104. {
  1105. bool openedSuccessfully = QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromUtf8(fullEntryPath.c_str())));
  1106. if (!openedSuccessfully)
  1107. {
  1108. AZ_Printf("Asset Browser", "Unable to open '%s' using the operating system. There might be no editor associated with this kind of file.\n", fullEntryPath.c_str());
  1109. }
  1110. return openedSuccessfully;
  1111. }