// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "ClassViewerPrivatePCH.h" #include "EditorWidgets.h" #include "Editor/ClassViewer/Private/SClassViewer.h" #include "Editor/UnrealEd/Public/DragAndDrop/ClassDragDropOp.h" #include "Editor/UnrealEd/Public/DragAndDrop/AssetDragDropOp.h" #include "Editor/UnrealEd/Public/ClassIconFinder.h" #include "Editor/ContentBrowser/Public/ContentBrowserModule.h" #include "Kismet2/KismetEditorUtilities.h" #include "Toolkits/AssetEditorManager.h" #include "PackageTools.h" #include "MessageLog.h" #include "AssetRegistryModule.h" #include "AssetSelection.h" #include "AssetToolsModule.h" #include "ClassViewerNode.h" #include "UnloadedBlueprintData.h" #include "EditorClassUtils.h" #include "IDocumentation.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "GameProjectGenerationModule.h" #include "SourceCodeNavigation.h" #include "HotReloadInterface.h" #include "SSearchBox.h" #include "SListViewSelectorDropdownMenu.h" #include "Engine/BlueprintGeneratedClass.h" #define LOCTEXT_NAMESPACE "SClassViewer" DEFINE_LOG_CATEGORY_STATIC(LogEditorClassViewer, Log, All); ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(InClass->IsChildOf(*CurClassIt)) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(InClass->IsChildOf(*CurClassIt)) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(!InClass->IsChildOf(*CurClassIt)) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(!InClass->IsChildOf(*CurClassIt)) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(!(*CurClassIt)->IsA(InClass)) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(!(*CurClassIt)->IsA(UBlueprintGeneratedClass::StaticClass())) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { const UObject* Object = *CurClassIt; if(!Object->IsA(InClass)) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { const UObject* Object = *CurClassIt; if(!Object->IsA(UBlueprintGeneratedClass::StaticClass())) { // Since it doesn't match one, it fails. return EFilterReturn::Failed; } } // It matches all of them, so it passes. return EFilterReturn::Passed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { const UObject* Object = *CurClassIt; if(Object->IsA(InClass)) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { const UObject* Object = *CurClassIt; if(Object->IsA(UBlueprintGeneratedClass::StaticClass())) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) { check(InClass); if(InSet.Num()) { for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { if(InClass == *CurClassIt) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) { check(InClass.IsValid()); if(InSet.Num()) { for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) { const TSharedPtr UnloadedBlueprintData = StaticCastSharedPtr(InClass); if(*UnloadedBlueprintData->GetClassViewerNode().Pin()->GetClassName() == (*CurClassIt)->GetName()) { return EFilterReturn::Passed; } } return EFilterReturn::Failed; } // Since there are none on this list, return that there is no items. return EFilterReturn::NoItems; } ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// // A hack to make IMPLEMENT_COMPARE_CONSTREF with a templated type typedef TSharedPtr FClassViewerNodeSharedPtr; FORCEINLINE bool CompareFClassViewerNodes( const FClassViewerNodeSharedPtr& A, const FClassViewerNodeSharedPtr& B ) { check(A.IsValid()); check(B.IsValid()); // Pull out the FString, for ease of reading. FString AString = *A->GetClassName().Get(); FString BString = *B->GetClassName().Get(); return *A->GetClassName().Get() < *B->GetClassName().Get(); } class FClassHierarchy { public: FClassHierarchy(); ~FClassHierarchy(); /** Populates the class hierarchy tree, pulling all the loaded and unloaded classes into a master tree. */ void PopulateClassHierarchy(); void PopulateClassHierarchy(const FAssetData& InAssetData) { PopulateClassHierarchy(); } /** Recursive function to sort a tree. * @param InOutRootNode The current node to sort. */ void SortChildren( TSharedPtr< FClassViewerNode >& InRootNode ); /** Checks if a particular class is placeable. * @return The ObjectClassRoot for building a duplicate tree using. */ const TSharedPtr< FClassViewerNode > GetObjectRootNode() const { // This node should always be valid. check(ObjectClassRoot.IsValid()) return ObjectClassRoot; } /** Finds the parent of a node, recursively going deeper into the hierarchy. * @param InRootNode The current class node to examine. * @param InParentClassName The classname to look for. * @param InParentClass The parent class to look for. * * @return The parent node. */ TSharedPtr< FClassViewerNode > FindParent(const TSharedPtr< FClassViewerNode >& InRootNode, FName InParentClassname, const UClass* InParentClass); /** Updates the Class of a node. Uses the generated class package name to find the node. * @param InGeneratedClassPackageName The name of the generated class package to find the node for. * @param InNewClass The class to update the node with. */ void UpdateClassInNode(const FString& InGeneratedClassPackageName, UClass* InNewClass, UBlueprint* InNewBluePrint ); private: /** Recursive function to build a tree, will not filter. * @param InOutRootNode The node that this function will add the children of to the tree. * @param PackageNameToAssetDataMap The asset registry map of blueprint package names to blueprint data */ void AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ); /** Called when hot reload has finished */ void OnHotReload( bool bWasTriggeredAutomatically ); /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing generated class package names. * @param InGeneratedClassPackageName The name of the generated class package to find the node for. * * @return The node. */ TSharedPtr< FClassViewerNode > FindNodeByGeneratedClassPackageName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InGeneratedClassPackageName); /** * Loads the tag data for an unloaded blueprint asset. * * @param InOutClassViewerNode The node to save all the data into. * @param InAssetData The asset data to pull the tags from. */ void LoadUnloadedTagData(TSharedPtr& InOutClassViewerNode, const FAssetData& InAssetData); /** * Finds the UClass and UBlueprint for the passed in node, utilizing unloaded data to find it. * * @param InOutClassNode The node to find the class and fill out. */ void FindClass(TSharedPtr< FClassViewerNode > InOutClassNode); /** * Recursively searches through the hierarchy to find and remove the asset. Used when deleting assets. * * @param InRootNode The node to start the search with. * @param InAssetPackage The package name of the asset to delete. * * @return Returns true if the asset was found and deleted successfully. */ bool FindAndRemoveNodeByPackageName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InAssetPackage); /** Callback registered to the Asset Registry to be notified when an asset is added. */ void AddAsset(const FAssetData& InAddedAssetData); /** Callback registered to the Asset Registry to be notified when an asset is removed. */ void RemoveAsset(const FAssetData& InRemovedAssetData); private: /** The "Object" class node that is used as a rooting point for the Class Viewer. */ TSharedPtr< FClassViewerNode > ObjectClassRoot; /** Handles to various registered RequestPopulateClassHierarchy delegates */ FDelegateHandle OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle; FDelegateHandle OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle; FDelegateHandle OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle; }; namespace ClassViewer { namespace Helpers { DECLARE_MULTICAST_DELEGATE( FPopulateClassViewer ); /** The class hierarchy that manages the unfiltered class tree for the Class Viewer. */ static TSharedPtr< FClassHierarchy > ClassHierarchy; /** Used to inform any registered Class Viewers to refresh. */ static FPopulateClassViewer PopulateClassviewerDelegate; /** true if the Class Hierarchy should be populated. */ static bool bPopulateClassHierarchy; // Pre-declare these functions. static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ); static UBlueprint* GetBlueprint( UClass* InClass ); static void UpdateClassInNode(const FString& InGeneratedClassPackageName, UClass* InNewClass, UBlueprint* InNewBluePrint ); /** Util class to checks if a particular class can be made into a Blueprint, ignores deprecation * * @param InClass The class to verify can be made into a Blueprint * @return TRUE if the class can be made into a Blueprint */ bool CanCreateBlueprintOfClass_IgnoreDeprecation(UClass* InClass) { // Temporarily remove the deprecated flag so we can check if it is valid for bool bIsClassDeprecated = InClass->HasAnyClassFlags(CLASS_Deprecated); InClass->ClassFlags &= ~CLASS_Deprecated; bool bCanCreateBlueprintOfClass = FKismetEditorUtilities::CanCreateBlueprintOfClass( InClass ); // Reassign the deprecated flag if it was previously assigned if(bIsClassDeprecated) { InClass->ClassFlags |= CLASS_Deprecated; } return bCanCreateBlueprintOfClass; } /** Checks if a particular class is a brush. * @param InClass The Class to check. * @return Returns true if the class is a brush. */ static bool IsBrush(const UClass* InClass) { return InClass->IsChildOf( ABrush::StaticClass() ); } /** Checks if a particular class is placeable. * @param InClass The Class to check. * @return Returns true if the class is placeable. */ static bool IsPlaceable(const UClass* InClass) { return !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) && InClass->IsChildOf(); } /** Checks if a particular class is abstract. * @param InClass The Class to check. * @return Returns true if the class is abstract. */ static bool IsAbstract(const UClass* InClass) { return InClass->HasAnyClassFlags(CLASS_Abstract); } /** Checks if the class is allowed under the init options of the class viewer currently building it's tree/list. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InClass The class to test against. */ static bool IsClassAllowed( const FClassViewerInitializationOptions& InInitOptions, const TWeakObjectPtr InClass ) { if(InInitOptions.ClassFilter.IsValid()) { return InInitOptions.ClassFilter->IsClassAllowed(InInitOptions, InClass.Get(), MakeShareable(new FClassViewerFilterFuncs)); } return true; } /** Checks if the unloaded class is allowed under the init options of the class viewer currently building it's tree/list. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InNode The node to pull the unloaded class from. */ bool IsClassAllowed_UnloadedBlueprint(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode > InNode ) { if(InInitOptions.ClassFilter.IsValid() && InNode->UnloadedBlueprintData.IsValid()) { return InInitOptions.ClassFilter->IsUnloadedClassAllowed(InInitOptions, InNode->UnloadedBlueprintData.ToSharedRef(), MakeShareable(new FClassViewerFilterFuncs)); } return true; } /** Checks if the TestString passes the filter. * @param InTestString The string to test against the filter. * @param InFilterSearchTerms A list of terms to filter with. * * @return true if it passes the filter. */ static bool PassesFilter( const FString& InTestString, const TArray& InFilterSearchTerms ) { bool bPassesFilter = false; bPassesFilter = !InFilterSearchTerms.Num(); for( auto CurSearchTermIndex = 0; !bPassesFilter && CurSearchTermIndex < InFilterSearchTerms.Num(); ++CurSearchTermIndex ) { if(InTestString.Contains(InFilterSearchTerms[CurSearchTermIndex])) { bPassesFilter = true; break; } } return bPassesFilter; } /** Will create the instance of FClassHierarchy and populate the class hierarchy tree. */ static void ConstructClassHierarchy() { if(!ClassHierarchy.IsValid()) { ClassHierarchy = MakeShareable(new FClassHierarchy); // When created, populate the hierarchy. GWarn->BeginSlowTask( LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true ); ClassHierarchy->PopulateClassHierarchy(); GWarn->EndSlowTask(); } } /** Cleans up the Class Hierarchy */ static void DestroyClassHierachy() { ClassHierarchy.Reset(); } /** Will populate the class hierarchy tree if previously requested. */ static void PopulateClassHierarchy() { if(bPopulateClassHierarchy) { bPopulateClassHierarchy = false; GWarn->BeginSlowTask( LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true ); ClassHierarchy->PopulateClassHierarchy(); GWarn->EndSlowTask(); } } /** Will enable the Class Hierarchy to be populated next Tick. */ static void RequestPopulateClassHierarchy() { bPopulateClassHierarchy = true; } /** Refreshes all registered instances of Class Viewer/Pickers. */ static void RefreshAll() { ClassViewer::Helpers::PopulateClassviewerDelegate.Broadcast(); } /** Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search terms. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. * @param InFilterSearchTerms A list of terms to filter with. * @param bInOnlyActors Filter option to remove non-actor classes. * @param bInOnlyPlaceables Filter option to remove non-placeable classes. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. * * @return Returns true if the child passed the filter. */ static bool AddChildren_Tree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const TArray& InFilterSearchTerms, bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints ) { if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) { InOutRootNode->bPassesFilter = false; return false; } bool bChildrenPassesFilter(false); bool bReturnPassesFilter(false); bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); bool bPassesPlaceableFilter = false; // When in picker mode, brushes are valid "placeable" actors if (bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) { bPassesPlaceableFilter = true; } else { bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); } // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. if(bIsUnloadedBlueprint) { if(bInShowUnloadedBlueprints) { bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InFilterSearchTerms); } } else { bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InFilterSearchTerms); } TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) { TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ChildList[ChildIdx].Get() ) ); bReturnPassesFilter |= bChildrenPassesFilter = AddChildren_Tree(InInitOptions, NewNode, ChildList[ChildIdx], InFilterSearchTerms, false, /* bInOnlyActors - false so that anything below Actor is added */ bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints); if(bChildrenPassesFilter) { InOutRootNode->AddChild(NewNode); } } return bReturnPassesFilter; } /** Builds the class tree. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node to root the tree to. * @param InFilterSearchTerms A list of terms to filter with. * @param bInOnlyPlaceables Filter option to remove non-placeable classes. * @param bInOnlyActors Filter option to root the tree in the "Actor" class. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. * * @return A fully built tree. */ static void GetClassTree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, const TArray& InFilterSearchTerms, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints ) { const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // Duplicate the node, it will have no children. InOutRootNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot)); if(bInOnlyActors) { for(int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++) { TSharedPtr ChildNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot->GetChildrenList()[ClassIdx].Get())); if (AddChildren_Tree(InInitOptions, ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InFilterSearchTerms, true, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints)) InOutRootNode->AddChild(ChildNode); } } else { AddChildren_Tree(InInitOptions, InOutRootNode, ObjectClassRoot, InFilterSearchTerms, false, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints); } } /** Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. * @param InFilterSearchTerms A list of terms to filter with. * @param bInOnlyActors Filter option to remove non-actor classes. * @param bInOnlyPlaceables Filter option to remove non-placeable classes. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. * * @return Returns true if the child passed the filter. */ static void AddChildren_List( const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const TArray& InFilterSearchTerms, bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints ) { if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) { return; } bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); bool bPassesPlaceableFilter = false; // When in picker mode, brushes are valid "placeable" actors if( bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) { bPassesPlaceableFilter = true; } else { bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); } TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *InOriginalRootNode.Get() ) ); // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. if(bIsUnloadedBlueprint) { if(bInShowUnloadedBlueprints) { NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InFilterSearchTerms); } } else { NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InFilterSearchTerms); } if(NewNode->bPassesFilter) { InOutNodeList.Add(NewNode); } NewNode->PropertyHandle = InInitOptions.PropertyHandle; TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) { AddChildren_List(InInitOptions, InOutNodeList, ChildList[ChildIdx], InFilterSearchTerms, false, /* bInOnlyActors - false so that anything below Actor is added */ bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints); } } /** Builds the class list. * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutNodeList The list to add all the nodes to. * @param InFilterSearchTerms A list of terms to filter with. * @param bInOnlyPlaceables Filter option to remove non-placeable classes. * @param bInOnlyActors Filter option to root the tree in the "Actor" class. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. * * @return A fully built list. */ static void GetClassList(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TArray& InFilterSearchTerms, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints) { const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // If the option to see the object root class is set, add it to the list, proceed normally from there so the actor's only filter continues to work. if(InInitOptions.bShowObjectRootClass) { TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ObjectClassRoot.Get() ) ); NewNode->bPassesFilter = IsClassAllowed(InInitOptions, ObjectClassRoot->Class) && PassesFilter(*ObjectClassRoot->GetClassName().Get(), InFilterSearchTerms); if(NewNode->bPassesFilter) { InOutNodeList.Add(NewNode); } NewNode->PropertyHandle = InInitOptions.PropertyHandle; } TArray< TSharedPtr< FClassViewerNode > >& ChildList = ObjectClassRoot->GetChildrenList(); for(int32 ObjectChildIndex = 0; ObjectChildIndex < ChildList.Num(); ObjectChildIndex++) { AddChildren_List(InInitOptions, InOutNodeList, ChildList[ObjectChildIndex], InFilterSearchTerms, bInOnlyActors, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints); } } /** Retrieves the blueprint for a class index. * @param InClass The class whose blueprint is desired. * * @return The blueprint associated with the class index. */ static UBlueprint* GetBlueprint( UClass* InClass ) { if( InClass->ClassGeneratedBy && InClass->ClassGeneratedBy->IsA(UBlueprint::StaticClass()) ) { return Cast(InClass->ClassGeneratedBy); } return NULL; } /** Retrieves a few items of information on the given UClass (retrieved via the InClassIndex). * @param InClass The class to gather info of. * @param bInOutIsBlueprintBase true if the class is a blueprint. * @param bInOutHasBlueprint true if the class has a blueprint. * * @return The blueprint associated with the class index. */ static void GetClassInfo( TWeakObjectPtr InClass, bool& bInOutIsBlueprintBase, bool& bInOutHasBlueprint ) { if (UClass* Class = InClass.Get()) { bInOutIsBlueprintBase = CanCreateBlueprintOfClass_IgnoreDeprecation( Class ); bInOutHasBlueprint = Class->ClassGeneratedBy != NULL; } else { bInOutIsBlueprintBase = false; bInOutHasBlueprint = false; } } /** Checks if a node is a blueprint base or not. * @param InNode The node to check if it is a blueprint base. * * @return true if the class is a blueprint. */ static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ) { // If there is no class, it may be an unloaded blueprint. if(UClass* Class = InNode->Class.Get()) { return CanCreateBlueprintOfClass_IgnoreDeprecation(Class); } else if(InNode->bIsBPNormalType) { bool bAllowDerivedBlueprints = false; GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); return bAllowDerivedBlueprints; } return false; } /** Recursively loads the entire chain of blueprints because children need their parents to be loaded. * @param InRootNode The current node being examined. * @param InBlueprint The blueprint to add. */ static void AddBlueprintChainToHierarchy(TSharedPtr< FClassViewerNode > InRootNode, UObject* InBlueprint) { TSharedPtr< FClassViewerNode > ParentNode = ClassHierarchy->FindParent(ClassHierarchy->GetObjectRootNode(), InRootNode->ParentClassname, NULL); if( ParentNode.IsValid() && !ParentNode->GeneratedClassPackage.IsEmpty() ) { UPackage* Package = LoadPackage(NULL, *ParentNode->GeneratedClassPackage, LOAD_NoRedirects ); Package->FullyLoad(); UObject* ParentObject = FindObject(Package, *ParentNode->AssetName); if(ParentObject->IsA(UBlueprint::StaticClass())) { AddBlueprintChainToHierarchy(ParentNode, ParentObject); } } } /** * Creates a blueprint from a class. * * @param InCreationClass The class to create the blueprint from. * @param InParentContent The content to parent the STextEntryPopup to. */ static void CreateBlueprint(const FString& InBlueprintName, UClass* InCreationClass) { if(InCreationClass == NULL || !FKismetEditorUtilities::CanCreateBlueprintOfClass(InCreationClass)) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "InvalidClassToMakeBlueprintFrom", "Invalid class to make a Blueprint of.")); return; } // Get the full name of where we want to create the physics asset. FString PackageName = InBlueprintName; // Then find/create it. UPackage* Package = CreatePackage(NULL, *PackageName); check(Package); // Handle fully loading packages before creating new objects. TArray TopLevelPackages; TopLevelPackages.Add( Package->GetOutermost() ); if( !PackageTools::HandleFullyLoadingPackages( TopLevelPackages, NSLOCTEXT("UnrealEd", "CreateANewObject", "Create a new object") ) ) { // Can't load package return; } FName BPName(*FPackageName::GetLongPackageAssetName(PackageName)); if(PromptUserIfExistingObject(BPName.ToString(), PackageName,FString(), Package)) { // Create and init a new Blueprint UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(InCreationClass, Package, BPName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("ClassViewer")); if(NewBP) { FAssetEditorManager::Get().OpenEditorForAsset(NewBP); // Notify the asset registry FAssetRegistryModule::AssetCreated(NewBP); // Mark the package dirty... Package->MarkPackageDirty(); } } // All viewers must refresh. RefreshAll(); } /** * Creates a SaveAssetDialog for specifying the path for the new blueprint */ static void OpenCreateBlueprintDialog(UClass* InCreationClass) { // Determine default path for the Save Asset dialog FString DefaultPath; const FString DefaultDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::NEW_ASSET); FPackageName::TryConvertFilenameToLongPackageName(DefaultDirectory, DefaultPath); if (DefaultPath.IsEmpty()) { DefaultPath = TEXT("/Game/Blueprints"); } // Determine default filename for the Save Asset dialog check(InCreationClass != nullptr); const FString ClassName = InCreationClass->ClassGeneratedBy ? InCreationClass->ClassGeneratedBy->GetName() : InCreationClass->GetName(); FString DefaultName = LOCTEXT("PrefixNew", "New").ToString() + ClassName; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); FString UniquePackageName; FString UniqueAssetName; AssetToolsModule.Get().CreateUniqueAssetName(DefaultPath / DefaultName, TEXT(""), UniquePackageName, UniqueAssetName); DefaultName = FPaths::GetCleanFilename(UniqueAssetName); // Initialize SaveAssetDialog config FSaveAssetDialogConfig SaveAssetDialogConfig; SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("CreateBlueprintDialogTitle", "Create Blueprint Class"); SaveAssetDialogConfig.DefaultPath = DefaultPath; SaveAssetDialogConfig.DefaultAssetName = DefaultName; SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn; FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig); if (!SaveObjectPath.IsEmpty()) { const FString PackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath); const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackageName); const FString PackagePath = FPaths::GetPath(PackageFilename); CreateBlueprint(PackageName, InCreationClass); FEditorDirectories::Get().SetLastDirectory(ELastDirectory::NEW_ASSET, PackagePath); } } /** Returns the tooltip to display when attempting to derive a Blueprint */ FText GetCreateBlueprintTooltip(UClass* InCreationClass) { if(InCreationClass->HasAnyClassFlags(CLASS_Deprecated)) { return LOCTEXT("ClassViewerMenuCreateDeprecatedBlueprint_Tooltip", "Class is deprecated!"); } else { return LOCTEXT("ClassViewerMenuCreateBlueprint_Tooltip", "Creates a Blueprint Class using this class as a base."); } } /** Returns TRUE if you can derive a Blueprint */ bool CanOpenCreateBlueprintDialog(UClass* InCreationClass) { return !InCreationClass->HasAnyClassFlags(CLASS_Deprecated); } /** * Creates a class wizard for creating a new C++ class * * @param InParentContent The content to parent the STextEntryPopup to. */ static void OpenCreateCPlusPlusClassWizard(UClass* InCreationClass) { FGameProjectGenerationModule::Get().OpenAddCodeToProjectDialog( FAddToProjectConfig() .ParentClass(InCreationClass) .ParentWindow(FGlobalTabmanager::Get()->GetRootWindow()) ); } /** * Creates a blueprint from a class. * * @param InOutClassNode Class node to pull what class to load and to update information in. */ static void LoadClass(TSharedPtr< FClassViewerNode > InOutClassNode) { GWarn->BeginSlowTask(LOCTEXT("LoadPackage", "Loading Package..."), true); UPackage* Package = LoadPackage(NULL, *InOutClassNode->GeneratedClassPackage, LOAD_NoRedirects ); if(Package) { Package->FullyLoad(); UObject* Object = FindObject(Package, *InOutClassNode->AssetName); GWarn->EndSlowTask(); // Check if this item is a blueprint. if( Object->IsA(UBlueprint::StaticClass()) ) { InOutClassNode->Blueprint = Cast(Object); InOutClassNode->Class = Cast(InOutClassNode->Blueprint->GeneratedClass); // Tell the original node to update so when a refresh happens it will still know about the newly loaded class. ClassViewer::Helpers::UpdateClassInNode(InOutClassNode->GeneratedClassPackage, InOutClassNode->Class.Get(), InOutClassNode->Blueprint.Get() ); // Adds the entire hierarchy of Blueprints to the EditorClassHierarchy so they will continue to appear when a full rebuild of the tree happens. ClassViewer::Helpers::AddBlueprintChainToHierarchy(InOutClassNode, Object); } else if (UClass* Class = Cast(Object)) { InOutClassNode->Blueprint = Cast(Class->ClassGeneratedBy); InOutClassNode->Class = Class; // Tell the original node to update so when a refresh happens it will still know about the newly loaded class. ClassViewer::Helpers::UpdateClassInNode(InOutClassNode->GeneratedClassPackage, InOutClassNode->Class.Get(), InOutClassNode->Blueprint.Get() ); // Adds the entire hierarchy of Blueprints to the EditorClassHierarchy so they will continue to appear when a full rebuild of the tree happens. ClassViewer::Helpers::AddBlueprintChainToHierarchy(InOutClassNode, Object); } else { InOutClassNode->Class = Object->GetClass(); } } else { GWarn->EndSlowTask(); // Check to see if the class can be found, if it can't, notify that the package failed to load. //if(!FindClass(InOutClassNode)) { FMessageLog EditorErrors("EditorErrors"); EditorErrors.Error(LOCTEXT("PackageLoadFail", "Package Load Failed")); EditorErrors.Info(FText::FromString(InOutClassNode->GeneratedClassPackage)); EditorErrors.Notify(LOCTEXT("PackageLoadFail", "Package Load Failed")); } } } /** * Opens a blueprint. * * @param InBlueprint The blueprint to open. */ static void OpenBlueprintTool(UBlueprint* InBlueprint) { if( InBlueprint != NULL ) { FAssetEditorManager::Get().OpenEditorForAsset(InBlueprint); } } /** * Opens a class's source file. * * @param InClass The class to open source for. */ static void OpenClassHeaderFileInIDE(UClass* InClass) { if( InClass != NULL ) { FString ClassHeaderPath; if( FSourceCodeNavigation::FindClassHeaderPath( InClass, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE ) { FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath); FSourceCodeNavigation::OpenSourceFile( AbsoluteHeaderPath ); } } } /** * Finds the blueprint or class in the content browser. Blueprint prioritized because if there is a blueprint we want to find that. * * @param InBlueprint The blueprint to find. * @param InClass The class to find. */ static void FindInContentBrowser(UBlueprint* InBlueprint, UClass* InClass) { // If there is a blueprint, use the blueprint instead of the class. Otherwise it will not fully find the requested object. if(InBlueprint) { TArray Objects; Objects.Add(InBlueprint); GEditor->SyncBrowserToObjects(Objects); } else if (InClass) { TArray Objects; Objects.Add(InClass); GEditor->SyncBrowserToObjects(Objects); } } /** Updates the Class of a node. Uses the generated class package name to find the node. * @param InGeneratedClassPackageName The name of the generated class package to find the node for. * @param InNewClass The class to update the node with. */ static void UpdateClassInNode(const FString& InGeneratedClassPackageName, UClass* InNewClass, UBlueprint* InNewBluePrint ) { ClassHierarchy->UpdateClassInNode(InGeneratedClassPackageName, InNewClass, InNewBluePrint ); } static TSharedRef CreateMenu(UClass* Class, const bool bIsBlueprint, const bool bHasBlueprint) { // Empty list of commands. TSharedPtr< FUICommandList > Commands; const bool bShouldCloseWindowAfterMenuSelection = true; // Set the menu to automatically close when the user commits to a choice FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, Commands); { if (bIsBlueprint) { TAttribute::FGetter DynamicTooltipGetter; DynamicTooltipGetter.BindStatic(&ClassViewer::Helpers::GetCreateBlueprintTooltip, Class); TAttribute DynamicTooltipAttribute = TAttribute::Create(DynamicTooltipGetter); MenuBuilder.AddMenuEntry( LOCTEXT("ClassViewerMenuCreateBlueprint", "Create Blueprint Class..."), DynamicTooltipAttribute, FSlateIcon(), FUIAction( FExecuteAction::CreateStatic(&ClassViewer::Helpers::OpenCreateBlueprintDialog, Class), FCanExecuteAction::CreateStatic(&ClassViewer::Helpers::CanOpenCreateBlueprintDialog, Class) ) ); } if (bHasBlueprint) { MenuBuilder.BeginSection("ClassViewerDropDownHasBlueprint"); { FUIAction Action(FExecuteAction::CreateStatic(&ClassViewer::Helpers::OpenBlueprintTool, ClassViewer::Helpers::GetBlueprint(Class))); MenuBuilder.AddMenuEntry(LOCTEXT("ClassViewerMenuEditBlueprint", "Edit Blueprint Class..."), LOCTEXT("ClassViewerMenuEditBlueprint_Tooltip", "Open the Blueprint Class in the editor."), FSlateIcon(), Action); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("ClassViewerDropDownHasBlueprint2"); { FUIAction Action(FExecuteAction::CreateStatic(&ClassViewer::Helpers::FindInContentBrowser, ClassViewer::Helpers::GetBlueprint(Class), Class)); MenuBuilder.AddMenuEntry(LOCTEXT("ClassViewerMenuFindContent", "Find in Content Browser..."), LOCTEXT("ClassViewerMenuFindContent_Tooltip", "Find in Content Browser"), FSlateIcon(), Action); } MenuBuilder.EndSection(); } else { MenuBuilder.BeginSection("ClassViewerIsCode"); { FUIAction Action(FExecuteAction::CreateStatic(&ClassViewer::Helpers::OpenClassHeaderFileInIDE, Class)); MenuBuilder.AddMenuEntry(LOCTEXT("ClassViewerMenuOpenCPlusPlusClass", "Open C++ Header..."), LOCTEXT("ClassViewerMenuOpenCPlusPlusClass_Tooltip", "Open the header file for this class in the IDE."), FSlateIcon(), Action); } { FUIAction Action(FExecuteAction::CreateStatic(&ClassViewer::Helpers::OpenCreateCPlusPlusClassWizard, Class)); MenuBuilder.AddMenuEntry(LOCTEXT("ClassViewerMenuCreateCPlusPlusClass", "Create New C++ Class..."), LOCTEXT("ClassViewerMenuCreateCPlusPlusClass_Tooltip", "Creates a new C++ class using this class as a base."), FSlateIcon(), Action); } MenuBuilder.EndSection(); } } return MenuBuilder.MakeWidget(); } } // namespace Helpers } // namespace ClassViewer /** Delegate used with the Class Viewer in 'class picking' mode. You'll bind a delegate when the class viewer widget is created, which will be fired off when the selected class is double clicked */ DECLARE_DELEGATE_OneParam( FOnClassItemDoubleClickDelegate, TSharedPtr ); /** The item used for visualizing the class in the tree. */ class SClassItem : public STableRow< TSharedPtr > { public: SLATE_BEGIN_ARGS( SClassItem ) : _ClassName() , _bIsPlaceable(false) , _bIsInClassViewer( true ) , _bDynamicClassLoading( true ) , _HighlightText(NULL) , _TextColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)) {} /** The classname this item contains. */ SLATE_ARGUMENT( TSharedPtr, ClassName ) /** true if this item is a placeable object. */ SLATE_ARGUMENT( bool, bIsPlaceable ) /** true if this item is in a Class Viewer (as opposed to a Class Picker) */ SLATE_ARGUMENT( bool, bIsInClassViewer ) /** true if this item should allow dynamic class loading */ SLATE_ARGUMENT( bool, bDynamicClassLoading ) /** The text this item should highlight, if any. */ SLATE_ARGUMENT( const FText*, HighlightText ) /** The color text this item will use. */ SLATE_ARGUMENT( FSlateColor, TextColor ) /** The node this item is associated with. */ SLATE_ARGUMENT( TSharedPtr, AssociatedNode) /** the delegate for handling double clicks outside of the SClassItem */ SLATE_ARGUMENT( FOnClassItemDoubleClickDelegate, OnClassItemDoubleClicked ) /** On Class Picked callback. */ SLATE_EVENT( FOnDragDetected, OnDragDetected ) SLATE_END_ARGS() /** * Construct the widget * * @param InArgs A declaration from which to construct the widget */ void Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView ) { ClassName = InArgs._ClassName; bIsClassPlaceable = InArgs._bIsPlaceable; bIsInClassViewer = InArgs._bIsInClassViewer; bDynamicClassLoading = InArgs._bDynamicClassLoading; AssociatedNode = InArgs._AssociatedNode; OnDoubleClicked = InArgs._OnClassItemDoubleClicked; bool bIsBlueprint(false); bool bHasBlueprint(false); //SetEnabled( AssociatedNode->Restrictions.Num() == 0 ); ClassViewer::Helpers::GetClassInfo(AssociatedNode->Class, bIsBlueprint, bHasBlueprint); struct Local { static TSharedPtr GetToolTip(TSharedPtr AssociatedNode) { TSharedPtr ToolTip; if( AssociatedNode->PropertyHandle.IsValid() && AssociatedNode->IsRestricted() ) { FText RestrictionToolTip; AssociatedNode->PropertyHandle->GenerateRestrictionToolTip(*AssociatedNode->GetClassName(),RestrictionToolTip); ToolTip = IDocumentation::Get()->CreateToolTip(RestrictionToolTip, nullptr, "", ""); } else if (UClass* Class = AssociatedNode->Class.Get()) { UPackage* Package = Class->GetOutermost(); UMetaData* MetaData = Package->GetMetaData(); ToolTip = FEditorClassUtils::GetTooltip(Class); } return ToolTip; } }; bool bIsRestricted = AssociatedNode->IsRestricted(); const FSlateBrush* ClassIcon = FClassIconFinder::FindIconForClass(AssociatedNode->Class.Get()); this->ChildSlot [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew( SExpanderArrow, SharedThis(this) ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( 0.0f, 2.0f, 6.0f, 2.0f ) [ SNew( SImage ) .Image( ClassIcon ) .Visibility( ClassIcon != FEditorStyle::GetDefaultBrush()? EVisibility::Visible : EVisibility::Collapsed ) ] +SHorizontalBox::Slot() .FillWidth(1.0f) .Padding( 0.0f, 3.0f, 6.0f, 3.0f ) .VAlign(VAlign_Center) [ SNew( STextBlock ) .Text( FText::FromString(*ClassName.Get()) ) .HighlightText(*InArgs._HighlightText) .ColorAndOpacity( this, &SClassItem::GetTextColor) .ToolTip(Local::GetToolTip(AssociatedNode)) .IsEnabled(!bIsRestricted) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding( 0.0f, 0.0f, 6.0f, 0.0f ) [ SNew( SComboButton ) .ContentPadding(FMargin(2.0f)) .Visibility(this, &SClassItem::ShowOptions) .OnGetMenuContent(this, &SClassItem::GenerateDropDown) ] ]; TextColor = InArgs._TextColor; UE_LOG(LogEditorClassViewer, VeryVerbose, TEXT("CLASS [%s]"), **ClassName); STableRow< TSharedPtr >::ConstructInternal( STableRow::FArguments() .ShowSelection(true) .OnDragDetected(InArgs._OnDragDetected), InOwnerTableView ); } private: FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { // If in a Class Viewer and it has not been loaded, load the class when double-left clicking. if ( bIsInClassViewer ) { if( bDynamicClassLoading && AssociatedNode->Class == NULL && AssociatedNode->UnloadedBlueprintData.IsValid() && InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { ClassViewer::Helpers::LoadClass(AssociatedNode); } // If there is a blueprint, open it. Otherwise try to open the class header. if(AssociatedNode->Blueprint.IsValid()) { ClassViewer::Helpers::OpenBlueprintTool(AssociatedNode->Blueprint.Get()); } else { ClassViewer::Helpers::OpenClassHeaderFileInIDE(AssociatedNode->Class.Get()); } } else { OnDoubleClicked.ExecuteIfBound( AssociatedNode ); } return FReply::Handled(); } EVisibility ShowOptions() const { // If it's in viewer mode, show the options combo button. if(bIsInClassViewer) { bool bIsBlueprint(false); bool bHasBlueprint(false); ClassViewer::Helpers::GetClassInfo(AssociatedNode->Class, bIsBlueprint, bHasBlueprint); return (bIsBlueprint || AssociatedNode->Blueprint.IsValid())? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Collapsed; } /** * Generates the drop down menu for the item. * * @return The drop down menu widget. */ TSharedRef GenerateDropDown() { if (UClass* Class = AssociatedNode->Class.Get()) { bool bIsBlueprint(false); bool bHasBlueprint(false); ClassViewer::Helpers::GetClassInfo(Class, bIsBlueprint, bHasBlueprint); bHasBlueprint = AssociatedNode->Blueprint.IsValid(); return ClassViewer::Helpers::CreateMenu(Class, bIsBlueprint, bHasBlueprint); } return SNullWidget::NullWidget; } /** Returns the text color for the item based on if it is selected or not. */ FSlateColor GetTextColor() const { const TSharedPtr< ITypedTableView< TSharedPtr > > OwnerWidget = OwnerTablePtr.Pin(); const TSharedPtr* MyItem = OwnerWidget->Private_ItemFromWidget( this ); const bool bIsSelected = OwnerWidget->Private_IsItemSelected( *MyItem ); if(bIsSelected) { return FSlateColor::UseForeground(); } return TextColor; } private: /** The class name for which this item is associated with. */ TSharedPtr ClassName; /** true if this class is placeable. */ bool bIsClassPlaceable; /** true if in a Class Viewer (as opposed to a Class Picker). */ bool bIsInClassViewer; /** true if dynamic class loading is permitted. */ bool bDynamicClassLoading; /** The text color for this item. */ FSlateColor TextColor; /** The Class Viewer Node this item is associated with. */ TSharedPtr< FClassViewerNode > AssociatedNode; /** the on Double Clicked delegate */ FOnClassItemDoubleClickDelegate OnDoubleClicked; }; static void OnModulesChanged(FName ModuleThatChanged, EModuleChangeReason ReasonForChange) { ClassViewer::Helpers::RequestPopulateClassHierarchy(); } FClassHierarchy::FClassHierarchy() { // Register with the Asset Registry to be informed when it is done loading up files. FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle = AssetRegistryModule.Get().OnFilesLoaded().AddStatic( ClassViewer::Helpers::RequestPopulateClassHierarchy ); AssetRegistryModule.Get().OnAssetAdded().AddRaw( this, &FClassHierarchy::AddAsset); AssetRegistryModule.Get().OnAssetRemoved().AddRaw( this, &FClassHierarchy::RemoveAsset ); // Register to have Populate called when doing a Hot Reload. IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked("HotReload"); HotReloadSupport.OnHotReload().AddRaw( this, &FClassHierarchy::OnHotReload ); // Register to have Populate called when a Blueprint is compiled. OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle = GEditor->OnBlueprintCompiled().AddStatic(ClassViewer::Helpers::RequestPopulateClassHierarchy); OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle = GEditor->OnClassPackageLoadedOrUnloaded().AddStatic(ClassViewer::Helpers::RequestPopulateClassHierarchy); FModuleManager::Get().OnModulesChanged().AddStatic(&OnModulesChanged); } FClassHierarchy::~FClassHierarchy() { // Unregister with the Asset Registry to be informed when it is done loading up files. if( FModuleManager::Get().IsModuleLoaded( TEXT("AssetRegistry") ) ) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnFilesLoaded().Remove(OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle); AssetRegistryModule.Get().OnAssetAdded().RemoveAll( this ); AssetRegistryModule.Get().OnAssetRemoved().RemoveAll( this ); // Unregister to have Populate called when doing a Hot Reload. IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked("HotReload"); HotReloadSupport.OnHotReload().RemoveAll( this ); // Unregister to have Populate called when a Blueprint is compiled. GEditor->OnBlueprintCompiled().Remove(OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle); GEditor->OnClassPackageLoadedOrUnloaded().Remove(OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle); } FModuleManager::Get().OnModulesChanged().RemoveAll(this); } static TSharedPtr< FClassViewerNode > CreateNodeForClass(UClass* Class, const TMultiMap& BlueprintPackageToAssetDataMap) { // Create the new node so it can be passed to AddChildren, fill it in with if it is placeable, abstract, and/or a brush. TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(Class->GetName(), Class->GetDisplayNameText().ToString())); NewNode->Blueprint = ClassViewer::Helpers::GetBlueprint(Class); NewNode->Class = Class; // Retrieve all blueprint classes TArray BlueprintList; BlueprintPackageToAssetDataMap.MultiFind(NewNode->Class->GetOuterUPackage()->GetFName(), BlueprintList); // Check if the Asset Registry found anything, it should, but check. if ( BlueprintList.Num() ) { // Grab the generated class name and check it before assigning. Objects that haven't been saved since this has started to be exported do not have the information. FString* GeneratedClassnamePtr = BlueprintList[0].TagsAndValues.Find(FName("GeneratedClass")); if ( GeneratedClassnamePtr ) { NewNode->GeneratedClassname = FName(**GeneratedClassnamePtr); } } return NewNode; } void FClassHierarchy::OnHotReload( bool bWasTriggeredAutomatically ) { ClassViewer::Helpers::RequestPopulateClassHierarchy(); } void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ) { UClass* RootClass = UObject::StaticClass(); ObjectClassRoot = MakeShareable(new FClassViewerNode(RootClass->GetName(), RootClass->GetDisplayNameText().ToString())); ObjectClassRoot->Class = RootClass; TMap< UClass*, TSharedPtr< FClassViewerNode > > Nodes; Nodes.Add(RootClass, ObjectClassRoot); TSet Visited; Visited.Add(RootClass); // Go through all of the classes children and see if they should be added to the list. for ( TObjectIterator ClassIt; ClassIt; ++ClassIt ) { UClass* CurrentClass = *ClassIt; // Ignore deprecated and temporary trash classes. if (CurrentClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) || FKismetEditorUtilities::IsClassABlueprintSkeleton(CurrentClass)) { continue; } TSharedPtr& Entry = Nodes.FindOrAdd(CurrentClass); if ( Visited.Contains(CurrentClass) ) { continue; } else { while ( CurrentClass->GetSuperClass() != NULL ) { TSharedPtr& ParentEntry = Nodes.FindOrAdd(CurrentClass->GetSuperClass()); if ( !ParentEntry.IsValid() ) { ParentEntry = CreateNodeForClass(CurrentClass->GetSuperClass(), BlueprintPackageToAssetDataMap); } TSharedPtr& MyEntry = Nodes.FindOrAdd(CurrentClass); if ( !MyEntry.IsValid() ) { MyEntry = CreateNodeForClass(CurrentClass, BlueprintPackageToAssetDataMap); } if ( !Visited.Contains(CurrentClass) ) { ParentEntry->AddChild(MyEntry); Visited.Add(CurrentClass); } CurrentClass = CurrentClass->GetSuperClass(); } } } } TSharedPtr< FClassViewerNode > FClassHierarchy::FindParent(const TSharedPtr< FClassViewerNode >& InRootNode, FName InParentClassname, const UClass* InParentClass) { // Check if the current node is the parent classname that is being searched for. if(InRootNode->GeneratedClassname == InParentClassname) { // Return the node if it is the correct parent, this ends the recursion. return InRootNode; } else { // If a class does not have a generated classname, we look up the parent class and compare. const UClass* ParentClass = InParentClass; if(const UClass* RootClass = InRootNode->Class.Get()) { if(ParentClass == RootClass) { return InRootNode; } } } TSharedPtr< FClassViewerNode > ReturnNode; // Search the children recursively, one of them might have the parent. for(int32 ChildClassIndex = 0; !ReturnNode.IsValid() && ChildClassIndex < InRootNode->GetChildrenList().Num(); ChildClassIndex++) { // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. ReturnNode = FindParent(InRootNode->GetChildrenList()[ChildClassIndex], InParentClassname, InParentClass); if(ReturnNode.IsValid()) { break; } } return ReturnNode; } TSharedPtr< FClassViewerNode > FClassHierarchy::FindNodeByGeneratedClassPackageName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InGeneratedClassPackageName) { if(InRootNode->GeneratedClassPackage == InGeneratedClassPackageName) { return InRootNode; } TSharedPtr< FClassViewerNode > ReturnNode; // Search the children recursively, one of them might have the parent. for(int32 ChildClassIndex = 0; !ReturnNode.IsValid() && ChildClassIndex < InRootNode->GetChildrenList().Num(); ChildClassIndex++) { // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. ReturnNode = FindNodeByGeneratedClassPackageName(InRootNode->GetChildrenList()[ChildClassIndex], InGeneratedClassPackageName); if(ReturnNode.IsValid()) { break; } } return ReturnNode; } void FClassHierarchy::UpdateClassInNode(const FString& InGeneratedClassPackageName, UClass* InNewClass, UBlueprint* InNewBluePrint ) { TSharedPtr< FClassViewerNode > Node = FindNodeByGeneratedClassPackageName(ObjectClassRoot, InGeneratedClassPackageName); if( Node.IsValid() ) { Node->Class = InNewClass; Node->Blueprint = InNewBluePrint; } } bool FClassHierarchy::FindAndRemoveNodeByPackageName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InAssetPackage) { bool bReturnValue = false; // Search the children recursively, one of them might have the parent. for(int32 ChildClassIndex = 0; ChildClassIndex < InRootNode->GetChildrenList().Num(); ChildClassIndex++) { if(InRootNode->GetChildrenList()[ChildClassIndex]->GeneratedClassPackage == InAssetPackage) { InRootNode->GetChildrenList().RemoveAt(ChildClassIndex); return true; } // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. bReturnValue = FindAndRemoveNodeByPackageName(InRootNode->GetChildrenList()[ChildClassIndex], InAssetPackage); if(bReturnValue) { break; } } return bReturnValue; } void FClassHierarchy::RemoveAsset(const FAssetData& InRemovedAssetData) { if(FindAndRemoveNodeByPackageName(ObjectClassRoot, InRemovedAssetData.PackageName.ToString())) { // All viewers must refresh. ClassViewer::Helpers::RefreshAll(); } } void FClassHierarchy::AddAsset(const FAssetData& InAddedAssetData) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); if ( !AssetRegistryModule.Get().IsLoadingAssets() ) { TArray AncestorClassNames; AssetRegistryModule.Get().GetAncestorClassNames(InAddedAssetData.AssetClass, AncestorClassNames); if( AncestorClassNames.Contains(UBlueprintCore::StaticClass()->GetFName()) ) { // Make sure that the node does not already exist. There is a bit of double adding going on at times and this prevents it. if(!FindNodeByGeneratedClassPackageName(ObjectClassRoot, InAddedAssetData.PackageName.ToString()).IsValid()) { TSharedPtr< FClassViewerNode > NewNode; LoadUnloadedTagData(NewNode, InAddedAssetData); // Find the blueprint if it's loaded. FindClass(NewNode); // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassName = NewNode->ParentClassname.ToString(); UObject* Outer(NULL); ResolveName(Outer, ParentClassName, false, false); UClass* ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); TSharedPtr< FClassViewerNode > ParentNode = FindParent(ObjectClassRoot, NewNode->ParentClassname, ParentClass); if(ParentNode.IsValid()) { ParentNode->AddChild(NewNode); // Make sure the children are properly sorted. SortChildren(ObjectClassRoot); // All Viewers must repopulate. ClassViewer::Helpers::RefreshAll(); } } } } } void FClassHierarchy::SortChildren( TSharedPtr< FClassViewerNode >& InRootNode ) { TArray< TSharedPtr< FClassViewerNode > >& ChildList = InRootNode->GetChildrenList(); for(int32 ChildIndex = 0; ChildIndex < ChildList.Num(); ChildIndex++) { // Setup the parent weak pointer, useful for going up the tree for unloaded blueprints. ChildList[ChildIndex]->ParentNode = InRootNode; // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. SortChildren(ChildList[ChildIndex]); } // Sort the children. ChildList.Sort(CompareFClassViewerNodes); } void FClassHierarchy::FindClass(TSharedPtr< FClassViewerNode > InOutClassNode) { UPackage* Package = FindPackage(NULL, *InOutClassNode->GeneratedClassPackage ); if (Package) { UObject* Object = FindObject(Package, *InOutClassNode->AssetName); if (Object) { // Check if this item is a blueprint. if (Object->IsA(UBlueprint::StaticClass())) { InOutClassNode->Blueprint = Cast(Object); InOutClassNode->Class = Cast(InOutClassNode->Blueprint->GeneratedClass); } else if (UClass* Class = Cast(Object)) { InOutClassNode->Blueprint = Cast(Class->ClassGeneratedBy); InOutClassNode->Class = Class; } else { InOutClassNode->Class = Object->GetClass(); } } } } void FClassHierarchy::LoadUnloadedTagData(TSharedPtr& InOutClassViewerNode, const FAssetData& InAssetData) { const FString GeneratedClassPackage = InAssetData.PackageName.ToString(); const FString* GeneratedClassname = InAssetData.TagsAndValues.Find("GeneratedClass"); const FString* ParentClassname = InAssetData.TagsAndValues.Find("ParentClass"); const FString* BlueprintType = InAssetData.TagsAndValues.Find("BlueprintType"); const FString AssetName = InAssetData.AssetName.ToString(); // Create the viewer node. InOutClassViewerNode = MakeShareable(new FClassViewerNode(AssetName, AssetName)); // It is an unloaded blueprint, so we need to create the structure that will hold the data. TSharedPtr UnloadedBlueprintData = MakeShareable( new FUnloadedBlueprintData(InOutClassViewerNode) ); InOutClassViewerNode->UnloadedBlueprintData = UnloadedBlueprintData; // Get the class flags. const FString* ClassFlags = InAssetData.TagsAndValues.Find("ClassFlags"); if(ClassFlags) { InOutClassViewerNode->UnloadedBlueprintData->SetClassFlags(FCString::Atoi(**ClassFlags)); } const FString* ImplementedInterfaces = InAssetData.TagsAndValues.Find("ImplementedInterfaces"); if(ImplementedInterfaces) { FString FullInterface; FString RemainingString; FString InterfaceName; FString CurrentString = *ImplementedInterfaces; while(CurrentString.Split(TEXT(","), &FullInterface, &RemainingString)) { if(FullInterface.Split(TEXT("."), &CurrentString, &InterfaceName, ESearchCase::CaseSensitive, ESearchDir::FromEnd)) { if(!CurrentString.StartsWith(TEXT("Graphs=("))) { UnloadedBlueprintData->AddImplementedInterfaces(InterfaceName); } } CurrentString = RemainingString; } } InOutClassViewerNode->GeneratedClassPackage = GeneratedClassPackage; InOutClassViewerNode->ParentClassname = NAME_None; if(ParentClassname) { InOutClassViewerNode->ParentClassname = FName(**ParentClassname); } if(GeneratedClassname) { InOutClassViewerNode->GeneratedClassname = FName(**GeneratedClassname); } if(BlueprintType && *BlueprintType == TEXT("BPType_Normal")) { InOutClassViewerNode->bIsBPNormalType = true; } InOutClassViewerNode->AssetName = AssetName; } void FClassHierarchy::PopulateClassHierarchy() { TArray< TSharedPtr< FClassViewerNode > > RootLevelClasses; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Retrieve all blueprint classes TArray BlueprintList; FARFilter Filter; Filter.ClassNames.Add(UBlueprint::StaticClass()->GetFName()); Filter.ClassNames.Add(UAnimBlueprint::StaticClass()->GetFName()); Filter.ClassNames.Add(UBlueprintGeneratedClass::StaticClass()->GetFName()); // Include any Blueprint based objects as well, this includes things like Blutilities, UMG, and GameplayAbility objects Filter.bRecursiveClasses = true; AssetRegistryModule.Get().GetAssets(Filter, BlueprintList); TMultiMap BlueprintPackageToAssetDataMap; for ( int32 AssetIdx = 0; AssetIdx < BlueprintList.Num(); ++AssetIdx ) { TSharedPtr< FClassViewerNode > NewNode; LoadUnloadedTagData(NewNode, BlueprintList[AssetIdx]); RootLevelClasses.Add(NewNode); // Find the blueprint if it's loaded. FindClass(NewNode); BlueprintPackageToAssetDataMap.Add(BlueprintList[AssetIdx].PackageName, BlueprintList[AssetIdx]); } AddChildren_NoFilter(ObjectClassRoot, BlueprintPackageToAssetDataMap); RootLevelClasses.Add(ObjectClassRoot); // Second pass to link them to parents. for (int32 CurrentNodeIdx = 0; CurrentNodeIdx < RootLevelClasses.Num(); ++CurrentNodeIdx) { if(RootLevelClasses[CurrentNodeIdx]->ParentClassname != NAME_None) { // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassName = RootLevelClasses[CurrentNodeIdx]->ParentClassname.ToString(); UObject* Outer(NULL); ResolveName(Outer, ParentClassName, false, false); const UClass* ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); for (int32 SearchNodeIdx = 0; SearchNodeIdx < RootLevelClasses.Num(); ++SearchNodeIdx) { TSharedPtr< FClassViewerNode > ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], RootLevelClasses[CurrentNodeIdx]->ParentClassname, ParentClass); if(ParentNode.IsValid()) { // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); --CurrentNodeIdx; break; } } } } // Recursively sort the children. SortChildren(ObjectClassRoot); // All viewers must refresh. ClassViewer::Helpers::RefreshAll(); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitializationOptions& InInitOptions ) { bNeedsRefresh = true; InitOptions = InInitOptions; OnClassPicked = InArgs._OnClassPickedDelegate; bSaveExpansionStates = true; bPendingSetExpansionStates = false; bEnableClassDynamicLoading = InInitOptions.bEnableClassDynamicLoading; EVisibility HeaderVisibility = (this->InitOptions.Mode == EClassViewerMode::ClassBrowsing)? EVisibility::Visible : EVisibility::Collapsed; // Set these values to the user specified settings. bIsActorsOnly = InitOptions.bIsActorsOnly | InitOptions.bIsPlaceableOnly; bIsPlaceableOnly = InitOptions.bIsPlaceableOnly; bIsBlueprintBaseOnly = InitOptions.bIsBlueprintBaseOnly; bShowUnloadedBlueprints = InitOptions.bShowUnloadedBlueprints; bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false; // If set to default, decide what display mode to use. if( InitOptions.DisplayMode == EClassViewerDisplayMode::DefaultView ) { // By default the Browser uses the tree view, the Picker the list. The option is available to users to force to another display mode when creating the Class Browser/Picker. if( InitOptions.Mode == EClassViewerMode::ClassBrowsing ) { InitOptions.DisplayMode = EClassViewerDisplayMode::TreeView; } else { InitOptions.DisplayMode = EClassViewerDisplayMode::ListView; } } // Build the top menu. TSharedRef< FUICommandList > CommandList(new FUICommandList()); FMenuBarBuilder MenuBarBuilder( CommandList ); { if( InitOptions.Mode == EClassViewerMode::ClassBrowsing ) { MenuBarBuilder.AddPullDownMenu( LOCTEXT("Filters", "Filters"), LOCTEXT("Filters_Tooltip", "Filter options for the Class Viewer."), FNewMenuDelegate::CreateRaw( this, &SClassViewer::FillFilterEntries ) ); } // Only want the options this menu generates if in tree view. if( InitOptions.Mode == EClassViewerMode::ClassBrowsing && InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView ) { MenuBarBuilder.AddPullDownMenu( LOCTEXT("View", "View"), LOCTEXT("Tree_Tooltip", "Tree options for the Class Viewer."), FNewMenuDelegate::CreateRaw( this, &SClassViewer::FillTreeEntries ) ); } } // Create the asset discovery indicator FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked("EditorWidgets"); TSharedRef AssetDiscoveryIndicator = EditorWidgetsModule.CreateAssetDiscoveryIndicator(EAssetDiscoveryIndicatorScaleMode::Scale_Vertical); FOnContextMenuOpening OnContextMenuOpening; if ( InitOptions.Mode == EClassViewerMode::ClassBrowsing ) { OnContextMenuOpening = FOnContextMenuOpening::CreateSP(this, &SClassViewer::BuildMenuWidget); } // Holds the bulk of the class viewer's sub-widgets, to be added to the widget after construction TSharedPtr< SWidget > ClassViewerContent; SAssignNew(ClassViewerContent, SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ MenuBarBuilder.MakeWidget() ] +SVerticalBox::Slot() .AutoHeight() .Padding( 1.0f, 0.0f, 1.0f, 0.0f ) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Visibility(bHasTitle ? EVisibility::Visible : EVisibility::Collapsed) .ColorAndOpacity(FEditorStyle::GetColor("MultiboxHookColor")) .Text(InitOptions.ViewerTitleString) ] ] +SVerticalBox::Slot() .AutoHeight() [ SAssignNew(SearchBox, SSearchBox) .OnTextChanged( this, &SClassViewer::OnFilterTextChanged ) .OnTextCommitted( this, &SClassViewer::OnFilterTextCommitted ) ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SSeparator) .Visibility(HeaderVisibility) ] +SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SOverlay) +SOverlay::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) [ SNew(SVerticalBox) +SVerticalBox::Slot() .FillHeight(1.0f) [ SAssignNew(ClassTree, STreeView >) .Visibility(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView ? EVisibility::Visible : EVisibility::Collapsed) .SelectionMode(ESelectionMode::Single) .TreeItemsSource( &RootTreeItems ) // Called to child items for any given parent item .OnGetChildren( this, &SClassViewer::OnGetChildrenForClassViewerTree ) // Called to handle recursively expanding/collapsing items .OnSetExpansionRecursive(this, &SClassViewer::SetAllExpansionStates_Helper ) // Generates the actual widget for a tree item .OnGenerateRow( this, &SClassViewer::OnGenerateRowForClassViewer ) // Generates the right click menu. .OnContextMenuOpening( OnContextMenuOpening ) // Find out when the user selects something in the tree .OnSelectionChanged( this, &SClassViewer::OnClassViewerSelectionChanged ) // Called when the expansion state of an item changes .OnExpansionChanged( this, &SClassViewer::OnClassViewerExpansionChanged ) // Allow for some spacing between items with a larger item height. .ItemHeight(20.0f) .HeaderRow ( SNew(SHeaderRow) .Visibility(EVisibility::Collapsed) + SHeaderRow::Column(TEXT("Class")) .DefaultLabel(NSLOCTEXT("ClassViewer", "Class", "Class")) ) ] +SVerticalBox::Slot() .FillHeight(1.0f) [ SAssignNew(ClassList, SListView >) .Visibility(InitOptions.DisplayMode == EClassViewerDisplayMode::ListView ? EVisibility::Visible : EVisibility::Collapsed) .SelectionMode(ESelectionMode::Single) .ListItemsSource( &RootTreeItems ) // Generates the actual widget for a tree item .OnGenerateRow( this, &SClassViewer::OnGenerateRowForClassViewer ) // Generates the right click menu. .OnContextMenuOpening( OnContextMenuOpening ) // Find out when the user selects something in the tree .OnSelectionChanged( this, &SClassViewer::OnClassViewerSelectionChanged ) // Allow for some spacing between items with a larger item height. .ItemHeight(20.0f) .HeaderRow ( SNew(SHeaderRow) .Visibility(EVisibility::Collapsed) + SHeaderRow::Column(TEXT("Class")) .DefaultLabel(NSLOCTEXT("ClassViewer", "Class", "Class")) ) ] ] +SOverlay::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Bottom) .Padding(FMargin(24, 0, 24, 0)) [ // Asset discovery indicator AssetDiscoveryIndicator ] ]; // When using a class picker in list-view mode, the widget will auto-focus the search box // and allow the up and down arrow keys to navigate and enter to pick without using the mouse ever if ( InitOptions.Mode == EClassViewerMode::ClassPicker && InitOptions.DisplayMode == EClassViewerDisplayMode::ListView ) { this->ChildSlot [ SNew(SListViewSelectorDropdownMenu>, SearchBox, ClassList) [ ClassViewerContent.ToSharedRef() ] ]; } else { this->ChildSlot [ ClassViewerContent.ToSharedRef() ]; } // Construct the class hierarchy. ClassViewer::Helpers::ConstructClassHierarchy(); // Only want filter options enabled in browsing mode. if( this->InitOptions.Mode == EClassViewerMode::ClassBrowsing ) { // Default the "Only Placeable" checkbox to be checked, it will check "Only Actors" MenuPlaceableOnly_Execute(); } ClassViewer::Helpers::PopulateClassviewerDelegate.AddSP(this, &SClassViewer::Refresh); // Request delayed setting of focus to the search box bPendingFocusNextFrame = true; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef SClassViewer::GetContent() { return SharedThis( this ); } SClassViewer::~SClassViewer() { ClassViewer::Helpers::PopulateClassviewerDelegate.RemoveAll(this); } void SClassViewer::ClearSelection() { ClassTree->ClearSelection(); } void SClassViewer::OnGetChildrenForClassViewerTree( TSharedPtr InParent, TArray< TSharedPtr< FClassViewerNode > >& OutChildren ) { // Simply return the children, it's already setup. OutChildren = InParent->GetChildrenList(); } void SClassViewer::OnClassViewerSelectionChanged( TSharedPtr Item, ESelectInfo::Type SelectInfo ) { // Do not act on selection change when it is for navigation if(SelectInfo == ESelectInfo::OnNavigation) { return; } // Sometimes the item is not valid anymore due to filtering. if(Item.IsValid() == false || Item->IsRestricted()) { return; } if(InitOptions.Mode == EClassViewerMode::ClassBrowsing) { // Allows the user to right click in the level editor and select to place the selected class. GUnrealEd->SetCurrentClass( Item->Class.Get() ); } else { UClass* Class = Item->Class.Get(); // If the class is NULL and UnloadedBlueprintData is valid then attempt to load it. UnloadedBlueprintData is invalid in the case of a "None" item. if ( bEnableClassDynamicLoading && !Class && Item->UnloadedBlueprintData.IsValid() ) { ClassViewer::Helpers::LoadClass( Item ); } // Check if the item passes the filter, parent items might be displayed but filtered out and thus not desired to be selected. if ( ( Item->Class.IsValid() || !Class )) { if( Item->bPassesFilter ) { OnClassPicked.ExecuteIfBound( Item->Class.Get() ); } else { OnClassPicked.ExecuteIfBound( NULL ); } } } } void SClassViewer::OnClassViewerExpansionChanged(TSharedPtr Item, bool bExpanded) { // Sometimes the item is not valid anymore due to filtering. if (Item.IsValid() == false || Item->IsRestricted()) { return; } ExpansionStateMap.Add(*(Item->GetClassName()), bExpanded); } TSharedPtr< SWidget > SClassViewer::BuildMenuWidget() { bool bIsBlueprint; bool bHasBlueprint; TArray< TSharedPtr< FClassViewerNode > > SelectedList; // Based upon which mode the viewer is in, pull the selected item. if( InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView ) { SelectedList = ClassTree->GetSelectedItems(); } else { SelectedList = ClassList->GetSelectedItems(); } // If there is no selected item, return a null widget. if(SelectedList.Num() == 0) { return SNullWidget::NullWidget; } // If it is NOT stale, it has not been set (meaning it was never valid but now is invalid). if( bEnableClassDynamicLoading && !SelectedList[0]->Class.IsStale() && !SelectedList[0]->Class.IsValid() && SelectedList[0]->UnloadedBlueprintData.IsValid() ) { ClassViewer::Helpers::LoadClass(SelectedList[0]); // Populate the tree/list so any changes to previously unloaded classes will be reflected. Refresh(); } // Get the class and it's info. RightClickClass = SelectedList[0]->Class.Get(); RightClickBlueprint = SelectedList[0]->Blueprint.Get(); ClassViewer::Helpers::GetClassInfo(RightClickClass, bIsBlueprint, bHasBlueprint); if(RightClickBlueprint) { bHasBlueprint = true; } return ClassViewer::Helpers::CreateMenu(RightClickClass, bIsBlueprint, bHasBlueprint); } TSharedRef< ITableRow > SClassViewer::OnGenerateRowForClassViewer( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { // If the item was accepted by the filter, leave it bright, otherwise dim it. float AlphaValue = Item->bPassesFilter? 1.0f : 0.5f; TSharedRef< SClassItem > ReturnRow = SNew(SClassItem, OwnerTable) .ClassName(Item->GetClassName(InitOptions.bShowDisplayNames)) .bIsPlaceable(Item->IsClassPlaceable()) .HighlightText(&SearchBox->GetText()) .TextColor(Item->IsClassPlaceable()? FLinearColor(0.2f, 0.4f, 0.6f, AlphaValue) : FLinearColor(1.0f, 1.0f, 1.0f, AlphaValue)) .AssociatedNode(Item) .bIsInClassViewer( InitOptions.Mode == EClassViewerMode::ClassBrowsing ) .bDynamicClassLoading( bEnableClassDynamicLoading ) .OnDragDetected(this, &SClassViewer::OnDragDetected) .OnClassItemDoubleClicked(FOnClassItemDoubleClickDelegate::CreateSP(this, &SClassViewer::ToggleExpansionState_Helper)); if ( !Item->GeneratedClassPackage.IsEmpty() ) { ReturnRow->SetToolTipText(FText::FromString(Item->GeneratedClassPackage)); } // Expand the item if needed. if (!bPendingSetExpansionStates) { bool* bIsExpanded = ExpansionStateMap.Find(*(Item->GetClassName())); if (bIsExpanded && *bIsExpanded) { bPendingSetExpansionStates = true; } } return ReturnRow; } const TArray< TSharedPtr< FClassViewerNode > > SClassViewer::GetSelectedItems() const { if ( InitOptions.DisplayMode == EClassViewerDisplayMode::ListView ) { return ClassList->GetSelectedItems(); } return ClassTree->GetSelectedItems(); } void SClassViewer::ExpandRootNodes() { for (int32 NodeIdx = 0; NodeIdx < RootTreeItems.Num(); ++NodeIdx) { ExpansionStateMap.Add(*(RootTreeItems[NodeIdx]->GetClassName()), true); ClassTree->SetItemExpansion(RootTreeItems[NodeIdx], true); } } FReply SClassViewer::OnDragDetected( const FGeometry& Geometry, const FPointerEvent& PointerEvent ) { if(InitOptions.Mode == EClassViewerMode::ClassBrowsing) { const TArray< TSharedPtr< FClassViewerNode > > SelectedItems = GetSelectedItems(); if ( SelectedItems.Num() > 0 && SelectedItems[0].IsValid() ) { TSharedRef< FClassViewerNode > Item = SelectedItems[0].ToSharedRef(); // If there is no class then we must spawn an FUnloadedClassDragDropOp so the class will be loaded when dropped. if ( UClass* Class = Item->Class.Get() ) { // Spawn a loaded blueprint just like any other asset from the Content Browser. if ( Item->Blueprint.IsValid() ) { TArray InAssetData; InAssetData.Add(FAssetData(Item->Blueprint.Get())); return FReply::Handled().BeginDragDrop(FAssetDragDropOp::New(InAssetData)); } else { // Add the UClass associated with this item to the drag event being spawned. return FReply::Handled().BeginDragDrop(FClassDragDropOp::New(TWeakObjectPtr(Class))); } } else { return FReply::Handled().BeginDragDrop(FUnloadedClassDragDropOp::New(FClassPackageData(Item->AssetName, Item->GeneratedClassPackage))); } } } return FReply::Unhandled(); } void SClassViewer::OnOpenBlueprintTool() { ClassViewer::Helpers::OpenBlueprintTool(RightClickBlueprint); } void SClassViewer::FindInContentBrowser() { ClassViewer::Helpers::FindInContentBrowser(RightClickBlueprint, RightClickClass); } void SClassViewer::OnFilterTextChanged( const FText& InFilterText ) { // If the filter was empty, we want it to save the expansion states so when it returns to empty it can restore them. bSaveExpansionStates = !FilterSearchTerms.Num(); FilterSearchTerms.Empty(); FString CurrentFilterText = InFilterText.ToString(); // Sanitize, parse and split the filter text into search terms CurrentFilterText.Trim().TrimTrailing(); //FilterSearchTerms.Reset(); { bool bIsWithinQuotedSection = false; FString NewSearchTerm; for( auto CurChar : CurrentFilterText ) { // Keep an eye out for double-quotes. We want to retain whitespace within a search term if // it has double-quotes around it if( CurChar == TCHAR('\"') ) { // Toggle whether we're within a quoted section, but don't bother adding the quote character bIsWithinQuotedSection = !bIsWithinQuotedSection; } else if( bIsWithinQuotedSection || !FChar::IsWhitespace( CurChar ) ) { // Add the character! NewSearchTerm.AppendChar( CurChar ); } else { // Encountered whitespace, so add the search term up to here if( NewSearchTerm.Len() > 0 ) { FilterSearchTerms.Add( NewSearchTerm ); NewSearchTerm = FString(); } } } // Encountered EOL, so add the search term up to here if( NewSearchTerm.Len() > 0 ) { FilterSearchTerms.Add( NewSearchTerm ); NewSearchTerm = FString(); } if( bIsWithinQuotedSection ) { // User forgot to terminate their double-quoted section. No big deal. } } // Repopulate the list to show only what has not been filtered out. Refresh(); } void SClassViewer::OnFilterTextCommitted(const FText& InText, ETextCommit::Type CommitInfo) { if (CommitInfo == ETextCommit::OnEnter) { if (InitOptions.Mode == EClassViewerMode::ClassPicker) { TArray< TSharedPtr< FClassViewerNode > > SelectedList = ClassList->GetSelectedItems(); TSharedPtr< FClassViewerNode > FirstSelected; UClass* Class = NULL; if (SelectedList.Num() > 0) { FirstSelected = SelectedList[0]; Class = FirstSelected->Class.Get(); // If the class is NULL and UnloadedBlueprintData is valid then attempt to load it. UnloadedBlueprintData is invalid in the case of a "None" item. if ( bEnableClassDynamicLoading && Class == nullptr && FirstSelected->UnloadedBlueprintData.IsValid()) { ClassViewer::Helpers::LoadClass(FirstSelected); Class = FirstSelected->Class.Get(); } // Check if the item passes the filter, parent items might be displayed but filtered out and thus not desired to be selected. if (Class && FirstSelected->bPassesFilter == true) { OnClassPicked.ExecuteIfBound(Class); } } } } } bool SClassViewer::Menu_CanExecute() const { return true; } void SClassViewer::MenuActorsOnly_Execute() { bIsActorsOnly = !bIsActorsOnly; // "Placeable Only" cannot be true when "Actors Only" is false. if(!bIsActorsOnly) { bIsPlaceableOnly = false; } Refresh(); } bool SClassViewer::MenuActorsOnly_IsChecked() const { return bIsActorsOnly; } void SClassViewer::MenuPlaceableOnly_Execute() { bIsPlaceableOnly = !bIsPlaceableOnly; // "Actors Only" must be true when "Placeable Only" is true. if(bIsPlaceableOnly) { bIsActorsOnly = true; } Refresh(); } bool SClassViewer::MenuPlaceableOnly_IsChecked() const { return bIsPlaceableOnly; } void SClassViewer::MenuBlueprintBasesOnly_Execute() { bIsBlueprintBaseOnly = !bIsBlueprintBaseOnly; Refresh(); } bool SClassViewer::MenuBlueprintBasesOnly_IsChecked() const { return bIsBlueprintBaseOnly; } void SClassViewer::FillFilterEntries( FMenuBuilder& MenuBuilder ) { MenuBuilder.BeginSection("ClassViewerFilterEntries"); { MenuBuilder.AddMenuEntry( LOCTEXT("ActorsOnly", "Actors Only"), LOCTEXT( "ActorsOnly_Tooltip", "Filter the Class Viewer to show only actors" ), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SClassViewer::MenuActorsOnly_Execute), FCanExecuteAction::CreateRaw(this, &SClassViewer::Menu_CanExecute), FIsActionChecked::CreateRaw(this, &SClassViewer::MenuActorsOnly_IsChecked)), NAME_None, EUserInterfaceActionType::Check ); MenuBuilder.AddMenuEntry( LOCTEXT("PlaceableOnly", "Placeable Only"), LOCTEXT( "PlaceableOnly_Tooltip", "Filter the Class Viewer to show only placeable actors." ), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SClassViewer::MenuPlaceableOnly_Execute), FCanExecuteAction::CreateRaw(this, &SClassViewer::Menu_CanExecute), FIsActionChecked::CreateRaw(this, &SClassViewer::MenuPlaceableOnly_IsChecked)), NAME_None, EUserInterfaceActionType::Check ); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("ClassViewerFilterEntries2"); { MenuBuilder.AddMenuEntry( LOCTEXT("BlueprintsOnly", "Blueprint Class Bases Only"), LOCTEXT( "BlueprinsOnly_Tooltip", "Filter the Class Viewer to show only base blueprint classes." ), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &SClassViewer::MenuBlueprintBasesOnly_Execute), FCanExecuteAction::CreateRaw(this, &SClassViewer::Menu_CanExecute), FIsActionChecked::CreateRaw(this, &SClassViewer::MenuBlueprintBasesOnly_IsChecked)), NAME_None, EUserInterfaceActionType::Check ); } MenuBuilder.EndSection(); } void SClassViewer::FillTreeEntries( FMenuBuilder& MenuBuilder ) { MenuBuilder.AddMenuEntry( LOCTEXT("ExpandAll", "Expand All"), LOCTEXT( "ExpandAll_Tooltip", "Expands the entire tree" ), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::SetAllExpansionStates, bool(true)) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("CollapseAll", "Collapse All"), LOCTEXT( "CollapseAll_Tooltip", "Collapses the entire tree" ), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::SetAllExpansionStates, bool(false)) ), NAME_None, EUserInterfaceActionType::Button ); } void SClassViewer::SetAllExpansionStates(bool bInExpansionState) { // Go through all the items in the root of the tree and recursively visit their children to set every item in the tree. for(int32 ChildIndex = 0; ChildIndex < RootTreeItems.Num(); ChildIndex++) { SetAllExpansionStates_Helper( RootTreeItems[ChildIndex], bInExpansionState ); } } void SClassViewer::SetAllExpansionStates_Helper(TSharedPtr< FClassViewerNode > InNode, bool bInExpansionState) { ClassTree->SetItemExpansion(InNode, bInExpansionState); // Recursively go through the children. for(int32 ChildIndex = 0; ChildIndex < InNode->GetChildrenList().Num(); ChildIndex++) { SetAllExpansionStates_Helper( InNode->GetChildrenList()[ChildIndex], bInExpansionState ); } } void SClassViewer::ToggleExpansionState_Helper(TSharedPtr< FClassViewerNode > InNode) { bool bExpanded = ClassTree->IsItemExpanded( InNode ); ClassTree->SetItemExpansion(InNode, !bExpanded); } bool SClassViewer::ExpandFilteredInNodes(TSharedPtr InNode) { bool bShouldExpand(InNode->bPassesFilter); for(int32 ChildIdx = 0; ChildIdx < InNode->GetChildrenList().Num(); ChildIdx++) { bShouldExpand |= ExpandFilteredInNodes(InNode->GetChildrenList()[ChildIdx]); } if(bShouldExpand) { ClassTree->SetItemExpansion(InNode, true); } return bShouldExpand; } void SClassViewer::MapExpansionStatesInTree( TSharedPtr InItem ) { ExpansionStateMap.Add( *(InItem->GetClassName()), ClassTree->IsItemExpanded( InItem ) ); // Map out all the children, this will be done recursively. for( int32 ChildIdx(0); ChildIdx < InItem->GetChildrenList().Num(); ++ChildIdx ) { MapExpansionStatesInTree( InItem->GetChildrenList()[ChildIdx] ); } } void SClassViewer::SetExpansionStatesInTree( TSharedPtr InItem ) { bool* bIsExpanded = ExpansionStateMap.Find( *(InItem->GetClassName()) ); if( bIsExpanded ) { ClassTree->SetItemExpansion( InItem, *bIsExpanded ); // No reason to set expansion states if the parent is not expanded, it does not seem to do anything. if( *bIsExpanded ) { for( int32 ChildIdx(0); ChildIdx < InItem->GetChildrenList().Num(); ++ChildIdx ) { SetExpansionStatesInTree( InItem->GetChildrenList()[ChildIdx] ); } } } else { // Default to no expansion. ClassTree->SetItemExpansion( InItem, false ); } } void SClassViewer::Populate() { bPendingSetExpansionStates = false; // If showing a class tree, we may need to save expansion states. if( ClassTree->GetVisibility() == EVisibility::Visible ) { if( bSaveExpansionStates ) { for( int32 ChildIdx(0); ChildIdx < RootTreeItems.Num(); ++ChildIdx ) { // Check if the item is actually expanded or if it's only expanded because it is root level. bool* bIsExpanded = ExpansionStateMap.Find( *(RootTreeItems[ChildIdx]->GetClassName()) ); if((bIsExpanded && !*bIsExpanded) || !bIsExpanded) { ClassTree->SetItemExpansion( RootTreeItems[ChildIdx], false ); } // Recursively map out the expansion state of the tree-node. MapExpansionStatesInTree( RootTreeItems[ChildIdx] ); } } // This is set to false before the call to populate when it is not desired. bSaveExpansionStates = true; } // Empty the tree out so it can be redone. RootTreeItems.Empty(); // Based on if the list or tree is visible we create what will be displayed differently. if(ClassTree->GetVisibility() == EVisibility::Visible) { // The root node for the tree, will be "Object" which we will skip. TSharedPtr RootNode; // Get the class tree, passing in certain filter options. ClassViewer::Helpers::GetClassTree(InitOptions, RootNode, FilterSearchTerms, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), bShowUnloadedBlueprints ); // Check if we will restore expansion states, we will not if there is filtering happening. const bool bRestoreExpansionState = !FilterSearchTerms.Num(); if(InitOptions.bShowObjectRootClass) { RootTreeItems.Add(RootNode); if( bRestoreExpansionState ) { SetExpansionStatesInTree( RootNode ); } // Expand any items that pass the filter. if(FilterSearchTerms.Num() > 0) { ExpandFilteredInNodes(RootNode); } } else { // Add all the children of the "Object" root. for(int32 ChildIndex = 0; ChildIndex < RootNode->GetChildrenList().Num(); ChildIndex++) { RootTreeItems.Add(RootNode->GetChildrenList()[ChildIndex]); if( bRestoreExpansionState ) { SetExpansionStatesInTree( RootTreeItems[ChildIndex] ); } // Expand any items that pass the filter. if(FilterSearchTerms.Num() > 0) { ExpandFilteredInNodes(RootNode->GetChildrenList()[ChildIndex]); } } } // Only display this option if the user wants it and in Picker Mode. if(InitOptions.bShowNoneOption && InitOptions.Mode == EClassViewerMode::ClassPicker) { // @todo - It would seem smart to add this in before the other items, since it needs to be on top. However, that causes strange issues with saving/restoring expansion states. // This is likely not very efficient since the list can have hundreds and even thousands of items. RootTreeItems.Insert(CreateNoneOption(), 0); } // Now that new items are in the tree, we need to request a refresh. ClassTree->RequestTreeRefresh(); } else { // Get the class list, passing in certain filter options. ClassViewer::Helpers::GetClassList(InitOptions, RootTreeItems, FilterSearchTerms, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), bShowUnloadedBlueprints); // Sort the list alphabetically. struct FCompareFClassViewerNode { FORCEINLINE bool operator()( TSharedPtr A, TSharedPtr B ) const { check(A.IsValid()); check(B.IsValid()); // Pull out the FString, for ease of reading. const FString& AString = *A->GetClassName().Get(); const FString& BString = *B->GetClassName().Get(); return AString < BString; } }; RootTreeItems.Sort( FCompareFClassViewerNode() ); // Only display this option if the user wants it and in Picker Mode. if(InitOptions.bShowNoneOption && InitOptions.Mode == EClassViewerMode::ClassPicker) { // @todo - It would seem smart to add this in before the other items, since it needs to be on top. However, that causes strange issues with saving/restoring expansion states. // This is likely not very efficient since the list can have hundreds and even thousands of items. RootTreeItems.Insert(CreateNoneOption(), 0); } // Now that new items are in the list, we need to request a refresh. ClassList->RequestListRefresh(); } } FReply SClassViewer::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { // Forward key down to class tree return ClassTree->OnKeyDown(MyGeometry,InKeyEvent); } FReply SClassViewer::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent ) { if (RootTreeItems.Num() > 0) { ClassTree->SetItemSelection(RootTreeItems[0], true, ESelectInfo::OnMouseClick); ClassTree->SetItemExpansion(RootTreeItems[0], true); OnClassViewerSelectionChanged(RootTreeItems[0],ESelectInfo::OnMouseClick); } FSlateApplication::Get().SetKeyboardFocus(SearchBox.ToSharedRef(), EFocusCause::SetDirectly); return FReply::Unhandled(); } bool SClassViewer::SupportsKeyboardFocus() const { return true; } void SClassViewer::DestroyClassHierarchy() { ClassViewer::Helpers::DestroyClassHierachy(); } TSharedPtr SClassViewer::CreateNoneOption() { TSharedPtr NoneItem = MakeShareable( new FClassViewerNode("None", "None") ); // The item "passes" the filter so it does not appear grayed out. NoneItem->bPassesFilter = true; return NoneItem; } void SClassViewer::Refresh() { bNeedsRefresh = true; } void SClassViewer::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { // Will populate the class hierarchy as needed. ClassViewer::Helpers::PopulateClassHierarchy(); // Move focus to search box if (bPendingFocusNextFrame && SearchBox.IsValid()) { FWidgetPath WidgetToFocusPath; FSlateApplication::Get().GeneratePathToWidgetUnchecked( SearchBox.ToSharedRef(), WidgetToFocusPath ); FSlateApplication::Get().SetKeyboardFocus( WidgetToFocusPath, EFocusCause::SetDirectly ); bPendingFocusNextFrame = false; } if (bNeedsRefresh) { bNeedsRefresh = false; Populate(); if (InitOptions.bExpandRootNodes) { ExpandRootNodes(); } } if (bPendingSetExpansionStates) { check(RootTreeItems.Num() > 0); SetExpansionStatesInTree(RootTreeItems[0]); bPendingSetExpansionStates = false; } } bool SClassViewer::IsClassAllowed(const UClass* InClass) const { return ClassViewer::Helpers::IsClassAllowed(InitOptions, InClass); } #undef LOCTEXT_NAMESPACE