Files
UnrealEngine/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp
2025-05-18 13:04:45 +08:00

2775 lines
94 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SClassViewer.h"
#include "AddToProjectConfig.h"
#include "AssetDiscoveryIndicator.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryHelpers.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetToolsModule.h"
#include "Blueprint/BlueprintSupport.h"
#include "ClassViewerFilter.h"
#include "ClassViewerNode.h"
#include "ClassViewerProjectSettings.h"
#include "Containers/ArrayView.h"
#include "Containers/UnrealString.h"
#include "ContentBrowserDataDragDropOp.h"
#include "ContentBrowserModule.h"
#include "CoreGlobals.h"
#include "CoreTypes.h"
#include "Dialogs/Dialogs.h"
#include "DragAndDrop/ClassDragDropOp.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "EditorClassUtils.h"
#include "EditorDirectories.h"
#include "EditorWidgetsModule.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandInfo.h"
#include "Framework/Docking/TabManager.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/SlateDelegates.h"
#include "Framework/Views/ITypedTableView.h"
#include "GameProjectGenerationModule.h"
#include "HAL/PlatformMisc.h"
#include "IAssetTools.h"
#include "IContentBrowserSingleton.h"
#include "IDocumentation.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Internationalization/Internationalization.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Layout/Children.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Layout/WidgetPath.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Logging/MessageLog.h"
#include "Math/Color.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/CString.h"
#include "Misc/FeedbackContext.h"
#include "Misc/MessageDialog.h"
#include "Misc/PackageName.h"
#include "Misc/Paths.h"
#include "Misc/TextFilterExpressionEvaluator.h"
#include "Modules/ModuleManager.h"
#include "PackageTools.h"
#include "PropertyHandle.h"
#include "SListViewSelectorDropdownMenu.h"
#include "SlateOptMacros.h"
#include "SlotBase.h"
#include "SourceCodeNavigation.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateColor.h"
#include "Styling/SlateIconFinder.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Templates/Casts.h"
#include "Textures/SlateIcon.h"
#include "Trace/Detail/Channel.h"
#include "Types/SlateStructs.h"
#include "UObject/Class.h"
#include "UObject/CoreRedirects.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Package.h"
#include "UObject/SoftObjectPath.h"
#include "UObject/TopLevelAssetPath.h"
#include "UObject/UObjectBase.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UObjectIterator.h"
#include "UObject/UnrealNames.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "UnloadedBlueprintData.h"
#include "UnrealEdGlobals.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/SExpanderArrow.h"
#include "Widgets/Views/SHeaderRow.h"
#include "Widgets/Views/STableRow.h"
class FUICommandList;
class ITableRow;
class SWidget;
struct FGeometry;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "SClassViewer"
//////////////////////////////////////////////////////////////
struct FClassViewerNodeNameLess
{
FClassViewerNodeNameLess(EClassViewerNameTypeToDisplay NameTypeToDisplay = EClassViewerNameTypeToDisplay::ClassName) : NameTypeToDisplay(NameTypeToDisplay) {}
bool operator()(TSharedPtr<FClassViewerNode> A, TSharedPtr<FClassViewerNode> B) const
{
check(A.IsValid());
check(B.IsValid());
// The display name only matters when NameTypeToDisplay == DisplayName. For NameTypeToDisplay == Dynamic,
// the class name is displayed first with the display name in parentheses, but only if it differs from the display name.
bool bUseDisplayName = NameTypeToDisplay == EClassViewerNameTypeToDisplay::DisplayName;
const FString& NameA = *A->GetClassName(bUseDisplayName).Get();
const FString& NameB = *B->GetClassName(bUseDisplayName).Get();
return NameA.Compare(NameB, ESearchCase::IgnoreCase) < 0;
}
EClassViewerNameTypeToDisplay NameTypeToDisplay;
};
class FClassHierarchy
{
public:
FClassHierarchy();
~FClassHierarchy();
/** Populates the class hierarchy tree, pulling in all the loaded and unloaded classes. */
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, FTopLevelAssetPath 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(FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint );
/** 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, FTopLevelAssetPath InGeneratedClassPath);
private:
/** Adds UClass information to The node in InOutClassPathToNode for every loaded class, creating the node if it does not exist. Does not filter classes.
* @param OutRootNode Out parameter that receives the node pointer for the root UClass.
* @param InOutClassPathToNode Map containing all nodes.
*/
void CreateNodesForLoadedClasses( TSharedPtr<FClassViewerNode>& OutRootNode, TMap<FTopLevelAssetPath, TSharedPtr< FClassViewerNode >>& InOutClassPathToNode );
/** Called when reload has finished */
void OnReloadComplete( EReloadCompleteReason Reason );
/**
* Makes or updates a class viewer node with the 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.
* @parma InClassPath The class path
*/
void CreateOrUpdateUnloadedClassNode(TSharedPtr<FClassViewerNode>& InOutClassViewerNode, const FAssetData& InAssetData, FTopLevelAssetPath InClassPath);
/**
* 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);
/** Sets the fields calculated from the in-memory UClass on the given Node, if not already set. */
void SetClassFields(TSharedPtr<FClassViewerNode>& InOutClassNode, UClass& Class);
/**
* 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, FTopLevelAssetPath 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);
/** Apply CoreRedirects on TopLevelAssetPath. */
void FixupClassCoreRedirects(FTopLevelAssetPath& InOutClassPath);
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 UBlueprint* GetBlueprint( UClass* InClass );
static void UpdateClassInNode(FTopLevelAssetPath 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 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);
}
/** 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 InOutRootNode The node that this function will add the children of to the tree.
* @param InRootClassIndex The index of the root node.
* @param InClassFilter The class filter to use to filter nodes.
* @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses.
*
* @return Returns true if the child passed the filter.
*/
static bool AddChildren_Tree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr< FClassViewerNode >& InOriginalRootNode,
const TSharedPtr<FClassViewerFilter>& InClassFilter, const FClassViewerInitializationOptions& InInitOptions)
{
bool bCheckTextFilter = true;
InOutRootNode->bPassesFilter = InClassFilter->IsNodeAllowed(InInitOptions, InOutRootNode.ToSharedRef(), bCheckTextFilter);
bool bReturnPassesFilter = InOutRootNode->bPassesFilter;
bCheckTextFilter = false;
InOutRootNode->bPassesFilterRegardlessTextFilter = bReturnPassesFilter || InClassFilter->IsNodeAllowed(InInitOptions, InOutRootNode.ToSharedRef(), bCheckTextFilter);
TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList();
for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++)
{
TSharedPtr< FClassViewerNode > NewNode = MakeShared<FClassViewerNode>( *ChildList[ChildIdx].Get() );
const bool bChildrenPassesFilter = AddChildren_Tree(NewNode, ChildList[ChildIdx], InClassFilter, InInitOptions);
bReturnPassesFilter |= bChildrenPassesFilter;
if (bChildrenPassesFilter)
{
InOutRootNode->AddChild(NewNode);
}
}
if (bReturnPassesFilter)
{
InOutRootNode->GetChildrenList().Sort(FClassViewerNodeNameLess(InInitOptions.NameTypeToDisplay));
}
return bReturnPassesFilter;
}
/** Builds the class tree.
* @param InOutRootNode The node to root the tree to.
* @param InClassFilter The class filter to use to filter nodes.
* @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses.
*
* @return A fully built tree.
*/
static void GetClassTree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr<FClassViewerFilter>& InClassFilter,
const FClassViewerInitializationOptions& InInitOptions)
{
const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode();
// Duplicate the node, it will have no children.
InOutRootNode = MakeShared<FClassViewerNode>(*ObjectClassRoot);
if (InInitOptions.bIsActorsOnly)
{
for (int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++)
{
TSharedPtr<FClassViewerNode> ChildNode = MakeShared<FClassViewerNode>(*ObjectClassRoot->GetChildrenList()[ClassIdx].Get());
if (AddChildren_Tree(ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InClassFilter, InInitOptions))
{
InOutRootNode->AddChild(ChildNode);
}
}
}
else
{
AddChildren_Tree(InOutRootNode, ObjectClassRoot, InClassFilter, InInitOptions);
}
}
/** Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms.
* @param InOutRootNode The node that this function will add the children of to the tree.
* @param InRootClassIndex The index of the root node.
* @param InClassFilter The class filter to use to filter nodes.
* @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses.
*
* @return Returns true if the child passed the filter.
*/
static void AddChildren_List(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr< FClassViewerNode >& InOriginalRootNode,
const TSharedPtr< FClassViewerFilter >& InClassFilter, const FClassViewerInitializationOptions& InInitOptions)
{
const bool bCheckTextFilter = true;
if (InClassFilter->IsNodeAllowed(InInitOptions, InOriginalRootNode.ToSharedRef(), bCheckTextFilter))
{
TSharedPtr< FClassViewerNode > NewNode = MakeShared<FClassViewerNode>(*InOriginalRootNode.Get());
NewNode->bPassesFilter = true;
NewNode->bPassesFilterRegardlessTextFilter = true;
NewNode->PropertyHandle = InOriginalRootNode->PropertyHandle;
InOutNodeList.Add(NewNode);
}
for(const TSharedPtr<FClassViewerNode>& ChildNode : InOriginalRootNode->GetChildrenList())
{
FClassViewerInitializationOptions TempOptions = InInitOptions;
// set bOnlyActors to false so that anything below Actor is added
TempOptions.bIsActorsOnly = false;
AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions);
}
}
/** Builds the class list.
* @param InOutNodeList The list to add all the nodes to.
* @param InClassFilter The class filter to use to filter nodes.
* @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses.
*
* @return A fully built list.
*/
static void GetClassList(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr<FClassViewerFilter>& InClassFilter,
const FClassViewerInitializationOptions& InInitOptions)
{
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)
{
const bool bCheckTextFilter = true;
if (InClassFilter->IsNodeAllowed(InInitOptions, ObjectClassRoot.ToSharedRef(), bCheckTextFilter))
{
TSharedPtr< FClassViewerNode > NewNode = MakeShared<FClassViewerNode>(*ObjectClassRoot.Get());
NewNode->bPassesFilter = true;
NewNode->bPassesFilterRegardlessTextFilter = true;
NewNode->PropertyHandle = InInitOptions.PropertyHandle;
InOutNodeList.Add(NewNode);
}
}
for(const TSharedPtr<FClassViewerNode>& ChildNode : ObjectClassRoot->GetChildrenList())
{
AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions);
}
}
/** 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<UBlueprint>(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<UClass> InClass, bool& bInOutIsBlueprintBase, bool& bInOutHasBlueprint )
{
if (UClass* Class = InClass.Get())
{
bInOutIsBlueprintBase = CanCreateBlueprintOfClass_IgnoreDeprecation( Class );
bInOutHasBlueprint = Class->ClassGeneratedBy != nullptr;
}
else
{
bInOutIsBlueprintBase = false;
bInOutHasBlueprint = 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( *PackageName);
check(Package);
// Handle fully loading packages before creating new objects.
TArray<UPackage*> 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, Package))
{
// Create and init a new Blueprint
UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(InCreationClass, Package, BPName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("ClassViewer"));
if(NewBP)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->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<FAssetToolsModule>("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<FContentBrowserModule>("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<UClass>(nullptr, *InOutClassNode->ClassPath.ToString());
GWarn->EndSlowTask();
if (Class)
{
InOutClassNode->Blueprint = Cast<UBlueprint>(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::FromString(InOutClassNode->ClassPath.ToString()));
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 )
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->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<UObject*> Objects;
Objects.Add(InBlueprint);
GEditor->SyncBrowserToObjects(Objects);
}
else if (InClass)
{
TArray<UObject*> 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(FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint )
{
ClassHierarchy->UpdateClassInNode(InGeneratedClassPath, InNewClass, InNewBluePrint );
}
static TSharedRef<SWidget> 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<FText>::FGetter DynamicTooltipGetter;
DynamicTooltipGetter.BindStatic(&ClassViewer::Helpers::GetCreateBlueprintTooltip, Class);
TAttribute<FText> DynamicTooltipAttribute = TAttribute<FText>::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<FClassViewerNode> );
/** The item used for visualizing the class in the tree. */
class SClassItem : public STableRow< TSharedPtr<FString> >
{
public:
SLATE_BEGIN_ARGS( SClassItem )
: _ClassName()
, _bIsPlaceable(false)
, _bIsInClassViewer( true )
, _bDynamicClassLoading( true )
, _HighlightText()
, _Font(FAppStyle::Get().GetFontStyle("NormalFont"))
{}
/** The classname this item contains. */
SLATE_ARGUMENT( TSharedPtr<FString>, 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 font this item will use. */
SLATE_ARGUMENT( FSlateFontInfo, Font )
/** The node this item is associated with. */
SLATE_ARGUMENT( TSharedPtr<FClassViewerNode>, 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<STableViewBase>& 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<SToolTip> GetToolTip(TSharedPtr<FClassViewerNode> AssociatedNode)
{
TSharedPtr<SToolTip> 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.IsNull())
{
ToolTip = SNew(SToolTip).Text(FText::FromString(AssociatedNode->ClassPath.ToString()));
}
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()
.VAlign(VAlign_Center)
.Padding( 0.0f, 2.0f, 6.0f, 2.0f )
[
SNew( SImage )
.Image( ClassIcon )
.Visibility( ClassIcon != FAppStyle::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()) )
.Font(InArgs._Font)
.HighlightText(InArgs._HighlightText)
.ColorAndOpacity(FSlateColor::UseForeground())
.ToolTip(Local::GetToolTip(AssociatedNode))
.IsEnabled(!bIsRestricted)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding( 0.0f, 1.0f, 6.0f, 1.0f )
[
SNew( SComboButton )
.ContentPadding(FMargin(2.0f))
.Visibility(this, &SClassItem::ShowOptions)
.OnGetMenuContent(this, &SClassItem::GenerateDropDown)
]
];
UE_LOG(LogEditorClassViewer, VeryVerbose, TEXT("CLASS [%s]"), **ClassName);
STableRow< TSharedPtr<FString> >::ConstructInternal(
STableRow::FArguments()
.ShowSelection(true)
.OnDragDetected(InArgs._OnDragDetected),
InOwnerTableView
);
}
private:
virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override
{
// 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<SWidget> 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;
}
private:
/** The class name for which this item is associated with. */
TSharedPtr<FString> 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 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.
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle = AssetRegistry.OnFilesLoaded().AddStatic( ClassViewer::Helpers::RequestPopulateClassHierarchy );
AssetRegistry.OnAssetAdded().AddRaw( this, &FClassHierarchy::AddAsset);
AssetRegistry.OnAssetRemoved().AddRaw( this, &FClassHierarchy::RemoveAsset );
// Register to have Populate called when doing a Reload.
FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw( this, &FClassHierarchy::OnReloadComplete );
// 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 (FAssetRegistryModule* AssetRegistryModule = FModuleManager::Get().GetModulePtr<FAssetRegistryModule>(AssetRegistryConstants::ModuleName))
{
IAssetRegistry* AssetRegistry = AssetRegistryModule->TryGet();
if (AssetRegistry)
{
AssetRegistry->OnFilesLoaded().Remove(OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle);
AssetRegistry->OnAssetAdded().RemoveAll(this);
AssetRegistry->OnAssetRemoved().RemoveAll(this);
}
// Unregister to have Populate called when doing a Reload.
FCoreUObjectDelegates::ReloadCompleteDelegate.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);
}
void FClassHierarchy::OnReloadComplete(EReloadCompleteReason Reason)
{
ClassViewer::Helpers::RequestPopulateClassHierarchy();
}
void FClassHierarchy::CreateNodesForLoadedClasses(TSharedPtr<FClassViewerNode>& OutRootNode, TMap<FTopLevelAssetPath, TSharedPtr< FClassViewerNode >>& InOutClassPathToNode)
{
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
UClass* CurrentClass = *ClassIt;
// Ignore deprecated and temporary trash classes.
if (CurrentClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists | CLASS_Hidden) ||
FBlueprintSupport::IsClassPlaceholder(CurrentClass) ||
FKismetEditorUtilities::IsClassABlueprintSkeleton(CurrentClass))
{
continue;
}
TSharedPtr<FClassViewerNode>& Node = InOutClassPathToNode.FindOrAdd(CurrentClass->GetClassPathName());
if (!Node)
{
Node = MakeShared<FClassViewerNode>(CurrentClass->GetName(), CurrentClass->GetDisplayNameText().ToString());
}
SetClassFields(Node, *CurrentClass);
}
TSharedPtr<FClassViewerNode>* ExistingRoot = InOutClassPathToNode.Find(UObject::StaticClass()->GetClassPathName());
check(ExistingRoot && ExistingRoot->IsValid());
OutRootNode = *ExistingRoot;
}
TSharedPtr< FClassViewerNode > FClassHierarchy::FindParent(const TSharedPtr< FClassViewerNode >& InRootNode, FTopLevelAssetPath 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::FindNodeByGeneratedClassPath(const TSharedPtr< FClassViewerNode >& InRootNode, FTopLevelAssetPath 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(FTopLevelAssetPath 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, FTopLevelAssetPath InClassPath)
{
FixupClassCoreRedirects(InClassPath);
TFunction<bool(const TSharedPtr<FClassViewerNode>&)> FindAndRemoveNodeRecursive;
FindAndRemoveNodeRecursive = [&InClassPath, &FindAndRemoveNodeRecursive](const TSharedPtr<FClassViewerNode>& InRootNode)
{
// Search the children recursively, one of them might have the parent.
TArray<TSharedPtr<FClassViewerNode>>& ChildrenList = InRootNode->GetChildrenList();
for (int32 ChildClassIndex = 0; ChildClassIndex < ChildrenList.Num(); ChildClassIndex++)
{
if (ChildrenList[ChildClassIndex]->ClassPath == InClassPath)
{
ChildrenList.RemoveAt(ChildClassIndex);
return true;
}
// Check the child, then check the return to see if it is valid. If it is valid, end the recursion.
if (FindAndRemoveNodeRecursive(ChildrenList[ChildClassIndex]))
{
return true;
}
}
return false;
};
return FindAndRemoveNodeRecursive(InRootNode);
}
void FClassHierarchy::RemoveAsset(const FAssetData& InRemovedAssetData)
{
// BPGCs can be missing if it was already deleted prior to the notification being sent.
// Let's try to reconstruct the generated class path from the BP object path.
bool bGenerateClassPathIfMissing = true;
const FTopLevelAssetPath ClassPath = FEditorClassUtils::GetClassPathNameFromAsset(InRemovedAssetData, bGenerateClassPathIfMissing);
if (!ClassPath.IsNull() && FindAndRemoveNodeByClassPath(ObjectClassRoot, ClassPath))
{
// All viewers must refresh.
ClassViewer::Helpers::RefreshAll();
}
}
void FClassHierarchy::AddAsset(const FAssetData& InAddedAssetData)
{
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
if (AssetRegistry.IsLoadingAssets())
{
return;
}
const FTopLevelAssetPath ClassPath = FEditorClassUtils::GetClassPathNameFromAsset(InAddedAssetData);
// 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 (!ClassPath.IsNull() && !FindNodeByGeneratedClassPath(ObjectClassRoot, ClassPath).IsValid())
{
TSharedPtr<FClassViewerNode> NewNode;
CreateOrUpdateUnloadedClassNode(NewNode, InAddedAssetData, ClassPath);
// 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<UClass>(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++)
{
// 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(FClassViewerNodeNameLess());
}
void FClassHierarchy::FindClass(TSharedPtr<FClassViewerNode> InOutClassNode)
{
UClass* Class = FindObject<UClass>(nullptr, *InOutClassNode->ClassPath.ToString());
if (Class)
{
SetClassFields(InOutClassNode, *Class);
}
}
void FClassHierarchy::SetClassFields(TSharedPtr<FClassViewerNode>& InOutClassNode, UClass& Class)
{
if (InOutClassNode->Class.Get())
{
// Already set
return;
}
// Fields that can als be set from FAssetData
if (InOutClassNode->ClassPath.IsNull())
{
InOutClassNode->ClassPath = Class.GetClassPathName();
}
if (InOutClassNode->ParentClassPath.IsNull())
{
if (Class.GetSuperClass())
{
InOutClassNode->ParentClassPath = Class.GetSuperClass()->GetClassPathName();
}
}
// UClass-specific fields
InOutClassNode->Blueprint = Cast<UBlueprint>(Class.ClassGeneratedBy);
InOutClassNode->Class = &Class;
}
void FClassHierarchy::FixupClassCoreRedirects(FTopLevelAssetPath& InOutClassPath)
{
const FCoreRedirectObjectName OldName = FCoreRedirectObjectName(InOutClassPath);
const FCoreRedirectObjectName NewName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Class, OldName);
if (OldName != NewName)
{
const FString OldClassPath = InOutClassPath.ToString();
// Only do the fixup if the old class name isn't in memory.
const UClass* FoundOldClass = UClass::TryFindTypeSlow<UClass>(OldClassPath);
const FTopLevelAssetPath NewClassPath(NewName.ToString());
if (!FoundOldClass || FoundOldClass->GetClassPathName() == NewClassPath)
{
InOutClassPath = NewClassPath;
}
}
}
void FClassHierarchy::CreateOrUpdateUnloadedClassNode(TSharedPtr<FClassViewerNode>& InOutClassViewerNode, const FAssetData& InAssetData, FTopLevelAssetPath InClassPath)
{
if (!InOutClassViewerNode)
{
const FString ClassName = InAssetData.AssetName.ToString();
FString ClassDisplayName = InAssetData.GetTagValueRef<FString>(FBlueprintTags::BlueprintDisplayName);
if (ClassDisplayName.IsEmpty())
{
ClassDisplayName = ClassName;
}
InOutClassViewerNode = MakeShared<FClassViewerNode>(ClassName, ClassDisplayName);
}
if (InOutClassViewerNode->UnloadedBlueprintData.IsValid())
{
// Already set
return;
}
// Fields that can also be set from UClass*
InOutClassViewerNode->ClassPath = InClassPath;
if (InOutClassViewerNode->ParentClassPath.IsNull())
{
FString ParentClassPathString;
if (InAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassPathString))
{
FTopLevelAssetPath ParentClassPath(FTopLevelAssetPath(*FPackageName::ExportTextPathToObjectPath(ParentClassPathString)));
FixupClassCoreRedirects(ParentClassPath);
InOutClassViewerNode->ParentClassPath = ParentClassPath;
}
}
// Blueprint-specific fields
InOutClassViewerNode->BlueprintAssetPath = InAssetData.GetSoftObjectPath();
// It is an unloaded blueprint, so we need to create the structure that will hold the data.
TSharedPtr<FUnloadedBlueprintData> UnloadedBlueprintData = MakeShareable(new FUnloadedBlueprintData(InOutClassViewerNode));
InOutClassViewerNode->UnloadedBlueprintData = UnloadedBlueprintData;
const bool bNormalBlueprintType = InAssetData.GetTagValueRef<FString>(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal");
InOutClassViewerNode->UnloadedBlueprintData->SetNormalBlueprintType(bNormalBlueprintType);
// Get the class flags.
const uint32 ClassFlags = InAssetData.GetTagValueRef<uint32>(FBlueprintTags::ClassFlags);
InOutClassViewerNode->UnloadedBlueprintData->SetClassFlags(ClassFlags);
// Get interface class paths.
TArray<FString> ImplementedInterfaces;
FEditorClassUtils::GetImplementedInterfaceClassPathsFromAsset(InAssetData, ImplementedInterfaces);
for (const FString& InterfacePath : ImplementedInterfaces)
{
UnloadedBlueprintData->AddImplementedInterface(InterfacePath);
}
}
void FClassHierarchy::PopulateClassHierarchy()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FClassHierarchy::PopulateClassHierarchy);
// Fetch all classes from AssetRegistry blueprint data (which covers unloaded classes), and in-memory UClasses.
// Create a node for each one with unioned data from the AssetRegistry or UClass for that class.
// Set parent/child pointers to create a tree, and store this tree in this->ObjectClassRoot
TMap<FTopLevelAssetPath, TSharedPtr<FClassViewerNode>> ClassPathToNode;
// Create a node for every Blueprint class listed in the AssetRegistry and set the Blueprint fields
// Retrieve all blueprint classes
{
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
FString ClassPathString;
TArray<FAssetData> Assets;
AssetRegistry.GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), Assets, /*bSearchSubClasses=*/true);
for (const FAssetData& AssetData : Assets)
{
FTopLevelAssetPath ClassPath = FEditorClassUtils::GetClassPathNameFromAssetTag(AssetData);
if (!ClassPath.IsNull())
{
FixupClassCoreRedirects(ClassPath);
TSharedPtr<FClassViewerNode>& Node = ClassPathToNode.FindOrAdd(ClassPath);
CreateOrUpdateUnloadedClassNode(Node, AssetData, ClassPath);
}
else
{
UE_LOG(LogEditorClassViewer, Warning, TEXT("AssetRegistry Blueprint %s is missing tag value for %s. Blueprint will not be available to ClassViewer when unloaded."),
*AssetData.GetObjectPathString(), *FBlueprintTags::GeneratedClassPath.ToString());
}
}
Assets.Reset();
AssetRegistry.GetAssetsByClass(UBlueprintGeneratedClass::StaticClass()->GetClassPathName(), Assets, /*bSearchSubClasses=*/true);
for (const FAssetData& AssetData : Assets)
{
FTopLevelAssetPath ClassPathNameFromAssetPath = AssetData.GetSoftObjectPath().GetAssetPath();
FixupClassCoreRedirects(ClassPathNameFromAssetPath);
TSharedPtr<FClassViewerNode>& Node = ClassPathToNode.FindOrAdd(ClassPathNameFromAssetPath);
CreateOrUpdateUnloadedClassNode(Node, AssetData, ClassPathNameFromAssetPath);
}
}
// FindOrCreate a node for every loaded UClass, and set the UClass fields
CreateNodesForLoadedClasses(ObjectClassRoot, ClassPathToNode);
// Set the parent and child pointers
for (TPair<FTopLevelAssetPath, TSharedPtr<FClassViewerNode>>& KVPair : ClassPathToNode)
{
TSharedPtr<FClassViewerNode>& Node = KVPair.Value;
if (Node == ObjectClassRoot)
{
// No parent expected for the root class
continue;
}
TSharedPtr<FClassViewerNode>* ParentNodePtr = nullptr;
if (!Node->ParentClassPath.IsNull())
{
ParentNodePtr = ClassPathToNode.Find(Node->ParentClassPath);
if(!ParentNodePtr)
{
FSoftObjectPath SoftParentClass(Node->ParentClassPath);
UAssetRegistryHelpers::FixupRedirectedAssetPath(SoftParentClass);
ParentNodePtr = ClassPathToNode.Find(FTopLevelAssetPath(SoftParentClass.ToString()));
}
}
if (!ParentNodePtr)
{
UE_LOG(LogEditorClassViewer, Warning, TEXT("Class %s has parent %s, but this parent is not found. The Class will not be shown in ClassViewer."),
*KVPair.Key.ToString(), *Node->ParentClassPath.ToString());
continue;
}
TSharedPtr<FClassViewerNode>& ParentNode = *ParentNodePtr;
check(ParentNode);
ParentNode->AddChild(Node);
}
// 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;
// Listen for when view settings are changed
UClassViewerSettings::OnSettingChanged().AddSP(this, &SClassViewer::HandleSettingChanged);
InitOptions = InInitOptions;
OnClassPicked = InArgs._OnClassPickedDelegate;
bSaveExpansionStates = true;
bPendingSetExpansionStates = false;
ClassFilter = MakeShareable(new FClassViewerFilter(InitOptions));
bEnableClassDynamicLoading = InInitOptions.bEnableClassDynamicLoading;
EVisibility HeaderVisibility = (this->InitOptions.Mode == EClassViewerMode::ClassBrowsing)? EVisibility::Visible : EVisibility::Collapsed;
// 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;
}
}
// Clear out the current set of custom filter options.
CustomClassFilterOptions.Empty(InitOptions.ClassFilters.Num());
// Gather additional filter options from any custom filters.
TArray<TSharedRef<FClassViewerFilterOption>> FilterOptions;
for (TSharedRef<IClassViewerFilter> CustomFilter : InitOptions.ClassFilters)
{
// Append this filter's options to the current set.
CustomFilter->GetFilterOptions(FilterOptions);
CustomClassFilterOptions.Append(FilterOptions);
// Reset the temp array for the next pass.
FilterOptions.Reset();
}
TSharedRef<SWidget> FiltersWidget = SNullWidget::NullWidget;
// Build the top menu
if(InitOptions.Mode == EClassViewerMode::ClassBrowsing)
{
FiltersWidget =
SNew(SComboButton)
.ComboButtonStyle(FAppStyle::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(FAppStyle::Get(), "GenericFilters.TextStyle")
.Font(FAppStyle::Get().GetFontStyle("FontAwesome.9"))
.Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0, 0, 0)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "GenericFilters.TextStyle")
.Text(LOCTEXT("Filters", "Filters"))
]
];
}
// Create the asset discovery indicator
FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked<FEditorWidgetsModule>("EditorWidgets");
TSharedRef<SWidget> AssetDiscoveryIndicator = EditorWidgetsModule.CreateAssetDiscoveryIndicator(EAssetDiscoveryIndicatorScaleMode::Scale_Vertical);
FOnContextMenuOpening OnContextMenuOpening;
if ( InitOptions.Mode == EClassViewerMode::ClassBrowsing )
{
OnContextMenuOpening = FOnContextMenuOpening::CreateSP(this, &SClassViewer::BuildMenuWidget);
}
SAssignNew(ClassList, SListView<TSharedPtr< FClassViewerNode > >)
.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)
.HeaderRow
(
SNew(SHeaderRow)
.Visibility(EVisibility::Collapsed)
+ SHeaderRow::Column(TEXT("Class"))
.DefaultLabel(NSLOCTEXT("ClassViewer", "Class", "Class"))
);
SAssignNew(ClassTree, STreeView<TSharedPtr< FClassViewerNode > >)
.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)
.HeaderRow
(
SNew(SHeaderRow)
.Visibility(EVisibility::Collapsed)
+ SHeaderRow::Column(TEXT("Class"))
.DefaultLabel(NSLOCTEXT("ClassViewer", "Class", "Class"))
);
TSharedRef<STreeView<TSharedPtr< FClassViewerNode > > > ClassTreeView = ClassTree.ToSharedRef();
TSharedRef<SListView<TSharedPtr< FClassViewerNode > > > ClassListView = ClassList.ToSharedRef();
bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false;
// 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(FAppStyle::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(FAppStyle::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, 6.0f, 2.0f)
[
SAssignNew(SearchBox, SSearchBox)
.OnTextChanged( this, &SClassViewer::OnFilterTextChanged )
.OnTextCommitted( this, &SClassViewer::OnFilterTextCommitted )
]
// View mode combo button
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 2.0f)
[
SAssignNew(ViewOptionsComboButton, SComboButton)
.ContentPadding(0)
.ForegroundColor(FSlateColor::UseForeground())
.ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton")
.HasDownArrow(false)
.OnGetMenuContent(this, &SClassViewer::GetViewButtonContent)
.ButtonContent()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.Settings"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
]
+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()
.VAlign(VAlign_Center)
.Padding(4.0f)
[
SNew(STextBlock)
.Text(this, &SClassViewer::GetClassCountText)
]
]
];
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<TSharedPtr<FClassViewerNode>>, 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<SWidget> 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<FClassViewerNode> InParent, TArray< TSharedPtr< FClassViewerNode > >& OutChildren )
{
// Simply return the children, it's already setup.
OutChildren = InParent->GetChildrenList();
}
void SClassViewer::OnClassViewerSelectionChanged( TSharedPtr<FClassViewerNode> 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 );
// Populate the tree/list so any changes to previously unloaded classes will be reflected.
Refresh();
}
// Check if the item passes the filter
if ( ( Item->Class.IsValid() || !Class ))
{
// Parent items might be displayed but filtered out by bPassesFilter, thus bPassesFilterRegardlessTextFilter makes sure to keep them selectable.
// In addition, Item->bPassesFilter would be redundant here as bPassesFilterRegardlessTextFilter = true if bPassesFilter = true
if (Item->bPassesFilterRegardlessTextFilter || Item->bPassesFilter)
{
OnClassPicked.ExecuteIfBound( Item->Class.Get() );
}
else
{
OnClassPicked.ExecuteIfBound( nullptr );
}
}
}
}
void SClassViewer::OnClassViewerExpansionChanged(TSharedPtr<FClassViewerNode> 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<FClassViewerNode> 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;
// If the item passed the filter, it may be from a match with hidden class name strings, update the search box text to retain highlighting of valid matching text
FText SearchBoxTextForHighlight = SearchBox->GetText();
TSharedPtr<FString> ClassNameDisplay = Item->GetClassName(InitOptions.NameTypeToDisplay);
if (!SearchBoxTextForHighlight.IsEmpty() && (ClassNameDisplay->Find(*SearchBoxTextForHighlight.ToString()) == INDEX_NONE))
{
SearchBoxTextForHighlight = FText::FromString(UObjectBase::RemoveClassPrefix(*SearchBoxTextForHighlight.ToString()));
}
TSharedRef< SClassItem > ReturnRow = SNew(SClassItem, OwnerTable)
.ClassName(ClassNameDisplay)
.bIsPlaceable(Item->IsClassPlaceable())
.HighlightText(SearchBoxTextForHighlight)
.Font(Item->IsClassPlaceable()? FCoreStyle::Get().GetFontStyle("NormalFontItalic") : FCoreStyle::Get().GetFontStyle("NormalFont"))
.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;
}
TSharedRef<SWidget> 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
);
for (const TSharedRef<FClassViewerFilterOption>& FilterOption : CustomClassFilterOptions)
{
MenuBuilder.AddMenuEntry(
FilterOption->LabelText,
FilterOption->ToolTipText,
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &SClassViewer::ToggleCustomFilterOption, FilterOption),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &SClassViewer::IsCustomFilterOptionEnabled, FilterOption)
),
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();
}
void SClassViewer::SetCurrentDeveloperViewType(EClassViewerDeveloperType NewType)
{
if (ensure((int)NewType < (int)EClassViewerDeveloperType::CVDT_Max) && NewType != GetDefault<UClassViewerSettings>()->DeveloperFolderType)
{
GetMutableDefault<UClassViewerSettings>()->DeveloperFolderType = NewType;
GetMutableDefault<UClassViewerSettings>()->PostEditChange();
}
}
EClassViewerDeveloperType SClassViewer::GetCurrentDeveloperViewType() const
{
if (!InitOptions.bAllowViewOptions)
{
return EClassViewerDeveloperType::CVDT_All;
}
return GetDefault<UClassViewerSettings>()->DeveloperFolderType;
}
bool SClassViewer::IsCurrentDeveloperViewType(EClassViewerDeveloperType ViewType) const
{
return GetCurrentDeveloperViewType() == ViewType;
}
void SClassViewer::GetInternalOnlyClasses(TArray<FSoftClassPath>& Classes)
{
if (!InitOptions.bAllowViewOptions)
{
return;
}
Classes = GetDefault<UClassViewerProjectSettings>()->InternalOnlyClasses;
}
void SClassViewer::GetInternalOnlyPaths(TArray<FDirectoryPath>& Paths)
{
if (!InitOptions.bAllowViewOptions)
{
return;
}
Paths = GetDefault<UClassViewerProjectSettings>()->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() )
{
const FAssetData AssetData(Item->Blueprint.Get());
return FReply::Handled().BeginDragDrop(FContentBrowserDataDragDropOp::Legacy_New(MakeArrayView(&AssetData, 1)));
}
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.IsNull())
{
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
// Pull asset data out of asset registry
const FAssetData AssetData = AssetRegistry.GetAssetByObjectPath(Item->BlueprintAssetPath);
return FReply::Handled().BeginDragDrop(FContentBrowserDataDragDropOp::Legacy_New(MakeArrayView(&AssetData, 1)));
}
}
}
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
ClassFilter->TextFilter->SetFilterText(InFilterText);
SearchBox->SetError(ClassFilter->TextFilter->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()
{
InitOptions.bIsActorsOnly = !InitOptions.bIsActorsOnly;
// "Placeable Only" cannot be true when "Actors Only" is false.
if (!InitOptions.bIsActorsOnly)
{
InitOptions.bIsPlaceableOnly = false;
}
Refresh();
}
bool SClassViewer::MenuActorsOnly_IsChecked() const
{
return InitOptions.bIsActorsOnly;
}
void SClassViewer::MenuPlaceableOnly_Execute()
{
InitOptions.bIsPlaceableOnly = !InitOptions.bIsPlaceableOnly;
// "Actors Only" must be true when "Placeable Only" is true.
if (InitOptions.bIsPlaceableOnly)
{
InitOptions.bIsActorsOnly = true;
}
Refresh();
}
bool SClassViewer::MenuPlaceableOnly_IsChecked() const
{
return InitOptions.bIsPlaceableOnly;
}
void SClassViewer::MenuBlueprintBasesOnly_Execute()
{
InitOptions.bIsBlueprintBaseOnly = !InitOptions.bIsBlueprintBaseOnly;
Refresh();
}
bool SClassViewer::MenuBlueprintBasesOnly_IsChecked() const
{
return InitOptions.bIsBlueprintBaseOnly;
}
TSharedRef<SWidget> 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<FClassViewerNode> 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<FClassViewerNode> 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<FClassViewerNode> 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<TSharedPtr<FClassViewerNode>>& ChildArray = Node->GetChildrenList();
for (int32 i = 0; i < ChildArray.Num(); i++)
{
Count += CountTreeItems(ChildArray[i].Get());
}
return Count;
}
void SClassViewer::Populate()
{
TArray<FTopLevelAssetPath> PreviousSelection;
{
TArray<TSharedPtr<FClassViewerNode>> SelectedItems = GetSelectedItems();
if (SelectedItems.Num() > 0)
{
for (TSharedPtr<FClassViewerNode>& Node : SelectedItems)
{
if (Node.IsValid())
{
PreviousSelection.Add(Node->ClassPath);
}
}
}
}
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();
TArray<FSoftClassPath> InternalClassNames;
// 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 (!IsShowingInternalClasses())
{
GetInternalOnlyPaths(ClassFilter->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++)
{
FTopLevelAssetPath PackageClassName(InternalClassNames[i].ToString());
const TSharedPtr<FClassViewerNode> ClassNode = ClassViewer::Helpers::ClassHierarchy->FindNodeByGeneratedClassPath(ClassViewer::Helpers::ClassHierarchy->GetObjectRootNode(), PackageClassName);
if (ClassNode.IsValid())
{
ClassFilter->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<FClassViewerNode> RootNode;
// Get the class tree, passing in certain filter options.
ClassViewer::Helpers::GetClassTree(RootNode, ClassFilter, InitOptions);
// Check if we will restore expansion states, we will not if there is filtering happening.
const bool bRestoreExpansionState = ClassFilter->TextFilter->GetFilterType() == ETextFilterExpressionType::Empty;
if(InitOptions.bShowObjectRootClass)
{
RootTreeItems.Add(RootNode);
if( bRestoreExpansionState )
{
SetExpansionStatesInTree( RootNode );
}
// Expand any items that pass the filter.
if(ClassFilter->TextFilter->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(ClassFilter->TextFilter->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 (int32 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();
TSharedPtr<FClassViewerNode> ClassNode;
TSharedPtr<FClassViewerNode> ExpandNode;
if (PreviousSelection.Num() > 0)
{
ClassNode = ClassViewer::Helpers::ClassHierarchy->FindNodeByGeneratedClassPath(RootNode, PreviousSelection[0]);
ExpandNode = ClassNode ? ClassNode->GetParentNode() : nullptr;
}
else if (InitOptions.InitiallySelectedClass)
{
UClass* CurrentClass = InitOptions.InitiallySelectedClass;
InitOptions.InitiallySelectedClass = nullptr;
TArray<UClass*> ClassHierarchy;
while (CurrentClass)
{
ClassHierarchy.Add(CurrentClass);
CurrentClass = CurrentClass->GetSuperClass();
}
ClassNode = RootNode;
for (int32 Index = ClassHierarchy.Num() - 2; Index >= 0; --Index)
{
for (const TSharedPtr<FClassViewerNode>& ChildClassNode : ClassNode->GetChildrenList())
{
UClass* ChildClass = ChildClassNode->Class.Get();
if (ChildClass == ClassHierarchy[Index])
{
ClassNode = ChildClassNode;
break;
}
}
}
ExpandNode = ClassNode;
}
for (; ExpandNode; ExpandNode = ExpandNode->GetParentNode())
{
ClassTree->SetItemExpansion(ExpandNode, true);
}
if (ClassNode)
{
ClassTree->SetSelection(ClassNode);
ClassTree->RequestScrollIntoView(ClassNode);
}
}
else
{
// Get the class list, passing in certain filter options.
ClassViewer::Helpers::GetClassList(RootTreeItems, ClassFilter, InitOptions);
if (InitOptions.ClassViewerSortPredicate)
{
RootTreeItems.Sort([this](const TSharedPtr<FClassViewerNode>& A, const TSharedPtr<FClassViewerNode>& B)
{
FClassViewerSortElementInfo InfoA (A->Class, A->GetClassName(false), A->GetClassName(true));
FClassViewerSortElementInfo InfoB (B->Class, B->GetClassName(false), B->GetClassName(true));
return InitOptions.ClassViewerSortPredicate(InfoA, InfoB);
});
}
else
{
// Sort the list alphabetically.
RootTreeItems.Sort(FClassViewerNodeNameLess(InitOptions.NameTypeToDisplay));
}
// 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 (int32 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();
FString ClassPathNameToSelect;
if (PreviousSelection.Num() > 0)
{
ClassPathNameToSelect = PreviousSelection[0].ToString();
}
else if (InitOptions.InitiallySelectedClass)
{
ClassPathNameToSelect = InitOptions.InitiallySelectedClass->GetPathName();
}
if (ClassPathNameToSelect.Len() > 0)
{
if(TSharedPtr<FClassViewerNode>* ClassNode = RootTreeItems.FindByPredicate([ClassPathNameToSelect](const TSharedPtr< FClassViewerNode > InClassNode) { return InClassNode->Class.IsValid() && (InClassNode->Class->GetPathName() == ClassPathNameToSelect); }))
{
ClassList->SetSelection(*ClassNode);
ClassList->RequestScrollIntoView(*ClassNode);
}
InitOptions.InitiallySelectedClass = nullptr;
}
}
}
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 (InFocusEvent.GetCause() == EFocusCause::Navigation)
{
FSlateApplication::Get().SetKeyboardFocus(SearchBox.ToSharedRef(), EFocusCause::SetDirectly);
}
return FReply::Unhandled();
}
bool SClassViewer::SupportsKeyboardFocus() const
{
return true;
}
void SClassViewer::RequestPopulateClassHierarchy()
{
ClassViewer::Helpers::RequestPopulateClassHierarchy();
}
void SClassViewer::DestroyClassHierarchy()
{
ClassViewer::Helpers::DestroyClassHierachy();
}
TSharedPtr<FClassViewerNode> SClassViewer::CreateNoneOption()
{
TSharedPtr<FClassViewerNode> NoneItem = MakeShared<FClassViewerNode>("None", "None");
// The item "passes" the filter so it does not appear grayed out.
NoneItem->bPassesFilter = true;
NoneItem->bPassesFilterRegardlessTextFilter = 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 (InitOptions.bExpandAllNodes)
{
SetAllExpansionStates(true);
}
// Scroll the first item into view if applicable
const TArray<TSharedPtr<FClassViewerNode>> SelectedItems = GetSelectedItems();
if (SelectedItems.Num() > 0)
{
ClassTree->RequestScrollIntoView(SelectedItems[0]);
}
}
if (bPendingSetExpansionStates)
{
check(RootTreeItems.Num() > 0);
SetExpansionStatesInTree(RootTreeItems[0]);
bPendingSetExpansionStates = false;
}
}
bool SClassViewer::IsClassAllowed(const UClass* InClass) const
{
return ClassFilter->IsClassAllowed(InitOptions, InClass, ClassFilter->FilterFunctions);
}
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<UClassViewerSettings>()->DisplayInternalClasses = !GetDefault<UClassViewerSettings>()->DisplayInternalClasses;
GetMutableDefault<UClassViewerSettings>()->PostEditChange();
}
bool SClassViewer::IsToggleShowInternalClassesAllowed() const
{
return InitOptions.bAllowViewOptions;
}
bool SClassViewer::IsShowingInternalClasses() const
{
if (!InitOptions.bAllowViewOptions)
{
return true;
}
return IsToggleShowInternalClassesAllowed() ? GetDefault<UClassViewerSettings>()->DisplayInternalClasses : false;
}
void SClassViewer::ToggleCustomFilterOption(TSharedRef<FClassViewerFilterOption> FilterOption)
{
FilterOption->bEnabled = !FilterOption->bEnabled;
if (FilterOption->OnOptionChanged.IsBound())
{
FilterOption->OnOptionChanged.Execute(FilterOption->bEnabled);
}
Refresh();
}
bool SClassViewer::IsCustomFilterOptionEnabled(TSharedRef<FClassViewerFilterOption> FilterOption) const
{
return FilterOption->bEnabled;
}
#undef LOCTEXT_NAMESPACE