// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SClassViewer.h" #include "Misc/MessageDialog.h" #include "HAL/FileManager.h" #include "Misc/ConfigCacheIni.h" #include "Misc/FeedbackContext.h" #include "Modules/ModuleManager.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" #include "UObject/CoreRedirects.h" #include "Misc/PackageName.h" #include "Widgets/SOverlay.h" #include "Layout/WidgetPath.h" #include "SlateOptMacros.h" #include "Framework/Application/SlateApplication.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandList.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Images/SImage.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SComboButton.h" #include "Framework/Docking/TabManager.h" #include "EditorStyleSet.h" #include "GameFramework/Actor.h" #include "Engine/BlueprintCore.h" #include "Engine/Blueprint.h" #include "Engine/Brush.h" #include "AssetData.h" #include "Editor/UnrealEdEngine.h" #include "Animation/AnimBlueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "EditorDirectories.h" #include "Dialogs/Dialogs.h" #include "UnrealEdGlobals.h" #include "EditorWidgetsModule.h" #include "Styling/SlateIconFinder.h" #include "DragAndDrop/ClassDragDropOp.h" #include "DragAndDrop/AssetDragDropOp.h" #include "IAssetTools.h" #include "ARFilter.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "Kismet2/KismetEditorUtilities.h" #include "Editor.h" #include "Toolkits/AssetEditorManager.h" #include "PackageTools.h" #include "Logging/MessageLog.h" #include "AssetRegistryModule.h" #include "AssetToolsModule.h" #include "ClassViewerNode.h" #include "ClassViewerFilter.h" #include "UnloadedBlueprintData.h" #include "EditorClassUtils.h" #include "IDocumentation.h" #include "PropertyHandle.h" #include "AddToProjectConfig.h" #include "GameProjectGenerationModule.h" #include "SourceCodeNavigation.h" #include "Misc/HotReloadInterface.h" #include "Widgets/Input/SSearchBox.h" #include "Misc/TextFilterExpressionEvaluator.h" #include "SListViewSelectorDropdownMenu.h" #include "ClassViewerProjectSettings.h" #include "Widgets/Layout/SScrollBorder.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 InGeneratedClassPath The path of the generated class to find the node for. * @param InNewClass The class to update the node with. */ void UpdateClassInNode(FName InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint ); /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing class names. * @param InClassName The name of the generated class package to find the node for. * * @return The node. */ TSharedPtr< FClassViewerNode > FindNodeByClassName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InClassName); 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 InGeneratedClassPath The path of the generated class to find the node for. * * @return The node. */ TSharedPtr< FClassViewerNode > FindNodeByGeneratedClassPath(const TSharedPtr< FClassViewerNode >& InRootNode, FName InGeneratedClassPath); /** * 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 InClassPath The class path of the asset to delete * * @return Returns true if the asset was found and deleted successfully. */ bool FindAndRemoveNodeByClassPath(const TSharedPtr< FClassViewerNode >& InRootNode, FName InClassPath); /** 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(FName InGeneratedClassPath, 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(), MakeShared()); } 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(), MakeShared()); } return true; } /** Checks if the TestString passes the filter. * @param InTestString The string to test against the filter. * @param InTextFilter Compiled text filter to apply. * * @return true if it passes the filter. */ static bool PassesFilter( const FString& InTestString, const FTextFilterExpressionEvaluator& InTextFilter ) { class FClassFilterContext : public ITextFilterExpressionContext { public: explicit FClassFilterContext(const FString& InStr) : StrPtr(&InStr) { } virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override { return TextFilterUtils::TestBasicStringExpression(*StrPtr, InValue, InTextComparisonMode); } virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override { return false; } private: const FString* StrPtr; }; return InTextFilter.TestTextFilter(FClassFilterContext(InTestString)); } /** 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 InTextFilter Compiled text filter to apply. * @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. * @param InAllowedDeveloperType Filter option for dealing with developer folders. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * * @return Returns true if the child passed the filter. */ static bool AddChildren_Tree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, const TArray& InternalClasses, const TArray& InternalPaths) { 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; // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. bool bPassesDeveloperFilter = true; static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) { bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); } else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) { if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); } // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? // INI path: /Script/ClassViewer.ClassViewerProjectSettings bool bPassesInternalFilter = true; if (!bInInternalClasses && InternalPaths.Num() > 0) { for (int i = 0; i < InternalPaths.Num(); i++) { if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) { bPassesInternalFilter = false; break; } } } if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter && InOriginalRootNode->Class.IsValid()) { for (int i = 0; i < InternalClasses.Num(); i++) { if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) { bPassesInternalFilter = false; break; } } } // 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 && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); } } else { bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); } 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], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); 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 InTextFilter Compiled text filter to apply. * @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. * @param InAllowedDeveloperType Filter option for dealing with developer folders. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * @return A fully built tree. */ static void GetClassTree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) { 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], InTextFilter, true, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths)) InOutRootNode->AddChild(ChildNode); } } else { AddChildren_Tree(InInitOptions, InOutRootNode, ObjectClassRoot, InTextFilter, false, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); } } /** 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 InTextFilter Compiled text filter to apply. * @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. * @param InAllowedDeveloperType Filter option for dealing with developer folders. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * * @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 FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, const TArray& InternalClasses, const TArray& InternalPaths) { if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) { return; } bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); bool bPassesPlaceableFilter = false; const bool bPassesEditorClassTest = !InInitOptions.bEditorClassesOnly || InOriginalRootNode->IsEditorOnlyClass(); // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. bool bPassesDeveloperFilter = true; static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) { bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); } else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) { if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); } // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? // INI path: /Script/ClassViewer.ClassViewerProjectSettings bool bPassesInternalFilter = true; if (!bInInternalClasses && InternalPaths.Num() > 0) { for (int i = 0; i < InternalPaths.Num(); i++) { if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) { bPassesInternalFilter = false; break; } } } if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter) { for (int i = 0; i < InternalClasses.Num(); i++) { if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) { bPassesInternalFilter = false; break; } } } // 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 && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); } } else { NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); } 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], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); } } /** 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 InTextFilter Compiled text filter to apply. * @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. * @param InAllowedDeveloperType Filter option for dealing with developer folders. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * * @return A fully built list. */ static void GetClassList(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) { 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(), InTextFilter); 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], InTextFilter, bInOnlyActors, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); } } /** 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 nullptr; } /** 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 != nullptr; } 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; } /** * 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 == nullptr || !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(nullptr, *PackageName); check(Package); // Handle fully loading packages before creating new objects. TArray TopLevelPackages; TopLevelPackages.Add( Package->GetOutermost() ); if( !UPackageTools::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); UClass* Class = LoadObject(nullptr, *InOutClassNode->ClassPath.ToString()); GWarn->EndSlowTask(); if (Class) { 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->ClassPath, InOutClassNode->Class.Get(), InOutClassNode->Blueprint.Get() ); } else { FMessageLog EditorErrors("EditorErrors"); FFormatNamedArguments Arguments; Arguments.Add(TEXT("ObjectName"), FText::FromName(InOutClassNode->ClassPath)); EditorErrors.Error(FText::Format(LOCTEXT("PackageLoadFail", "Failed to load class {ObjectName}"), Arguments)); } } /** * Opens a blueprint. * * @param InBlueprint The blueprint to open. */ static void OpenBlueprintTool(UBlueprint* InBlueprint) { if( InBlueprint != nullptr ) { FAssetEditorManager::Get().OpenEditorForAsset(InBlueprint); } } /** * Opens a class's source file. * * @param InClass The class to open source for. */ static void OpenClassInIDE(UClass* InClass) { //ignore result FSourceCodeNavigation::NavigateToClass(InClass); } /** * 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 InGeneratedClassPath The name of the generated class to find the node for. * @param InNewClass The class to update the node with. */ static void UpdateClassInNode(FName InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint ) { ClassHierarchy->UpdateClassInNode(InGeneratedClassPath, 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::OpenClassInIDE, Class)); MenuBuilder.AddMenuEntry(LOCTEXT("ClassViewerMenuOpenCPlusPlusClass", "Open Source Code..."), LOCTEXT("ClassViewerMenuOpenCPlusPlusClass_Tooltip", "Open the source 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() , _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( 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(); ToolTip = FEditorClassUtils::GetTooltip(Class); } else if (AssociatedNode->ClassPath != NAME_None) { ToolTip = SNew(SToolTip).Text(FText::FromName(AssociatedNode->ClassPath)); } return ToolTip; } }; bool bIsRestricted = AssociatedNode->IsRestricted(); const FSlateBrush* ClassIcon = FSlateIconFinder::FindIconBrushForClass(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 == nullptr && 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::OpenClassInIDE(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. if(FModuleManager::Get().IsModuleLoaded("HotReload")) { IHotReloadInterface& HotReloadSupport = FModuleManager::GetModuleChecked("HotReload"); HotReloadSupport.OnHotReload().RemoveAll(this); } if(GEditor) { // 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; NewNode->ClassPath = FName(*Class->GetPathName()); if (Class->GetSuperClass()) { NewNode->ParentClassPath = FName(*Class->GetSuperClass()->GetPathName()); } 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() != nullptr ) { 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->ClassPath == 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::FindNodeByClassName(const TSharedPtr< FClassViewerNode >& InRootNode, const FString& InClassName) { FString NodeClassName = InRootNode->Class.IsValid() ? InRootNode->Class->GetPathName() : FString(); if (NodeClassName == InClassName) { 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 = FindNodeByClassName(InRootNode->GetChildrenList()[ChildClassIndex], InClassName); if (ReturnNode.IsValid()) { break; } } return ReturnNode; } TSharedPtr< FClassViewerNode > FClassHierarchy::FindNodeByGeneratedClassPath(const TSharedPtr< FClassViewerNode >& InRootNode, FName InGeneratedClassPath) { if(InRootNode->ClassPath == InGeneratedClassPath) { 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 = FindNodeByGeneratedClassPath(InRootNode->GetChildrenList()[ChildClassIndex], InGeneratedClassPath); if(ReturnNode.IsValid()) { break; } } return ReturnNode; } void FClassHierarchy::UpdateClassInNode(FName InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint ) { TSharedPtr< FClassViewerNode > Node = FindNodeByGeneratedClassPath(ObjectClassRoot, InGeneratedClassPath); if( Node.IsValid() ) { Node->Class = InNewClass; Node->Blueprint = InNewBluePrint; } } bool FClassHierarchy::FindAndRemoveNodeByClassPath(const TSharedPtr< FClassViewerNode >& InRootNode, FName InClassPath) { 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]->ClassPath == InClassPath) { 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 = FindAndRemoveNodeByClassPath(InRootNode->GetChildrenList()[ChildClassIndex], InClassPath); if(bReturnValue) { break; } } return bReturnValue; } void FClassHierarchy::RemoveAsset(const FAssetData& InRemovedAssetData) { FString ClassObjectPath; if (InRemovedAssetData.GetTagValue(FBlueprintTags::GeneratedClassPath, ClassObjectPath)) { ClassObjectPath = FPackageName::ExportTextPathToObjectPath(ClassObjectPath); } if (FindAndRemoveNodeByClassPath(ObjectClassRoot, FName(*ClassObjectPath))) { // 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()) ) { FString ClassObjectPath; if (InAddedAssetData.GetTagValue(FBlueprintTags::GeneratedClassPath, ClassObjectPath)) { ClassObjectPath = FPackageName::ExportTextPathToObjectPath(ClassObjectPath); } // 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(!FindNodeByGeneratedClassPath(ObjectClassRoot, FName(*ClassObjectPath)).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 ParentClassPath = NewNode->ParentClassPath.ToString(); UClass* ParentClass = FindObject(nullptr, *ParentClassPath); TSharedPtr< FClassViewerNode > ParentNode = FindParent(ObjectClassRoot, NewNode->ParentClassPath, 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) { UClass* Class = FindObject(nullptr, *InOutClassNode->ClassPath.ToString()); if (Class) { InOutClassNode->Blueprint = Cast(Class->ClassGeneratedBy); InOutClassNode->Class = Class; } } void FClassHierarchy::LoadUnloadedTagData(TSharedPtr& InOutClassViewerNode, const FAssetData& InAssetData) { const FString ClassName = InAssetData.AssetName.ToString(); FString ClassDisplayName = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintDisplayName); if (ClassDisplayName.IsEmpty()) { ClassDisplayName = ClassName; } // Create the viewer node. We use the name without _C for both InOutClassViewerNode = MakeShareable(new FClassViewerNode(ClassName, ClassDisplayName)); InOutClassViewerNode->BlueprintAssetPath = InAssetData.ObjectPath; FString ClassObjectPath; if (InAssetData.GetTagValue(FBlueprintTags::GeneratedClassPath, ClassObjectPath)) { InOutClassViewerNode->ClassPath = FName(*FPackageName::ExportTextPathToObjectPath(ClassObjectPath)); } FString ParentClassPathString; if (InAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassPathString)) { InOutClassViewerNode->ParentClassPath = FName(*FPackageName::ExportTextPathToObjectPath(ParentClassPathString)); } InOutClassViewerNode->bIsBPNormalType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); // 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 uint32 ClassFlags = InAssetData.GetTagValueRef(FBlueprintTags::ClassFlags); InOutClassViewerNode->UnloadedBlueprintData->SetClassFlags(ClassFlags); const FString ImplementedInterfaces = InAssetData.GetTagValueRef(FBlueprintTags::ImplementedInterfaces); if(!ImplementedInterfaces.IsEmpty()) { FString FullInterface; FString RemainingString; FString InterfacePath; FString CurrentString = *ImplementedInterfaces; while(CurrentString.Split(TEXT(","), &FullInterface, &RemainingString)) { if (!CurrentString.StartsWith(TEXT("Graphs=("))) { if (FullInterface.Split(TEXT("\""), &CurrentString, &InterfacePath, ESearchCase::CaseSensitive)) { // The interface paths in metadata end with "', so remove those InterfacePath.RemoveFromEnd(TEXT("\"'")); FCoreRedirectObjectName ResolvedInterfaceName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Class, FCoreRedirectObjectName(InterfacePath)); UnloadedBlueprintData->AddImplementedInterface(ResolvedInterfaceName.ObjectName.ToString()); } } CurrentString = RemainingString; } } } 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]->ParentClassPath != NAME_None) { // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassPath = RootLevelClasses[CurrentNodeIdx]->ParentClassPath.ToString(); const UClass* ParentClass = FindObject(nullptr, *ParentClassPath); for (int32 SearchNodeIdx = 0; SearchNodeIdx < RootLevelClasses.Num(); ++SearchNodeIdx) { TSharedPtr< FClassViewerNode > ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], RootLevelClasses[CurrentNodeIdx]->ParentClassPath, 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; NumClasses = 0; bCanShowInternalClasses = true; // Listen for when view settings are changed UClassViewerSettings::OnSettingChanged().AddSP(this, &SClassViewer::HandleSettingChanged); InitOptions = InInitOptions; OnClassPicked = InArgs._OnClassPickedDelegate; TextFilterPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString)); 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; } } TSharedRef FiltersWidget = SNullWidget::NullWidget; // Build the top menu if(InitOptions.Mode == EClassViewerMode::ClassBrowsing) { FiltersWidget = SNew(SComboButton) .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") .ForegroundColor(FLinearColor::White) .ContentPadding(0) .ToolTipText(LOCTEXT("Filters_Tooltip", "Filter options for the Class Viewer.")) .OnGetMenuContent(this, &SClassViewer::FillFilterEntries) .HasDownArrow(true) .ContentPadding(FMargin(1, 0)) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) [ SNew(STextBlock) .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") .Text(LOCTEXT("Filters", "Filters")) ] ]; } // 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); } SAssignNew(ClassList, SListView >) .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")) ); SAssignNew(ClassTree, STreeView >) .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")) ); TSharedRef > > ClassTreeView = ClassTree.ToSharedRef(); TSharedRef > > ClassListView = ClassList.ToSharedRef(); // Holds the bulk of the class viewer's sub-widgets, to be added to the widget after construction TSharedPtr< SWidget > ClassViewerContent; ClassViewerContent = SNew(SBox) .MaxDesiredHeight(800.0f) [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush(InitOptions.bShowBackgroundBorder ? "ToolPanel.GroupBorder" : "NoBorder")) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ 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() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 2.0f) [ FiltersWidget ] + SHorizontalBox::Slot() .Padding(2.0f, 2.0f) [ 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) [ SNew(SScrollBorder, ClassTreeView) .Visibility(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView ? EVisibility::Visible : EVisibility::Collapsed) [ ClassTreeView ] ] +SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SScrollBorder, ClassListView) .Visibility(InitOptions.DisplayMode == EClassViewerDisplayMode::ListView ? EVisibility::Visible : EVisibility::Collapsed) [ ClassListView ] ] ] +SOverlay::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Bottom) .Padding(FMargin(24, 0, 24, 0)) [ // Asset discovery indicator AssetDiscoveryIndicator ] ] // Bottom panel + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) // Asset count + SHorizontalBox::Slot() .FillWidth(1.f) .VAlign(VAlign_Center) .Padding(8, 0) [ SNew(STextBlock) .Text(this, &SClassViewer::GetClassCountText) ] // View mode combo button + SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(ViewOptionsComboButton, SComboButton) .ContentPadding(0) .ForegroundColor(this, &SClassViewer::GetViewButtonForegroundColor) .ButtonStyle(FEditorStyle::Get(), "ToggleButton") // Use the tool bar item style for this button .OnGetMenuContent(this, &SClassViewer::GetViewButtonContent) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage).Image(FEditorStyle::GetBrush("GenericViewButton")) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) .VAlign(VAlign_Center) [ SNew(STextBlock).Text(LOCTEXT("ViewButton", "View Options")) ] ] ] ] ] ]; if (ViewOptionsComboButton.IsValid()) { ViewOptionsComboButton->SetVisibility(InitOptions.bAllowViewOptions ? EVisibility::Visible : EVisibility::Collapsed); } // 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); // Remove the listener for when view settings are changed UClassViewerSettings::OnSettingChanged().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 && InitOptions.DisplayMode == EClassViewerDisplayMode::ListView) { 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 nullptr 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( nullptr ); } } } } 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.NameTypeToDisplay)) .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)); // 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(); } const int SClassViewer::GetNumItems() const { return NumClasses; } FSlateColor SClassViewer::GetViewButtonForegroundColor() const { static const FName InvertedForegroundName("InvertedForeground"); static const FName DefaultForegroundName("DefaultForeground"); return ViewOptionsComboButton->IsHovered() ? FEditorStyle::GetSlateColor(InvertedForegroundName) : FEditorStyle::GetSlateColor(DefaultForegroundName); } TSharedRef SClassViewer::GetViewButtonContent() { // Get all menu extenders for this context menu from the content browser module FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr, nullptr, /*bCloseSelfOnly=*/ true); 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); MenuBuilder.BeginSection("Filters", LOCTEXT("ClassViewerFiltersHeading", "Class Filters")); { MenuBuilder.AddMenuEntry( LOCTEXT("ShowInternalClassesOption", "Show Internal Classes"), LOCTEXT("ShowInternalClassesOptionToolTip", "Shows internal-use only classes in the view."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::ToggleShowInternalClasses), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SClassViewer::IsShowingInternalClasses) ), NAME_None, EUserInterfaceActionType::ToggleButton ); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("DeveloperViewType", LOCTEXT("DeveloperViewTypeHeading", "Developer Folder Filter")); { MenuBuilder.AddMenuEntry( LOCTEXT("NoneDeveloperViewOption", "None"), LOCTEXT("NoneDeveloperViewOptionToolTip", "Filter classes to show no classes in developer folders."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::SetCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_None), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SClassViewer::IsCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_None) ), NAME_None, EUserInterfaceActionType::RadioButton ); MenuBuilder.AddMenuEntry( LOCTEXT("CurrentUserDeveloperViewOption", "Current Developer"), LOCTEXT("CurrentUserDeveloperViewOptionToolTip", "Filter classes to allow classes in the current user's development folder."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::SetCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_CurrentUser), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SClassViewer::IsCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_CurrentUser) ), NAME_None, EUserInterfaceActionType::RadioButton ); MenuBuilder.AddMenuEntry( LOCTEXT("AllUsersDeveloperViewOption", "All Developers"), LOCTEXT("AllUsersDeveloperViewOptionToolTip", "Filter classes to allow classes in all users' development folders."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SClassViewer::SetCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_All), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SClassViewer::IsCurrentDeveloperViewType, EClassViewerDeveloperType::CVDT_All) ), NAME_None, EUserInterfaceActionType::RadioButton ); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); return MenuBuilder.MakeWidget(); } void SClassViewer::SetCurrentDeveloperViewType(EClassViewerDeveloperType NewType) { if (ensure((int)NewType < (int)EClassViewerDeveloperType::CVDT_Max) && NewType != GetDefault()->DeveloperFolderType) { GetMutableDefault()->DeveloperFolderType = NewType; GetMutableDefault()->PostEditChange(); } } EClassViewerDeveloperType SClassViewer::GetCurrentDeveloperViewType() const { if (!InitOptions.bAllowViewOptions) { return EClassViewerDeveloperType::CVDT_All; } return GetDefault()->DeveloperFolderType; } bool SClassViewer::IsCurrentDeveloperViewType(EClassViewerDeveloperType ViewType) const { return GetCurrentDeveloperViewType() == ViewType; } void SClassViewer::GetInternalOnlyClasses(TArray& Classes) { if (!InitOptions.bAllowViewOptions) { return; } Classes = GetDefault()->InternalOnlyClasses; } void SClassViewer::GetInternalOnlyPaths(TArray& Paths) { if (!InitOptions.bAllowViewOptions) { return; } Paths = GetDefault()->InternalOnlyPaths; } FText SClassViewer::GetClassCountText() const { const int32 NumAssets = GetNumItems(); const int32 NumSelectedAssets = GetSelectedItems().Num(); FText AssetCount = LOCTEXT("AssetCountLabelSingular", "1 item"); if (NumSelectedAssets == 0) { if (NumAssets == 1) { AssetCount = LOCTEXT("AssetCountLabelSingular", "1 item"); } else { AssetCount = FText::Format(LOCTEXT("AssetCountLabelPlural", "{0} items"), FText::AsNumber(NumAssets)); } } else { if (NumAssets == 1) { AssetCount = FText::Format(LOCTEXT("AssetCountLabelSingularPlusSelection", "1 item ({0} selected)"), FText::AsNumber(NumSelectedAssets)); } else { AssetCount = FText::Format(LOCTEXT("AssetCountLabelPluralPlusSelection", "{0} items ({1} selected)"), FText::AsNumber(NumAssets), FText::AsNumber(NumSelectedAssets)); } } return AssetCount; } 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 FAssetDragDropOp 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(MakeWeakObjectPtr(Class))); } } else if (Item->BlueprintAssetPath != NAME_None) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Pull asset data out of asset registry TArray InAssetData; InAssetData.Add(AssetRegistryModule.Get().GetAssetByObjectPath(Item->BlueprintAssetPath)); return FReply::Handled().BeginDragDrop(FAssetDragDropOp::New(InAssetData)); } } } return FReply::Unhandled(); } void SClassViewer::OnOpenBlueprintTool() { ClassViewer::Helpers::OpenBlueprintTool(RightClickBlueprint); } void SClassViewer::FindInContentBrowser() { ClassViewer::Helpers::FindInContentBrowser(RightClickBlueprint, RightClickClass); } void SClassViewer::OnFilterTextChanged( const FText& InFilterText ) { // Update the compiled filter and report any syntax error information back to the user TextFilterPtr->SetFilterText(InFilterText); SearchBox->SetError(TextFilterPtr->GetFilterErrorText()); // 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 = nullptr; if (SelectedList.Num() > 0) { FirstSelected = SelectedList[0]; Class = FirstSelected->Class.Get(); // If the class is nullptr 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; } TSharedRef SClassViewer::FillFilterEntries() { FMenuBuilder MenuBuilder(true, nullptr); 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(); return MenuBuilder.MakeWidget(); } 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 ); } } int32 SClassViewer::CountTreeItems(FClassViewerNode* Node) { if (Node == nullptr) return 0; int32 Count = 1; TArray>& ChildArray = Node->GetChildrenList(); for (int i = 0; i < ChildArray.Num(); i++) { Count += CountTreeItems(ChildArray[i].Get()); } return Count; } void SClassViewer::Populate() { bPendingSetExpansionStates = false; // If showing a class tree, we may need to save expansion states. if(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView) { 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(); bool ShowingInternalClasses = IsShowingInternalClasses(); TArray InternalClassNames; TArray InternalClasses; TArray InternalPaths; // If we aren't showing the internal classes, then we need to know what classes to consider Internal Only, so let's gather them up from the settings object. if (!ShowingInternalClasses) { GetInternalOnlyPaths(InternalPaths); GetInternalOnlyClasses(InternalClassNames); // Take the package names for the internal only classes and convert them into their UClass for (int i = 0; i < InternalClassNames.Num(); i++) { FString PackageClassName = InternalClassNames[i].ToString(); const TSharedPtr ClassNode = ClassViewer::Helpers::ClassHierarchy->FindNodeByClassName(ClassViewer::Helpers::ClassHierarchy->GetObjectRootNode(), PackageClassName); if (ClassNode.IsValid()) { InternalClasses.Add(ClassNode->Class.Get()); } } } // Based on if the list or tree is visible we create what will be displayed differently. if(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView) { // 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, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); // Check if we will restore expansion states, we will not if there is filtering happening. const bool bRestoreExpansionState = TextFilterPtr->GetFilterType() == ETextFilterExpressionType::Empty; if(InitOptions.bShowObjectRootClass) { RootTreeItems.Add(RootNode); if( bRestoreExpansionState ) { SetExpansionStatesInTree( RootNode ); } // Expand any items that pass the filter. if(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) { 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(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) { 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); } NumClasses = 0; for (int i = 0; i < RootTreeItems.Num(); i++) NumClasses += CountTreeItems(RootTreeItems[i].Get()); // 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, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); // 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); } NumClasses = 0; for (int i = 0; i < RootTreeItems.Num(); i++) NumClasses += CountTreeItems(RootTreeItems[i].Get() ); // 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); } void SClassViewer::HandleSettingChanged(FName PropertyName) { if ((PropertyName == "DisplayInternalClasses") || (PropertyName == "DeveloperFolderType") || (PropertyName == NAME_None)) // @todo: Needed if PostEditChange was called manually, for now { Refresh(); } } void SClassViewer::ToggleShowInternalClasses() { check(IsToggleShowInternalClassesAllowed()); GetMutableDefault()->DisplayInternalClasses = !GetDefault()->DisplayInternalClasses; GetMutableDefault()->PostEditChange(); } bool SClassViewer::IsToggleShowInternalClassesAllowed() const { return bCanShowInternalClasses; } bool SClassViewer::IsShowingInternalClasses() const { if (!InitOptions.bAllowViewOptions) { return true; } return IsToggleShowInternalClassesAllowed() ? GetDefault()->DisplayInternalClasses : false; } #undef LOCTEXT_NAMESPACE