Files
UnrealEngineUWP/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp
Richard TalbotWatkin b1b443cda9 Changed SClassItem parent from SComboRow to STableRow so that dragging can be initiated from a selected item.
#jira UE-8326 - Unable to drag the selected actor from class viewer to scene

[CL 2515873 by Richard TalbotWatkin in Main branch]
2015-04-17 10:38:48 -04:00

2811 lines
98 KiB
C++

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