AzAssetBrowserRequestHandler.cpp 49 KB

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