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

421 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SBlueprintFavoritesPalette.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionMenuBuilder.h"
#include "BlueprintActionMenuUtils.h"
#include "BlueprintEditor.h"
#include "BlueprintPaletteFavorites.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "CoreGlobals.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphSchema.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Commands/Commands.h"
#include "Framework/Commands/InputChord.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandInfo.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformProcess.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Misc/Attribute.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Parse.h"
#include "SGraphActionMenu.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#include "Textures/SlateIcon.h"
#include "Types/SlateEnums.h"
#include "UObject/NameTypes.h"
#include "UObject/ObjectPtr.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealNames.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/STextBlock.h"
class SWidget;
#define LOCTEXT_NAMESPACE "BlueprintFavoritesPalette"
/*******************************************************************************
* Static File Helpers
*******************************************************************************/
/**
* Contains static helper methods (scoped inside this struct to avoid collisions
* during unified builds).
*/
struct SBlueprintFavoritesPaletteUtils
{
/** The definition of a delegate used to retrieve a set of palette actions */
DECLARE_DELEGATE_OneParam(FPaletteActionGetter, TArray< TSharedPtr<FEdGraphSchemaAction> >&);
/**
* Uses the provided ActionGetter to get a list of selected actions, and then
* removes every one from the user's favorites.
*
* @param ActionGetter A delegate to use for grabbing the palette's selected actions.
*/
static void RemoveSelectedFavorites(FPaletteActionGetter ActionGetter)
{
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (ActionGetter.IsBound() && (EditorPerProjectUserSettings->BlueprintFavorites != NULL))
{
TArray< TSharedPtr<FEdGraphSchemaAction> > SelectedActions;
ActionGetter.Execute(SelectedActions);
EditorPerProjectUserSettings->BlueprintFavorites->RemoveFavorites(SelectedActions);
}
}
/**
* Removes every single favorite and sets the user's profile to a custom one
* (if it isn't already).
*/
static void ClearPaletteFavorites()
{
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
EditorPerProjectUserSettings->BlueprintFavorites->ClearAllFavorites();
}
}
/**
* A UI hook, used to determine if the specified profile can be loaded (or
* is it what's currently set).
*
* @param ProfileName The name of the profile you wish to check.
* @return True if the user's settings don't have that profile currently set, false if it is set or the user's setting are null.
*/
static bool CanLoadFavoritesProfile(FString const& ProfileName)
{
bool bIsLoaded = false;
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
bIsLoaded = (EditorPerProjectUserSettings->BlueprintFavorites->GetCurrentProfile() == ProfileName);
}
return !bIsLoaded;
}
/**
* A UI hook for loading a specific favorites-profile, which throws out all
* current favorites and loads in ones for the specified profile.
*
* @param ProfileName The name of the profile you wish to load.
*/
static void LoadFavoritesProfile(FString ProfileName)
{
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
EditorPerProjectUserSettings->BlueprintFavorites->LoadProfile(ProfileName);
}
}
/**
* Takes the provided menu builder and adds elements representing various
* profiles that the user can choose from (default, tutorial, etc.).
*
* @param MenuBuilder The builder to modify and add entries to.
*/
static void BuildProfilesSubMenu(FMenuBuilder& MenuBuilder)
{
TArray<FString> AvailableProfiles;
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
static FString const ProfilesConfigKey("Profiles");
GConfig->GetArray(*SBlueprintFavoritesPaletteUtils::ConfigSection, *ProfilesConfigKey, AvailableProfiles, GEditorIni);
}
struct LocalUtils
{
static bool CanExecute() { return true; }
static bool CannotExecute() { return false; }
static void NavigateToURL(FString URL) { FPlatformProcess::LaunchURL(*URL, NULL, NULL); }
};
if (AvailableProfiles.Num() > 0)
{
for (FString const& Profile : AvailableProfiles)
{
FString ProfileName;
FParse::Value(*Profile, TEXT("Name="), ProfileName);
FString FriendlyProfileName;
FParse::Value(*Profile, TEXT("FriendlyName="), FriendlyProfileName);
FString ProfileToolTip;
FParse::Value(*Profile, TEXT("ToolTip="), ProfileToolTip);
FString ProfileURL;
FParse::Value(*Profile, TEXT("URL="), ProfileURL);
FString ProfileURLName;
FParse::Value(*Profile, TEXT("URLText="), ProfileURLName);
// @TODO how to best localize this?
FText ToolTipText = FText::FromString(ProfileToolTip);
if (ProfileURLName.IsEmpty())
{
ProfileURLName = ProfileURL;
}
if (FriendlyProfileName.IsEmpty())
{
FriendlyProfileName = ProfileName;
}
FUIAction ProfileAction;
if (SBlueprintFavoritesPaletteUtils::CanLoadFavoritesProfile(ProfileName))
{
if (ToolTipText.IsEmpty())
{
ToolTipText = FText::Format(LOCTEXT("ProfileAvailableFmt", "Loads {0} node favorites"), FText::FromString(FriendlyProfileName));
}
ProfileAction = FUIAction(
FExecuteAction::CreateStatic(&SBlueprintFavoritesPaletteUtils::LoadFavoritesProfile, ProfileName),
FCanExecuteAction::CreateStatic(&LocalUtils::CanExecute)
);
}
else
{
if (ToolTipText.IsEmpty())
{
ToolTipText = LOCTEXT("ProfileLoaded", "Current profile");
}
ProfileAction = FUIAction(
FExecuteAction(),
FCanExecuteAction::CreateStatic(&LocalUtils::CannotExecute)
);
}
// build the text that goes in the sub-menu
TSharedRef<STextBlock> MenuTextEntry = SNew(STextBlock)
.TextStyle(MenuBuilder.GetStyleSet(), FAppStyle::Join("Menu", ".Label"))
// @TODO how do we best localize this
.Text(FText::FromString(FriendlyProfileName));
FSlateFontInfo ToolTipFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);
TSharedPtr<SVerticalBox> ToolTipBox;
// build the specialized tooltip
TSharedRef<SToolTip> ToolTipWidget = SNew(SToolTip)
[
SAssignNew(ToolTipBox, SVerticalBox)
+SVerticalBox::Slot()
[
SNew(STextBlock)
.WrapTextAt(400)
.Font(ToolTipFont)
.Text(ToolTipText)
]
];
// add the url if one was specified
if (!ProfileURL.IsEmpty())
{
ToolTipBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
[
SNew(SHyperlink)
// @TODO how to best localize this?
.Text(FText::FromString(ProfileURLName))
.OnNavigate_Static(&LocalUtils::NavigateToURL, ProfileURL)
];
}
// now build the actual menu widget
TSharedRef<SWidget> MenuEntryWidget = SNew(SHorizontalBox)
.ToolTip(ToolTipWidget)
// so the tool tip shows up for the whole entry:
.Visibility(EVisibility::Visible)
+SHorizontalBox::Slot()
// match the padding with normal sub-menu entries
.Padding(FMargin(18.f, 0.f, 6.f, 0.f))
.FillWidth(1.0f)
[
MenuTextEntry
];
MenuBuilder.AddMenuEntry(ProfileAction, MenuEntryWidget);
}
}
else
{
FUIAction NullProfileAction(FExecuteAction(), FCanExecuteAction::CreateStatic(&LocalUtils::CannotExecute));
MenuBuilder.AddMenuEntry(LOCTEXT("NoProfiles", "No profiles available"), FText::GetEmpty(), FSlateIcon(), NullProfileAction);
}
}
/** String constants shared between multiple SBlueprintLibraryPalette functions */
static FString const ConfigSection;
static FString const FavoritesCategoryName;
};
FString const SBlueprintFavoritesPaletteUtils::ConfigSection("BlueprintEditor.Favorites");
FString const SBlueprintFavoritesPaletteUtils::FavoritesCategoryName = LOCTEXT("FavoriteseCategory", "Favorites").ToString();
/*******************************************************************************
* FBlueprintFavoritesPaletteCommands
*******************************************************************************/
class FBlueprintFavoritesPaletteCommands : public TCommands<FBlueprintFavoritesPaletteCommands>
{
public:
FBlueprintFavoritesPaletteCommands() : TCommands<FBlueprintFavoritesPaletteCommands>
( "BlueprintFavoritesPalette"
, LOCTEXT("FavoritesPaletteContext", "Favorites Palette")
, NAME_None
, FAppStyle::GetAppStyleSetName() )
{
}
TSharedPtr<FUICommandInfo> RemoveSingleFavorite;
TSharedPtr<FUICommandInfo> RemoveSubFavorites;
TSharedPtr<FUICommandInfo> ClearFavorites;
/** Registers context menu commands for the blueprint favorites palette. */
virtual void RegisterCommands() override
{
UI_COMMAND(RemoveSingleFavorite, "Remove from Favorites", "Removes this item from your favorites list.", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(RemoveSubFavorites, "Remove Category from Favorites", "Removes all the nodes in this category from your favorites.", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(ClearFavorites, "Clear All Favorites", "Clears out all of your favorited nodes.", EUserInterfaceActionType::Button, FInputChord());
}
};
/*******************************************************************************
* SBlueprintFavoritesPalette Public Interface
*******************************************************************************/
//------------------------------------------------------------------------------
SBlueprintFavoritesPalette::~SBlueprintFavoritesPalette()
{
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
EditorPerProjectUserSettings->BlueprintFavorites->OnFavoritesUpdated.RemoveAll(this);
}
}
//------------------------------------------------------------------------------
void SBlueprintFavoritesPalette::Construct(FArguments const& InArgs, TWeakPtr<FBlueprintEditor> InBlueprintEditor)
{
SBlueprintSubPalette::FArguments SuperArgs;
SuperArgs._Title = LOCTEXT("PaletteTitle", "Favorites");
SuperArgs._Icon = FAppStyle::GetBrush("Icons.Star");
SuperArgs._ToolTipText = LOCTEXT("PaletteToolTip", "A listing of your favorite and most used nodes.");
static FString const ShowFreqUsedConfigKey("bShowFrequentlyUsed");
// should be set before we call the super (so CollectAllActions() has the right value)
bShowFrequentlyUsed = false;
GConfig->GetBool(*SBlueprintFavoritesPaletteUtils::ConfigSection, *ShowFreqUsedConfigKey, bShowFrequentlyUsed, GEditorIni);
SBlueprintSubPalette::Construct(SuperArgs, InBlueprintEditor);
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
if (EditorPerProjectUserSettings->BlueprintFavorites != NULL)
{
EditorPerProjectUserSettings->BlueprintFavorites->OnFavoritesUpdated.AddSP(this, &SBlueprintFavoritesPalette::RefreshActionsList, true);
}
}
/*******************************************************************************
* SBlueprintFavoritesPalette Private Methods
*******************************************************************************/
//------------------------------------------------------------------------------
void SBlueprintFavoritesPalette::CollectAllActions(FGraphActionListBuilderBase& OutAllActions)
{
FBlueprintActionContext FilterContext;
FilterContext.Blueprints.Add(GetBlueprint());
FilterContext.EditorPtr = BlueprintEditorPtr;
FBlueprintActionMenuBuilder FavoritesBuilder;
FBlueprintActionMenuUtils::MakeFavoritesMenu(FilterContext, FavoritesBuilder);
OutAllActions.Append(FavoritesBuilder);
}
//------------------------------------------------------------------------------
void SBlueprintFavoritesPalette::BindCommands(TSharedPtr<FUICommandList> CommandListIn) const
{
SBlueprintSubPalette::BindCommands(CommandListIn);
FBlueprintFavoritesPaletteCommands::Register();
FBlueprintFavoritesPaletteCommands const& PaletteCommands = FBlueprintFavoritesPaletteCommands::Get();
SBlueprintFavoritesPaletteUtils::FPaletteActionGetter ActionGetter = SBlueprintFavoritesPaletteUtils::FPaletteActionGetter::CreateRaw(GraphActionMenu.Get(), &SGraphActionMenu::GetSelectedActions);
CommandListIn->MapAction(
PaletteCommands.RemoveSingleFavorite,
FExecuteAction::CreateStatic(&SBlueprintFavoritesPaletteUtils::RemoveSelectedFavorites, ActionGetter)
);
SBlueprintFavoritesPaletteUtils::FPaletteActionGetter CategoryGetter = SBlueprintFavoritesPaletteUtils::FPaletteActionGetter::CreateRaw(GraphActionMenu.Get(), &SGraphActionMenu::GetSelectedCategorySubActions);
CommandListIn->MapAction(
PaletteCommands.RemoveSubFavorites,
FExecuteAction::CreateStatic(&SBlueprintFavoritesPaletteUtils::RemoveSelectedFavorites, CategoryGetter)
);
CommandListIn->MapAction(
PaletteCommands.ClearFavorites,
FExecuteAction::CreateStatic(&SBlueprintFavoritesPaletteUtils::ClearPaletteFavorites)
);
}
//------------------------------------------------------------------------------
void SBlueprintFavoritesPalette::GenerateContextMenuEntries(FMenuBuilder& MenuBuilder) const
{
FBlueprintFavoritesPaletteCommands const& PaletteCommands = FBlueprintFavoritesPaletteCommands::Get();
MenuBuilder.BeginSection("FavoritedItem");
{
TSharedPtr<FEdGraphSchemaAction> SelectedAction = GetSelectedAction();
// if we have a specific action selected
if (SelectedAction.IsValid())
{
MenuBuilder.AddMenuEntry(PaletteCommands.RemoveSingleFavorite);
}
// if we have a category selected
{
FString CategoryName = GraphActionMenu->GetSelectedCategoryName();
// make sure it is an actual category and isn't the root (assume there's only one category with that name)
if (!CategoryName.IsEmpty() && (CategoryName != SBlueprintFavoritesPaletteUtils::FavoritesCategoryName))
{
MenuBuilder.AddMenuEntry(PaletteCommands.RemoveSubFavorites);
}
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("FavoritesList");
{
SBlueprintSubPalette::GenerateContextMenuEntries(MenuBuilder);
MenuBuilder.AddSubMenu(
LOCTEXT("LoadProfile", "Load Profile"),
LOCTEXT("LoadProfileTooltip", "Replace your current favorites with ones from a pre-defined profile."),
FNewMenuDelegate::CreateStatic(&SBlueprintFavoritesPaletteUtils::BuildProfilesSubMenu)
);
MenuBuilder.AddMenuEntry(PaletteCommands.ClearFavorites);
}
MenuBuilder.EndSection();
}
#undef LOCTEXT_NAMESPACE