Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp

1367 lines
43 KiB
C++
Raw Normal View History

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340) #lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3209340 on 2016/11/23 by Ben.Marsh Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h. Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms. * Every header now includes everything it needs to compile. * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first. * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h. * Every .cpp file includes its matching .h file first. * This helps validate that each header is including everything it needs to compile. * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more. * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there. * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible. * No engine code explicitly includes a precompiled header any more. * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies. * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files. Tool used to generate this transform is at Engine\Source\Programs\IncludeTool. [CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00
#include "SGraphActionMenu.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Text/SRichTextBlock.h"
#include "Widgets/Layout/SScrollBorder.h"
#include "EditorStyleSet.h"
#include "GraphEditorDragDropAction.h"
#include "EdGraphSchema_K2.h"
#include "K2Node.h"
#include "EdGraphSchema_K2_Actions.h"
#include "GraphActionNode.h"
Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340) #lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3209340 on 2016/11/23 by Ben.Marsh Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h. Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms. * Every header now includes everything it needs to compile. * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first. * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h. * Every .cpp file includes its matching .h file first. * This helps validate that each header is including everything it needs to compile. * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more. * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there. * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible. * No engine code explicitly includes a precompiled header any more. * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies. * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files. Tool used to generate this transform is at Engine\Source\Programs\IncludeTool. [CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00
#include "Widgets/SToolTip.h"
#include "IDocumentation.h"
#include "EditorCategoryUtils.h"
Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340) #lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3209340 on 2016/11/23 by Ben.Marsh Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h. Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms. * Every header now includes everything it needs to compile. * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first. * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h. * Every .cpp file includes its matching .h file first. * This helps validate that each header is including everything it needs to compile. * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more. * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there. * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible. * No engine code explicitly includes a precompiled header any more. * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies. * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files. Tool used to generate this transform is at Engine\Source\Programs\IncludeTool. [CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#define LOCTEXT_NAMESPACE "GraphActionMenu"
//////////////////////////////////////////////////////////////////////////
template<typename ItemType>
class SCategoryHeaderTableRow : public STableRow < ItemType >
{
public:
SLATE_BEGIN_ARGS(SCategoryHeaderTableRow)
{}
SLATE_DEFAULT_SLOT(typename SCategoryHeaderTableRow::FArguments, Content)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
STableRow<ItemType>::ChildSlot
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
[
SAssignNew(ContentBorder, SBorder)
.BorderImage(this, &SCategoryHeaderTableRow::GetBackgroundImage)
.Padding(FMargin(0.0f, 3.0f))
.BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(2.0f, 2.0f, 2.0f, 2.0f)
.AutoWidth()
[
SNew(SExpanderArrow, STableRow< ItemType >::SharedThis(this))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
InArgs._Content.Widget
]
]
];
STableRow < ItemType >::ConstructInternal(
typename STableRow< ItemType >::FArguments()
.Style(FEditorStyle::Get(), "DetailsView.TreeView.TableRow")
.ShowSelection(false),
InOwnerTableView
);
}
const FSlateBrush* GetBackgroundImage() const
{
if ( STableRow<ItemType>::IsHovered() )
{
return STableRow<ItemType>::IsItemExpanded() ? FEditorStyle::GetBrush("DetailsView.CategoryTop_Hovered") : FEditorStyle::GetBrush("DetailsView.CollapsedCategory_Hovered");
}
else
{
return STableRow<ItemType>::IsItemExpanded() ? FEditorStyle::GetBrush("DetailsView.CategoryTop") : FEditorStyle::GetBrush("DetailsView.CollapsedCategory");
}
}
virtual void SetContent(TSharedRef< SWidget > InContent) override
{
ContentBorder->SetContent(InContent);
}
virtual void SetRowContent(TSharedRef< SWidget > InContent) override
{
ContentBorder->SetContent(InContent);
}
private:
TSharedPtr<SBorder> ContentBorder;
};
//////////////////////////////////////////////////////////////////////////
namespace GraphActionMenuHelpers
{
bool ActionMatchesName(const FEdGraphSchemaAction* InGraphAction, const FName& ItemName)
{
bool bCheck = false;
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Var*)InGraphAction)->GetVariableName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2LocalVar*)InGraphAction)->GetVariableName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Graph*)InGraphAction)->EdGraph &&
((FEdGraphSchemaAction_K2Graph*)InGraphAction)->EdGraph->GetFName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Enum::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Enum*)InGraphAction)->GetPathName() == ItemName);
Merging UE4-Pretest @ 2042161 to UE4 Change 1996384 by Andrew Brown: 322252 - EDITOR: Asset picker displays incorrect text when there are no filter results. Change 1996385 by Andrew Brown: 321858 - CRASH: Assertion failed: (Index >= 0) Function: STransformViewportToolBar::GetLocationGridLabel() STextBlock::CacheDesiredSize() Change 1996977 by Andrew Brown: 309685 - UE4: Adding an event/renaming an event on an event track in Matinee does not update the MatineeActor node in blueprint Change 2034873 by Jaroslaw Palczynski: More robust VS installation detection. Change 2039693 by Jaroslaw Palczynski: 327268 - RocketGDC: POSTLAUNCH: DEV: Make engine more robust against bad Visual Studio environment variables Change 1978978 by Jaroslaw Surowiec: - Removed obsolete AllowEliminatingReferences from the FArchive Change 2020326 by Maciej Mroz: pretest BP K2Node: RemovePinsFromOldPins function moved from K2Node to RemovePinsFromOldPins Change 2017608 by Maciej Mroz: pretest Some changes in SFortMissionEventSelector caused by FPinTypeTreeInfo Change 2017463 by Maciej Mroz: PinTypeSelector can lins unloaded UDStructs Change 2019979 by Maciej Mroz: pretest BP: Crash when performing Diff against Depot with blueprints containing Format Text nodes Change 2024469 by Maciej Mroz: MemberReference variable added to PinType. It's necessary for delegate's signature. Change 2024049 by Maciej Mroz: HasExternalBlueprintDependencies added to UK2Node_DynamicCast Change 2024586 by Maciej Mroz: FillSimpleMemberReference fix Change 2024472 by Maciej Mroz: workaround for delegates signature in pintype removed. Change 2023997 by Maciej Mroz: BP, UDStruc: Class UserDefinedStructEditorData added. It fixes many problems with undo/redo. Change 2021934 by Maciej Mroz: typo in a comment Change 2020355 by Maciej Mroz: Back out changelist 2020342 Change 2022178 by Maciej Mroz: CRASH: PRETEST: EDITOR: UDS: Crash when undo then redo new variable in struct that is used by blueprint Change 2021958 by Maciej Mroz: CRASH: PRETEST: EDITOR: UDS: Crash using variable of a type of copied struct in blueprint Change 1986247 by Maciej Mroz: User Defined Structures: circle dependency fixed. Early version. Change 1985107 by Maciej Mroz: UserDefinedStruct cannot have a field of a non-native type Change 1986278 by Maciej Mroz: pretest ensureMsgf in Struct::link Change 1986250 by Maciej Mroz: User Defined Struct: Non native classes are accepted types od values in structures. Change 1980955 by Maciej Mroz: Using AssetPtr and LazyPtr as UFunction parameter (intput or return) is explicitly disallowed. Change 2041215 by Maciej Mroz: ttp331249 BLOCKER: PRETEST: UI: Survive the Storm is missing the Mission HUD. Change 1984316 by Maciej Mroz: New User Defined Structure. WIP - there are still problems with circular dependencies. Change 2011616 by Maciej Mroz: UserDefinedStructures - various problems fixed. Change 2011609 by Maciej Mroz: more robust HasExternalBlueprintDependencies implementation Change 2016697 by Maciej Mroz: pretest BP: UDStruct - default value propagation in cooked build Change 2016288 by Maciej Mroz: pretest BP: UDStruct: Renaming variables wont break links from make/break nodes Change 1987637 by Maciej Mroz: CustomStruct icons placeholders Change 1987422 by Maciej Mroz: Better tooltips for variables in MyBlueprint Change 1991387 by Maciej Mroz: UDStructures fixes: Change 2029165 by Maciej Mroz: BP: better comment for incomatible pins Change 2030016 by Maciej Mroz: 8PRETEST: EDITOR: UDS: Defaults values aren't updated in struct type variables in blueprints Change 2030017 by Maciej Mroz: Unused UDStructure code removed (PPF_UseDefaultsForUDStructures) Change 2028856 by Maciej Mroz: BP: Pins with PC_Struct type are compatible only with exactly the same structure. (No derived structures are not handled as compatible). Change 2026701 by Maciej Mroz: k2: odd error on an add item node within a function (see attached image in details) Change 2028160 by Maciej Mroz: PRETEST: EDITOR: UDS: When deleting structures just after creating there is always some references in the memory Change 2028165 by Maciej Mroz: BP: BreakHitResult function has proper icon. Change 2033340 by Maciej Mroz: ttp330786 PRETEST: EDITOR: UDS: Changes of default values aren't apllied to breeak nodes for text type of variables Change 2034255 by Maciej Mroz: EDITOR: UDS: Changes of default values aren't apllied to make nodes for text type of variables ttp#330620 Change 2037682 by Maciej Mroz: ttp331309 BLOCKER: PRETEST: CRASH: EDITOR: Crash occurs when performing Diff Against Depot on any Blueprint Change 2033142 by Maciej Mroz: CreateDelegate Node uses internally FMemberReference. Refactor. Change 2032329 by Maciej Mroz: ttp330608 CRASH: PRETEST: EDITOR: UDS: Crash when trying to use struct named 'Color' in blueprint Change 2032420 by Maciej Mroz: ttp330620 PRETEST: EDITOR: UDS: Changes of default values aren't apllied to make nodes for text type of variables Change 2033139 by Maciej Mroz: Functions generated from CustomEvents can be also identified by GUID Change 2026631 by Maciej Mroz: BP. UDStruct: Invalid structs are handled better. Change 2025344 by Maciej Mroz: UDStruct enabled by default Change 2026672 by Maciej Mroz: EDITOR: BP: Can't easily remove 'pass-by-reference' pins on ReturnNodes Change 2026411 by Maciej Mroz: ExposeOnSpawn updated, it supports UDStructs, custom native Structs, and it throws compiler error. Change 2025342 by Maciej Mroz: GenerateBlueprintSkeleton moved from BLueprint::Serialize to RegenerateBlueprintClass, because SkeletonClass compilation requires all external dependencies to be loaded and linked. Change 2025570 by Steve Robb: Moved dependency processing to its own function. Change 2033235 by Steve Robb: String improvements Change 2035830 by Steve Robb: Workaround for FriendsAndChat crash in Fortnite. Change 2035115 by Steve Robb: UBT build time regression fixes. Change 2034162 by Steve Robb: 312775: UObject improvement: Ensure that *.generated.inl is included somewhere Change 2034181 by Steve Robb: Removal of any references to .generated.inl Change 2020165 by Steve Robb: BuildPublicAndPrivateUObjectHeaders factored out into its own function. Change 2020187 by Steve Robb: CreateModuleCompileEnvironment function factored out. Change 2020055 by Steve Robb: Refactoring of Unity.cs to remove complex and duplicate iteration. Change 2020083 by Steve Robb: Another use of dictionary utilities. Change 2031049 by Steve Robb: 312775: UObject improvement: Ensure that *.generated.inl is included somewhere Change 2025728 by Steve Robb: Refactored the application of a shared PCH file to multiple file into a single ApplySharedPCH function. Change 2020068 by Steve Robb: A couple of helpful utility functions for populating dictionaries. Change 2032307 by Steve Robb: 312775: UObject improvement: Ensure that *.generated.inl is included somewhere [CL 2054495 by Robert Manuszewski in Main branch]
2014-04-23 20:18:55 -04:00
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Struct::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Struct*)InGraphAction)->GetPathName() == ItemName);
bCheck |= (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId() &&
((FEdGraphSchemaAction_K2Delegate*)InGraphAction)->GetDelegateName() == ItemName);
const bool bIsTargetNodeSubclass = (InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2TargetNode::StaticGetTypeId()) ||
(InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId()) ||
(InGraphAction->GetTypeId() == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId());
bCheck |= (bIsTargetNodeSubclass &&
((FEdGraphSchemaAction_K2TargetNode*)InGraphAction)->NodeTemplate->GetNodeTitle(ENodeTitleType::EditableTitle).ToString() == ItemName.ToString());
return bCheck;
}
}
//////////////////////////////////////////////////////////////////////////
void SDefaultGraphActionWidget::Construct(const FArguments& InArgs, const FCreateWidgetForActionData* InCreateData)
{
ActionPtr = InCreateData->Action;
MouseButtonDownDelegate = InCreateData->MouseButtonDownDelegate;
this->ChildSlot
[
SNew(SHorizontalBox)
.ToolTipText( FText::FromString(InCreateData->Action->GetTooltipDescription()) )
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Font(FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 9 ))
.Text(InCreateData->Action->GetMenuDescription())
.HighlightText(InArgs._HighlightText)
]
];
}
FReply SDefaultGraphActionWidget::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if( MouseButtonDownDelegate.Execute( ActionPtr ) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
//////////////////////////////////////////////////////////////////////////
class SGraphActionCategoryWidget : public SCompoundWidget
{
SLATE_BEGIN_ARGS( SGraphActionCategoryWidget )
{}
SLATE_ATTRIBUTE( FText, HighlightText )
SLATE_EVENT( FOnTextCommitted, OnTextCommitted )
SLATE_EVENT( FIsSelected, IsSelected )
SLATE_ATTRIBUTE( bool, IsReadOnly )
SLATE_END_ARGS()
TWeakPtr<FGraphActionNode> ActionNode;
TAttribute<bool> IsReadOnly;
public:
TWeakPtr<SInlineEditableTextBlock> InlineWidget;
void Construct( const FArguments& InArgs, TSharedPtr<FGraphActionNode> InActionNode )
{
ActionNode = InActionNode;
FText CategoryTooltip;
FString CategoryLink, CategoryExcerpt;
FEditorCategoryUtils::GetCategoryTooltipInfo(*InActionNode->GetDisplayName().ToString(), CategoryTooltip, CategoryLink, CategoryExcerpt);
TSharedRef<SToolTip> ToolTipWidget = IDocumentation::Get()->CreateToolTip(CategoryTooltip, NULL, CategoryLink, CategoryExcerpt);
IsReadOnly = InArgs._IsReadOnly;
this->ChildSlot
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SAssignNew(InlineWidget, SInlineEditableTextBlock)
.Font( FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 9 ) )
.Text( FEditorCategoryUtils::GetCategoryDisplayString(InActionNode->GetDisplayName()) )
.ToolTip( ToolTipWidget )
.HighlightText( InArgs._HighlightText )
.OnVerifyTextChanged( this, &SGraphActionCategoryWidget::OnVerifyTextChanged )
.OnTextCommitted( InArgs._OnTextCommitted )
.IsSelected( InArgs._IsSelected )
.IsReadOnly( InArgs._IsReadOnly )
]
];
}
// SWidget interface
virtual FReply OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) override
{
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->DroppedOnCategory( ActionNode.Pin()->GetCategoryPath() );
return FReply::Handled();
}
return FReply::Unhandled();
}
virtual void OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) override
{
if (IsReadOnly.Get())
{
return;
}
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->SetHoveredCategoryName( ActionNode.Pin()->GetDisplayName() );
}
}
virtual void OnDragLeave( const FDragDropEvent& DragDropEvent ) override
{
TSharedPtr<FGraphEditorDragDropAction> GraphDropOp = DragDropEvent.GetOperationAs<FGraphEditorDragDropAction>();
if (GraphDropOp.IsValid())
{
GraphDropOp->SetHoveredCategoryName( FText::GetEmpty() );
}
}
// End of SWidget interface
/** Callback for the SInlineEditableTextBlock to verify the text before commit */
bool OnVerifyTextChanged(const FText& InText, FText& OutErrorMessage)
{
if(InText.ToString().Len() > NAME_SIZE)
{
OutErrorMessage = LOCTEXT("CategoryNameTooLong_Error", "Name too long!");
return false;
}
return true;
}
};
//////////////////////////////////////////////////////////////////////////
void SGraphActionMenu::Construct( const FArguments& InArgs, bool bIsReadOnly/* = true*/ )
{
this->SelectedSuggestion = INDEX_NONE;
this->bIgnoreUIUpdate = false;
this->bUseSectionStyling = InArgs._UseSectionStyling;
this->bAutoExpandActionMenu = InArgs._AutoExpandActionMenu;
this->bShowFilterTextBox = InArgs._ShowFilterTextBox;
this->bAlphaSortItems = InArgs._AlphaSortItems;
this->OnActionSelected = InArgs._OnActionSelected;
this->OnActionDoubleClicked = InArgs._OnActionDoubleClicked;
this->OnActionDragged = InArgs._OnActionDragged;
this->OnCategoryDragged = InArgs._OnCategoryDragged;
this->OnCreateWidgetForAction = InArgs._OnCreateWidgetForAction;
this->OnCreateCustomRowExpander = InArgs._OnCreateCustomRowExpander;
this->OnCollectAllActions = InArgs._OnCollectAllActions;
this->OnCollectStaticSections = InArgs._OnCollectStaticSections;
this->OnCategoryTextCommitted = InArgs._OnCategoryTextCommitted;
this->OnCanRenameSelectedAction = InArgs._OnCanRenameSelectedAction;
this->OnGetSectionTitle = InArgs._OnGetSectionTitle;
this->OnGetSectionToolTip = InArgs._OnGetSectionToolTip;
this->OnGetSectionWidget = InArgs._OnGetSectionWidget;
this->FilteredRootAction = FGraphActionNode::NewRootNode();
this->OnActionMatchesName = InArgs._OnActionMatchesName;
// If a delegate for filtering text is passed in, assign it so that it will be used instead of the built-in filter box
if(InArgs._OnGetFilterText.IsBound())
{
this->OnGetFilterText = InArgs._OnGetFilterText;
}
TreeView = SNew(STreeView< TSharedPtr<FGraphActionNode> >)
.ItemHeight(24)
.TreeItemsSource(&(this->FilteredRootAction->Children))
.OnGenerateRow(this, &SGraphActionMenu::MakeWidget, bIsReadOnly)
.OnSelectionChanged(this, &SGraphActionMenu::OnItemSelected)
.OnMouseButtonDoubleClick(this, &SGraphActionMenu::OnItemDoubleClicked)
.OnContextMenuOpening(InArgs._OnContextMenuOpening)
.OnGetChildren(this, &SGraphActionMenu::OnGetChildrenForCategory)
.SelectionMode(ESelectionMode::Single)
Copying //UE4/Dev-Blueprints to Dev-Main (//UE4/Dev-Main) #lockdown Nick.Penwarden ========================== MAJOR FEATURES + CHANGES ========================== Change 2806214 on 2015/12/16 by Michael.Schoell Connection draw policy refactored into a factory system. PR#1663 #jira UE-22123 - GitHub 1663 : Implement PinConnectionPolicy factory system Change 2806845 on 2015/12/17 by Maciej.Mroz Blueprint C++ Conversion: fixed varioaus problem. Some limitations of included header list were reverted :( Any compilation-time optimization are premature (as far as Orion cannot be compiled). #codereview Dan.Oconnor Change 2806892 on 2015/12/17 by Maciej.Mroz Blueprint C++ Conversion: fixed compilation error "fatal error C1001: An internal error has occurred in the compiler." Change 2807020 on 2015/12/17 by Michael.Schoell Recursively expand all tree items in graph context menus or My Blueprint window by holding shift when clicking on an arrow. Change 2807639 on 2015/12/17 by Maciej.Mroz BP C++ conversion: Fix for const-correctness. Included generated headers in structs. Change 2807880 on 2015/12/17 by Mike.Beach PR #1865 : Render Kismet graph pin color box with alpha https://github.com/EpicGames/UnrealEngine/pull/1865 #github 1865 #1865 #jira UE-23863 Change 2808538 on 2015/12/18 by Maciej.Mroz Workaround for UE-24467. Some corrupted files can be opened and fixed. #codereivew Dan.Oconnor, Michael.Schoell Change 2808540 on 2015/12/18 by Maciej.Mroz BP C++ conversion: fixed const-correctness related issues Change 2809333 on 2015/12/18 by Phillip.Kavan [UE-23452] SCS/UCS component instancing optimizations change summary: - added FArchive::FCustomPropertyListNode - modified various parts of serialization code to accomodate custom property lists - added cooked component instancing data + supporting APIs to UBlueprintGeneratedClass Change 2810216 on 2015/12/21 by Maciej.Mroz Blueprint C++ Conversion: - Fixed Compiler Error C2026 (too big string) - Fixed a lot of errors related to const correctness. "NativeConst" and "NativeConstTemplateArg" MetaData added. - Fixed bunch of problems with TEnumAsByte - Fixed for error C2059: syntax error : 'bad suffix on number' - when struct name starts with a digit - Fixed int -> bool conversion #codereview Mike.Beach, Dan.Oconnor, Steve.Robb Change 2810397 on 2015/12/21 by Maciej.Mroz Blueprint C++ Conversion: Fixed Compiler Error C1091 (too big string) Change 2810638 on 2015/12/21 by Dan.Oconnor Timers for measuring VM overhead, stats for tracking individual script functions improved (now nicludes ProcessEvent). Stats system is expected to avoid double counting for object and function stats. Change 2811038 on 2015/12/22 by Phillip.Kavan [UE-23452] Fix uninitialized variables in cooked component data. - should eliminate crashes introduced by last change Change 2811054 on 2015/12/22 by Phillip.Kavan [UE-23452] Additional fix for uninitialized USTRUCT property. Change 2811254 on 2015/12/22 by Dan.Oconnor More script function detail in stats Change 2811325 on 2015/12/22 by Maciej.Mroz Blueprint C++ Conversion: improved: ExcludedBlueprintTypes list #codereview Dan.Oconnor Change 2812316 on 2015/12/24 by Michael.Schoell Added more ByValue/ByRef test cases and added a way to dump all tests that are no longer succeeding. Tests Include: Verification that functions take and can modify by-ref values. ForEach loop verification. Tests for verifying that current functionality will continue to work after COPY injection and that "expected" functionality will work (and currently doesn't). More MegaArray tests. Change 2812318 on 2015/12/24 by Michael.Schoell Usability fix: Duplicating graphs in the MyBP window will now open and focus the new graph. Change 2813179 on 2015/12/29 by Michael.Schoell When promoting pins to variables, will no longer carry bIsReference, bIsConst, or bIsWeakPointer value to the new variable's type. Change 2813435 on 2015/12/30 by Michael.Schoell Submitting by-value/by-ref testing BP with more tests: More "Set Members..." tests to catch edge cases. More Array tests. Change 2813441 on 2015/12/30 by Michael.Schoell [CL 2842166 by Mike Beach in Main branch]
2016-01-25 13:25:51 -05:00
.OnItemScrolledIntoView(this, &SGraphActionMenu::OnItemScrolledIntoView)
.OnSetExpansionRecursive(this, &SGraphActionMenu::OnSetExpansionRecursive);
this->ChildSlot
[
SNew(SVerticalBox)
// FILTER BOX
+SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(FilterTextBox, SSearchBox)
// If there is an external filter delegate, do not display this filter box
.Visibility(InArgs._OnGetFilterText.IsBound()? EVisibility::Collapsed : EVisibility::Visible)
.OnTextChanged( this, &SGraphActionMenu::OnFilterTextChanged )
.OnTextCommitted( this, &SGraphActionMenu::OnFilterTextCommitted )
]
// ACTION LIST
+SVerticalBox::Slot()
.Padding(FMargin(0.0f, 2.0f, 0.0f, 0.0f))
.FillHeight(1.f)
[
SNew(SScrollBorder, TreeView.ToSharedRef())
[
TreeView.ToSharedRef()
]
]
];
// When the search box has focus, we want first chance handling of any key down events so we can handle the up/down and escape keys the way we want
FilterTextBox->SetOnKeyDownHandler(FOnKeyDown::CreateSP(this, &SGraphActionMenu::OnKeyDown));
if (!InArgs._ShowFilterTextBox)
{
FilterTextBox->SetVisibility(EVisibility::Collapsed);
}
// Get all actions.
RefreshAllActions(false);
}
void SGraphActionMenu::RefreshAllActions(bool bPreserveExpansion, bool bHandleOnSelectionEvent/*=true*/)
{
// Save Selection (of only the first selected thing)
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
TSharedPtr<FGraphActionNode> SelectedAction = SelectedNodes.Num() > 0 ? SelectedNodes[0] : nullptr;
AllActions.Empty();
OnCollectAllActions.ExecuteIfBound(AllActions);
GenerateFilteredItems(bPreserveExpansion);
// Re-apply selection #0 if possible
if (SelectedAction.IsValid())
{
// Clear the selection, we will be re-selecting the previous action
TreeView->ClearSelection();
if(bHandleOnSelectionEvent)
{
SelectItemByName(*SelectedAction->GetDisplayName().ToString(), ESelectInfo::OnMouseClick, SelectedAction->SectionID, SelectedNodes[0]->IsCategoryNode());
}
else
{
// If we do not want to handle the selection, set it directly so it will reselect the item but not handle the event.
SelectItemByName(*SelectedAction->GetDisplayName().ToString(), ESelectInfo::Direct, SelectedAction->SectionID, SelectedNodes[0]->IsCategoryNode());
}
}
}
void SGraphActionMenu::GetSectionExpansion(TMap<int32, bool>& SectionExpansion) const
{
}
void SGraphActionMenu::SetSectionExpansion(const TMap<int32, bool>& InSectionExpansion)
{
for ( auto& PossibleSection : FilteredRootAction->Children )
{
if ( PossibleSection->IsSectionHeadingNode() )
{
const bool* IsExpanded = InSectionExpansion.Find(PossibleSection->SectionID);
if ( IsExpanded != nullptr )
{
TreeView->SetItemExpansion(PossibleSection, *IsExpanded);
}
}
}
}
TSharedRef<SEditableTextBox> SGraphActionMenu::GetFilterTextBox()
{
return FilterTextBox.ToSharedRef();
}
void SGraphActionMenu::GetSelectedActions(TArray< TSharedPtr<FEdGraphSchemaAction> >& OutSelectedActions) const
{
OutSelectedActions.Empty();
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
for ( int32 NodeIndex = 0; NodeIndex < SelectedNodes.Num(); NodeIndex++ )
{
OutSelectedActions.Append( SelectedNodes[NodeIndex]->Actions );
}
}
}
void SGraphActionMenu::OnRequestRenameOnActionNode()
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
if (!SelectedNodes[0]->BroadcastRenameRequest())
{
TreeView->RequestScrollIntoView(SelectedNodes[0]);
}
}
}
bool SGraphActionMenu::CanRequestRenameOnActionNode() const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() == 1 && OnCanRenameSelectedAction.IsBound())
{
return OnCanRenameSelectedAction.Execute(SelectedNodes[0]);
}
return false;
}
FString SGraphActionMenu::GetSelectedCategoryName() const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
return (SelectedNodes.Num() > 0) ? SelectedNodes[0]->GetDisplayName().ToString() : FString();
}
void SGraphActionMenu::GetSelectedCategorySubActions(TArray<TSharedPtr<FEdGraphSchemaAction>>& OutActions) const
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
for ( int32 SelectionIndex = 0; SelectionIndex < SelectedNodes.Num(); SelectionIndex++ )
{
if ( SelectedNodes[SelectionIndex].IsValid() )
{
GetCategorySubActions(SelectedNodes[SelectionIndex], OutActions);
}
}
}
void SGraphActionMenu::GetCategorySubActions(TWeakPtr<FGraphActionNode> InAction, TArray<TSharedPtr<FEdGraphSchemaAction>>& OutActions) const
{
if(InAction.IsValid())
{
TSharedPtr<FGraphActionNode> CategoryNode = InAction.Pin();
TArray<TSharedPtr<FGraphActionNode>> Children;
CategoryNode->GetLeafNodes(Children);
for (int32 i = 0; i < Children.Num(); ++i)
{
TSharedPtr<FGraphActionNode> CurrentChild = Children[i];
if (CurrentChild.IsValid() && CurrentChild->IsActionNode())
{
for ( int32 ActionIndex = 0; ActionIndex != CurrentChild->Actions.Num(); ActionIndex++ )
{
OutActions.Add(CurrentChild->Actions[ActionIndex]);
}
}
}
}
}
bool SGraphActionMenu::SelectItemByName(const FName& ItemName, ESelectInfo::Type SelectInfo, int32 SectionId/* = INDEX_NONE */, bool bIsCategory/* = false*/)
{
if (ItemName != NAME_None)
{
TSharedPtr<FGraphActionNode> SelectionNode;
TArray<TSharedPtr<FGraphActionNode>> GraphNodes;
FilteredRootAction->GetAllNodes(GraphNodes);
for (int32 i = 0; i < GraphNodes.Num() && !SelectionNode.IsValid(); ++i)
{
TSharedPtr<FGraphActionNode> CurrentGraphNode = GraphNodes[i];
FEdGraphSchemaAction* GraphAction = CurrentGraphNode->GetPrimaryAction().Get();
// If the user is attempting to select a category, make sure it's a category
if( CurrentGraphNode->IsCategoryNode() == bIsCategory )
{
if(SectionId == INDEX_NONE || CurrentGraphNode->SectionID == SectionId)
{
if (GraphAction)
{
if ((OnActionMatchesName.IsBound() && OnActionMatchesName.Execute(GraphAction, ItemName)) || GraphActionMenuHelpers::ActionMatchesName(GraphAction, ItemName))
{
SelectionNode = GraphNodes[i];
break;
}
}
if (CurrentGraphNode->GetDisplayName().ToString() == FName::NameToDisplayString(ItemName.ToString(), false))
{
SelectionNode = CurrentGraphNode;
break;
}
}
}
// One of the children may match
for(int32 ChildIdx = 0; ChildIdx < CurrentGraphNode->Children.Num() && !SelectionNode.IsValid(); ++ChildIdx)
{
TSharedPtr<FGraphActionNode> CurrentChildNode = CurrentGraphNode->Children[ChildIdx];
for ( int32 ActionIndex = 0; ActionIndex < CurrentChildNode->Actions.Num(); ActionIndex++ )
{
FEdGraphSchemaAction* ChildGraphAction = CurrentChildNode->Actions[ActionIndex].Get();
// If the user is attempting to select a category, make sure it's a category
if( CurrentChildNode->IsCategoryNode() == bIsCategory )
{
if(SectionId == INDEX_NONE || CurrentChildNode->SectionID == SectionId)
{
if(ChildGraphAction)
{
if ((OnActionMatchesName.IsBound() && OnActionMatchesName.Execute(ChildGraphAction, ItemName)) || GraphActionMenuHelpers::ActionMatchesName(ChildGraphAction, ItemName))
{
SelectionNode = GraphNodes[i]->Children[ChildIdx];
break;
}
}
else if (CurrentChildNode->GetDisplayName().ToString() == FName::NameToDisplayString(ItemName.ToString(), false))
{
SelectionNode = CurrentChildNode;
break;
}
}
}
}
}
}
if(SelectionNode.IsValid())
{
// Expand the parent nodes
for (TSharedPtr<FGraphActionNode> ParentAction = SelectionNode->GetParentNode().Pin(); ParentAction.IsValid(); ParentAction = ParentAction->GetParentNode().Pin())
{
TreeView->SetItemExpansion(ParentAction, true);
}
// Select the node
TreeView->SetSelection(SelectionNode,SelectInfo);
TreeView->RequestScrollIntoView(SelectionNode);
return true;
}
}
else
{
TreeView->ClearSelection();
return true;
}
return false;
}
void SGraphActionMenu::ExpandCategory(const FText& CategoryName)
{
if (!CategoryName.IsEmpty())
{
TArray<TSharedPtr<FGraphActionNode>> GraphNodes;
FilteredRootAction->GetAllNodes(GraphNodes);
for (int32 i = 0; i < GraphNodes.Num(); ++i)
{
if (GraphNodes[i]->GetDisplayName().EqualTo(CategoryName))
{
GraphNodes[i]->ExpandAllChildren(TreeView);
}
}
}
}
static bool CompareGraphActionNode(TSharedPtr<FGraphActionNode> A, TSharedPtr<FGraphActionNode> B)
{
check(A.IsValid());
check(B.IsValid());
// First check grouping is the same
if (A->GetDisplayName().ToString() != B->GetDisplayName().ToString())
{
return false;
}
if (A->HasValidAction() && B->HasValidAction())
{
return A->GetPrimaryAction()->GetMenuDescription().CompareTo(B->GetPrimaryAction()->GetMenuDescription()) == 0;
}
else if(!A->HasValidAction() && !B->HasValidAction())
{
return true;
}
else
{
return false;
}
}
template<typename ItemType, typename ComparisonType>
void RestoreExpansionState(TSharedPtr< STreeView<ItemType> > InTree, const TArray<ItemType>& ItemSource, const TSet<ItemType>& OldExpansionState, ComparisonType ComparisonFunction)
{
check(InTree.IsValid());
// Iterate over new tree items
for(int32 ItemIdx=0; ItemIdx<ItemSource.Num(); ItemIdx++)
{
ItemType NewItem = ItemSource[ItemIdx];
// Look through old expansion state
for (typename TSet<ItemType>::TConstIterator OldExpansionIter(OldExpansionState); OldExpansionIter; ++OldExpansionIter)
{
const ItemType OldItem = *OldExpansionIter;
// See if this matches this new item
if(ComparisonFunction(OldItem, NewItem))
{
// It does, so expand it
InTree->SetItemExpansion(NewItem, true);
}
}
}
}
void SGraphActionMenu::GenerateFilteredItems(bool bPreserveExpansion)
{
// First, save off current expansion state
TSet< TSharedPtr<FGraphActionNode> > OldExpansionState;
if(bPreserveExpansion)
{
TreeView->GetExpandedItems(OldExpansionState);
}
// Clear the filtered root action
FilteredRootAction->ClearChildren();
// Collect the list of always visible sections if any, and force the creation of those sections.
if ( OnCollectStaticSections.IsBound() )
{
TArray<int32> StaticSectionIDs;
OnCollectStaticSections.Execute(StaticSectionIDs);
for ( int32 i = 0; i < StaticSectionIDs.Num(); i++ )
{
FilteredRootAction->AddSection(0, StaticSectionIDs[i]);
}
}
// Trim and sanitized the filter text (so that it more likely matches the action descriptions)
FString TrimmedFilterString = FText::TrimPrecedingAndTrailing(GetFilterText()).ToString();
// Tokenize the search box text into a set of terms; all of them must be present to pass the filter
TArray<FString> FilterTerms;
TrimmedFilterString.ParseIntoArray(FilterTerms, TEXT(" "), true);
for (auto& String : FilterTerms)
{
String = String.ToLower();
}
// Generate a list of sanitized versions of the strings
TArray<FString> SanitizedFilterTerms;
for (int32 iFilters = 0; iFilters < FilterTerms.Num() ; iFilters++)
{
FString EachString = FName::NameToDisplayString( FilterTerms[iFilters], false );
EachString = EachString.Replace( TEXT( " " ), TEXT( "" ) );
SanitizedFilterTerms.Add( EachString );
}
ensure( SanitizedFilterTerms.Num() == FilterTerms.Num() );// Both of these should match !
const bool bRequiresFiltering = FilterTerms.Num() > 0;
int32 BestMatchCount = 0;
int32 BestMatchIndex = INDEX_NONE;
for (int32 CurTypeIndex=0; CurTypeIndex < AllActions.GetNumActions(); ++CurTypeIndex)
{
FGraphActionListBuilderBase::ActionGroup& CurrentAction = AllActions.GetAction( CurTypeIndex );
// If we're filtering, search check to see if we need to show this action
bool bShowAction = true;
int32 EachWeight = 0;
if (bRequiresFiltering)
{
// Combine the actions string, separate with \n so terms don't run into each other, and remove the spaces (incase the user is searching for a variable)
// In the case of groups containing multiple actions, they will have been created and added at the same place in the code, using the same description
// and keywords, so we only need to use the first one for filtering.
const FString& SearchText = CurrentAction.GetSearchTextForFirstAction();
FString EachTermSanitized;
for (int32 FilterIndex = 0; (FilterIndex < FilterTerms.Num()) && bShowAction; ++FilterIndex)
{
const bool bMatchesTerm = (SearchText.Contains(FilterTerms[FilterIndex], ESearchCase::CaseSensitive) || (SearchText.Contains(SanitizedFilterTerms[FilterIndex], ESearchCase::CaseSensitive) == true));
bShowAction = bShowAction && bMatchesTerm;
}
// Only if we are going to show the action do we want to generate the weight of the filter text
if (bShowAction)
{
// Get the 'weight' of this in relation to the filter
EachWeight = GetActionFilteredWeight( CurrentAction, FilterTerms, SanitizedFilterTerms );
}
}
if (bShowAction)
{
// If this action has a greater relevance than others, cache its index.
if( EachWeight > BestMatchCount )
{
BestMatchCount = EachWeight;
BestMatchIndex = CurTypeIndex;
}
FilteredRootAction->AddChild(CurrentAction);
}
}
FilteredRootAction->SortChildren(bAlphaSortItems, /*bRecursive =*/true);
TreeView->RequestTreeRefresh();
// Update the filtered list (needs to be done in a separate pass because the list is sorted as items are inserted)
FilteredActionNodes.Empty();
FilteredRootAction->GetLeafNodes(FilteredActionNodes);
// Get _all_ new nodes (flattened tree basically)
TArray< TSharedPtr<FGraphActionNode> > AllNodes;
FilteredRootAction->GetAllNodes(AllNodes);
// If theres a BestMatchIndex find it in the actions nodes and select it (maybe this should check the current selected suggestion first ?)
if( BestMatchIndex != INDEX_NONE )
{
FGraphActionListBuilderBase::ActionGroup& FilterSelectAction = AllActions.GetAction( BestMatchIndex );
if( FilterSelectAction.Actions[0].IsValid() == true )
{
for (int32 iNode = 0; iNode < FilteredActionNodes.Num() ; iNode++)
{
if( FilteredActionNodes[ iNode ].Get()->GetPrimaryAction() == FilterSelectAction.Actions[ 0 ] )
{
SelectedSuggestion = iNode;
}
}
}
}
// Make sure the selected suggestion stays within the filtered list
if ((SelectedSuggestion >= 0) && (FilteredActionNodes.Num() > 0))
{
//@TODO: Should try to actually maintain the highlight on the same item if it survived the filtering
SelectedSuggestion = FMath::Clamp<int32>(SelectedSuggestion, 0, FilteredActionNodes.Num() - 1);
MarkActiveSuggestion();
}
else
{
SelectedSuggestion = INDEX_NONE;
}
if (ShouldExpandNodes())
{
// Expand all
FilteredRootAction->ExpandAllChildren(TreeView);
}
else
{
// Expand to match the old state
RestoreExpansionState< TSharedPtr<FGraphActionNode> >(TreeView, AllNodes, OldExpansionState, CompareGraphActionNode);
}
}
int32 SGraphActionMenu::GetActionFilteredWeight( const FGraphActionListBuilderBase::ActionGroup& InCurrentAction, const TArray<FString>& InFilterTerms, const TArray<FString>& InSanitizedFilterTerms )
{
// The overall 'weight'
int32 TotalWeight = 0;
// Some simple weight figures to help find the most appropriate match
const int32 WholeMatchWeightMultiplier = 2;
const int32 DescriptionWeight = 10;
const int32 CategoryWeight = 1;
const int32 NodeTitleWeight = 1;
const int32 KeywordWeight = 4;
// Helper array
struct FArrayWithWeight
{
FArrayWithWeight(const TArray< FString >* InArray, int32 InWeight)
: Array(InArray)
, Weight(InWeight)
{
}
const TArray< FString >* Array;
int32 Weight;
};
// Setup an array of arrays so we can do a weighted search
TArray< FArrayWithWeight > WeightedArrayList;
int32 Action = 0;
if( InCurrentAction.Actions[Action].IsValid() == true )
{
// Combine the actions string, separate with \n so terms don't run into each other, and remove the spaces (incase the user is searching for a variable)
// In the case of groups containing multiple actions, they will have been created and added at the same place in the code, using the same description
// and keywords, so we only need to use the first one for filtering.
const FString& SearchText = InCurrentAction.GetSearchTextForFirstAction();
// First the keywords
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchKeywordsArrayForFirstAction(), KeywordWeight));
// The description
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetMenuDescriptionArrayForFirstAction(), DescriptionWeight));
// The node search title weight
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchTitleArrayForFirstAction(), NodeTitleWeight));
// The category
WeightedArrayList.Add(FArrayWithWeight(&InCurrentAction.GetSearchCategoryArrayForFirstAction(), CategoryWeight));
// Now iterate through all the filter terms and calculate a 'weight' using the values and multipliers
const FString* EachTerm = nullptr;
const FString* EachTermSanitized = nullptr;
for (int32 FilterIndex = 0; FilterIndex < InFilterTerms.Num(); ++FilterIndex)
{
EachTerm = &InFilterTerms[FilterIndex];
EachTermSanitized = &InSanitizedFilterTerms[FilterIndex];
if( SearchText.Contains( *EachTerm, ESearchCase::CaseSensitive ) )
{
TotalWeight += 2;
}
else if (SearchText.Contains(*EachTermSanitized, ESearchCase::CaseSensitive))
{
TotalWeight++;
}
// Now check the weighted lists (We could further improve the hit weight by checking consecutive word matches)
for (int32 iFindCount = 0; iFindCount < WeightedArrayList.Num() ; iFindCount++)
{
int32 WeightPerList = 0;
const TArray<FString>& KeywordArray = *WeightedArrayList[iFindCount].Array;
int32 EachWeight = WeightedArrayList[iFindCount].Weight;
int32 WholeMatchCount = 0;
for (int32 iEachWord = 0; iEachWord < KeywordArray.Num() ; iEachWord++)
{
// If we get an exact match weight the find count to get exact matches higher priority
if (KeywordArray[iEachWord].StartsWith(*EachTerm, ESearchCase::CaseSensitive))
{
if (iEachWord == 0)
{
WeightPerList += EachWeight * WholeMatchWeightMultiplier;
}
else
{
WeightPerList += EachWeight;
}
WholeMatchCount++;
}
else if (KeywordArray[iEachWord].Contains(*EachTerm, ESearchCase::CaseSensitive))
{
WeightPerList += EachWeight;
}
if (KeywordArray[iEachWord].StartsWith(*EachTermSanitized, ESearchCase::CaseSensitive))
{
if (iEachWord == 0)
{
WeightPerList += EachWeight * WholeMatchWeightMultiplier;
}
else
{
WeightPerList += EachWeight;
}
WholeMatchCount++;
}
else if (KeywordArray[iEachWord].Contains(*EachTermSanitized, ESearchCase::CaseSensitive))
{
WeightPerList += EachWeight / 2;
}
}
// Increase the weight if theres a larger % of matches in the keyword list
if( WholeMatchCount != 0 )
{
int32 PercentAdjust = ( 100 / KeywordArray.Num() ) * WholeMatchCount;
WeightPerList *= PercentAdjust;
}
TotalWeight += WeightPerList;
}
}
}
return TotalWeight;
}
// Returns true if the tree should be autoexpanded
bool SGraphActionMenu::ShouldExpandNodes() const
{
// Expand all the categories that have filter results, or when there are only a few to show
const bool bFilterActive = !GetFilterText().IsEmpty();
const bool bOnlyAFewTotal = AllActions.GetNumActions() < 10;
return bFilterActive || bOnlyAFewTotal || bAutoExpandActionMenu;
}
bool SGraphActionMenu::CanRenameNode(TWeakPtr<FGraphActionNode> InNode) const
{
return !OnCanRenameSelectedAction.Execute(InNode);
}
void SGraphActionMenu::OnFilterTextChanged( const FText& InFilterText )
{
// Reset the selection if the string is empty
if( InFilterText.IsEmpty() == true )
{
SelectedSuggestion = INDEX_NONE;
}
GenerateFilteredItems(false);
}
void SGraphActionMenu::OnFilterTextCommitted(const FText& InText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnEnter)
{
TryToSpawnActiveSuggestion();
}
}
bool SGraphActionMenu::TryToSpawnActiveSuggestion()
{
TArray< TSharedPtr<FGraphActionNode> > SelectionList = TreeView->GetSelectedItems();
if (SelectionList.Num() == 1)
{
// This isnt really a keypress - its Direct, but its always called from a keypress function. (Maybe pass the selectinfo in ?)
OnItemSelected( SelectionList[0], ESelectInfo::OnKeyPress );
return true;
}
else if (FilteredActionNodes.Num() == 1)
{
OnItemSelected( FilteredActionNodes[0], ESelectInfo::OnKeyPress );
return true;
}
return false;
}
void SGraphActionMenu::OnGetChildrenForCategory( TSharedPtr<FGraphActionNode> InItem, TArray< TSharedPtr<FGraphActionNode> >& OutChildren )
{
if (InItem->Children.Num())
{
OutChildren = InItem->Children;
}
}
void SGraphActionMenu::OnNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, TWeakPtr< FGraphActionNode > InAction )
{
if(OnCategoryTextCommitted.IsBound())
{
OnCategoryTextCommitted.Execute(NewText, InTextCommit, InAction);
}
}
void SGraphActionMenu::OnItemScrolledIntoView( TSharedPtr<FGraphActionNode> InActionNode, const TSharedPtr<ITableRow>& InWidget )
{
if (InActionNode->IsRenameRequestPending())
{
InActionNode->BroadcastRenameRequest();
}
}
TSharedRef<ITableRow> SGraphActionMenu::MakeWidget( TSharedPtr<FGraphActionNode> InItem, const TSharedRef<STableViewBase>& OwnerTable, bool bIsReadOnly )
{
TSharedPtr<IToolTip> SectionToolTip;
if ( InItem->IsSectionHeadingNode() )
{
if ( OnGetSectionToolTip.IsBound() )
{
SectionToolTip = OnGetSectionToolTip.Execute(InItem->SectionID);
}
}
// In the case of FGraphActionNodes that have multiple actions, all of the actions will
// have the same text as they will have been created at the same point - only the actual
// action itself will differ, which is why parts of this function only refer to InItem->Actions[0]
// rather than iterating over the array
// Create the widget but do not add any content, the widget is needed to pass the IsSelectedExclusively function down to the potential SInlineEditableTextBlock widget
TSharedPtr< STableRow< TSharedPtr<FGraphActionNode> > > TableRow;
if ( InItem->IsSectionHeadingNode() )
{
TableRow = SNew(SCategoryHeaderTableRow< TSharedPtr<FGraphActionNode> >, OwnerTable)
.ToolTip(SectionToolTip);
}
else
{
const FTableRowStyle* Style = bUseSectionStyling ? &FEditorStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.DarkRow") : &FCoreStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row");
TableRow = SNew(STableRow< TSharedPtr<FGraphActionNode> >, OwnerTable)
.Style(Style)
.OnDragDetected(this, &SGraphActionMenu::OnItemDragDetected)
.ShowSelection(!InItem->IsSeparator());
}
TSharedPtr<SHorizontalBox> RowContainer;
TableRow->SetRowContent
(
SAssignNew(RowContainer, SHorizontalBox)
);
TSharedPtr<SWidget> RowContent;
FMargin RowPadding = FMargin(0, 2);
if( InItem->IsActionNode() )
{
check(InItem->HasValidAction());
FCreateWidgetForActionData CreateData(&InItem->OnRenameRequest());
CreateData.Action = InItem->GetPrimaryAction();
CreateData.HighlightText = TAttribute<FText>(this, &SGraphActionMenu::GetFilterText);
CreateData.MouseButtonDownDelegate = FCreateWidgetMouseButtonDown::CreateSP( this, &SGraphActionMenu::OnMouseButtonDownEvent );
if(OnCreateWidgetForAction.IsBound())
{
CreateData.IsRowSelectedDelegate = FIsSelected::CreateSP( TableRow.Get(), &STableRow< TSharedPtr<FGraphActionNode> >::IsSelected );
CreateData.bIsReadOnly = bIsReadOnly;
CreateData.bHandleMouseButtonDown = false; //Default to NOT using the delegate. OnCreateWidgetForAction can set to true if we need it
RowContent = OnCreateWidgetForAction.Execute( &CreateData );
}
else
{
RowContent = SNew(SDefaultGraphActionWidget, &CreateData);
}
}
else if( InItem->IsCategoryNode() )
{
TWeakPtr< FGraphActionNode > WeakItem = InItem;
// Hook up the delegate for verifying the category action is read only or not
SGraphActionCategoryWidget::FArguments ReadOnlyArgument;
if(bIsReadOnly)
{
ReadOnlyArgument.IsReadOnly(bIsReadOnly);
}
else
{
ReadOnlyArgument.IsReadOnly(this, &SGraphActionMenu::CanRenameNode, WeakItem);
}
TSharedRef<SGraphActionCategoryWidget> CategoryWidget =
SNew(SGraphActionCategoryWidget, InItem)
.HighlightText(this, &SGraphActionMenu::GetFilterText)
.OnTextCommitted(this, &SGraphActionMenu::OnNameTextCommitted, TWeakPtr< FGraphActionNode >(InItem))
.IsSelected(TableRow.Get(), &STableRow< TSharedPtr<FGraphActionNode> >::IsSelectedExclusively)
.IsReadOnly(ReadOnlyArgument._IsReadOnly);
if(!bIsReadOnly)
{
InItem->OnRenameRequest().BindSP( CategoryWidget->InlineWidget.Pin().Get(), &SInlineEditableTextBlock::EnterEditingMode );
}
RowContent = CategoryWidget;
}
else if( InItem->IsSeparator() )
{
RowPadding = FMargin(0);
FText SectionTitle;
if( OnGetSectionTitle.IsBound() )
{
SectionTitle = OnGetSectionTitle.Execute(InItem->SectionID);
}
if( SectionTitle.IsEmpty() )
{
RowContent = SNew( SVerticalBox )
.Visibility(EVisibility::HitTestInvisible)
+ SVerticalBox::Slot()
.AutoHeight()
// Add some empty space before the line, and a tiny bit after it
.Padding( 0.0f, 5.f, 0.0f, 5.f )
[
SNew( SBorder )
// We'll use the border's padding to actually create the horizontal line
.Padding(FEditorStyle::GetMargin(TEXT("Menu.Separator.Padding")))
// Separator graphic
.BorderImage( FEditorStyle::GetBrush( TEXT( "Menu.Separator" ) ) )
];
}
else
{
RowContent = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(SRichTextBlock)
.Text(SectionTitle)
.DecoratorStyleSet(&FEditorStyle::Get())
.TextStyle(FEditorStyle::Get(), "DetailsView.CategoryTextStyle")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
.Padding(FMargin(0,0,2,0))
[
OnGetSectionWidget.IsBound() ? OnGetSectionWidget.Execute(TableRow.ToSharedRef(), InItem->SectionID) : SNullWidget::NullWidget
];
}
}
TSharedPtr<SExpanderArrow> ExpanderWidget;
if (OnCreateCustomRowExpander.IsBound())
{
FCustomExpanderData CreateData;
CreateData.TableRow = TableRow;
CreateData.WidgetContainer = RowContainer;
if (InItem->IsActionNode())
{
check(InItem->HasValidAction());
CreateData.RowAction = InItem->GetPrimaryAction();
}
ExpanderWidget = OnCreateCustomRowExpander.Execute(CreateData);
}
else
{
ExpanderWidget =
SNew(SExpanderArrow, TableRow)
.BaseIndentLevel(1);
}
RowContainer->AddSlot()
.AutoWidth()
.VAlign(VAlign_Fill)
.HAlign(HAlign_Right)
[
ExpanderWidget.ToSharedRef()
];
RowContainer->AddSlot()
.FillWidth(1.0)
.Padding(RowPadding)
[
RowContent.ToSharedRef()
];
return TableRow.ToSharedRef();
}
FText SGraphActionMenu::GetFilterText() const
{
// If there is an external source for the filter, use that text instead
if(OnGetFilterText.IsBound())
{
return OnGetFilterText.Execute();
}
return FilterTextBox->GetText();;
}
void SGraphActionMenu::OnItemSelected( TSharedPtr< FGraphActionNode > InSelectedItem, ESelectInfo::Type SelectInfo )
{
if (!bIgnoreUIUpdate)
{
HandleSelection(InSelectedItem, SelectInfo);
}
}
void SGraphActionMenu::OnItemDoubleClicked( TSharedPtr< FGraphActionNode > InClickedItem )
{
if ( InClickedItem.IsValid() && !bIgnoreUIUpdate )
{
if ( InClickedItem->IsActionNode() )
{
OnActionDoubleClicked.ExecuteIfBound(InClickedItem->Actions);
}
else if (InClickedItem->Children.Num())
{
TreeView->SetItemExpansion(InClickedItem, !TreeView->IsItemExpanded(InClickedItem));
}
}
}
FReply SGraphActionMenu::OnItemDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
// Start a function-call drag event for any entry that can be called by kismet
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
TArray< TSharedPtr<FGraphActionNode> > SelectedNodes = TreeView->GetSelectedItems();
if(SelectedNodes.Num() > 0)
{
TSharedPtr<FGraphActionNode> Node = SelectedNodes[0];
// Dragging a ctaegory
if(Node.IsValid() && Node->IsCategoryNode())
{
if(OnCategoryDragged.IsBound())
{
return OnCategoryDragged.Execute(Node->GetCategoryPath(), MouseEvent);
}
}
// Dragging an action
else
{
if(OnActionDragged.IsBound())
{
TArray< TSharedPtr<FEdGraphSchemaAction> > Actions;
GetSelectedActions(Actions);
return OnActionDragged.Execute(Actions, MouseEvent);
}
}
}
}
return FReply::Unhandled();
}
bool SGraphActionMenu::OnMouseButtonDownEvent( TWeakPtr<FEdGraphSchemaAction> InAction )
{
bool bResult = false;
if( (!bIgnoreUIUpdate) && InAction.IsValid() )
{
TArray< TSharedPtr<FGraphActionNode> > SelectionList = TreeView->GetSelectedItems();
TSharedPtr<FGraphActionNode> SelectedNode;
if (SelectionList.Num() == 1)
{
SelectedNode = SelectionList[0];
}
else if (FilteredActionNodes.Num() == 1)
{
SelectedNode = FilteredActionNodes[0];
}
if (SelectedNode.IsValid() && SelectedNode->HasValidAction())
{
if( SelectedNode->GetPrimaryAction().Get() == InAction.Pin().Get() )
{
bResult = HandleSelection( SelectedNode, ESelectInfo::OnMouseClick );
}
}
}
return bResult;
}
FReply SGraphActionMenu::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& KeyEvent )
{
int32 SelectionDelta = 0;
// Escape dismisses the menu without placing a node
if (KeyEvent.GetKey() == EKeys::Escape)
{
FSlateApplication::Get().DismissAllMenus();
return FReply::Handled();
}
else if ((KeyEvent.GetKey() == EKeys::Enter) && !bIgnoreUIUpdate)
{
return TryToSpawnActiveSuggestion() ? FReply::Handled() : FReply::Unhandled();
}
else if (FilteredActionNodes.Num() > 0 && !FilterTextBox->GetText().IsEmpty())
{
// Up and down move thru the filtered node list
if (KeyEvent.GetKey() == EKeys::Up)
{
SelectionDelta = -1;
}
else if (KeyEvent.GetKey() == EKeys::Down)
{
SelectionDelta = +1;
}
if (SelectionDelta != 0)
{
// If we have no selected suggestion then we need to use the items in the root to set the selection and set the focus
if( SelectedSuggestion == INDEX_NONE )
{
SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredRootAction->Children.Num()) % FilteredRootAction->Children.Num();
MarkActiveSuggestion();
return FReply::Handled();
}
//Move up or down one, wrapping around
SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredActionNodes.Num()) % FilteredActionNodes.Num();
MarkActiveSuggestion();
return FReply::Handled();
}
}
else
{
// When all else fails, it means we haven't filtered the list and we want to handle it as if we were just scrolling through a normal tree view
return TreeView->OnKeyDown(FindChildGeometry(MyGeometry, TreeView.ToSharedRef()), KeyEvent);
}
return FReply::Unhandled();
}
void SGraphActionMenu::MarkActiveSuggestion()
{
TGuardValue<bool> PreventSelectionFromTriggeringCommit(bIgnoreUIUpdate, true);
if (SelectedSuggestion >= 0)
{
TSharedPtr<FGraphActionNode>& ActionToSelect = FilteredActionNodes[SelectedSuggestion];
TreeView->SetSelection(ActionToSelect);
TreeView->RequestScrollIntoView(ActionToSelect);
}
else
{
TreeView->ClearSelection();
}
}
void SGraphActionMenu::AddReferencedObjects( FReferenceCollector& Collector )
{
for (int32 CurTypeIndex=0; CurTypeIndex < AllActions.GetNumActions(); ++CurTypeIndex)
{
FGraphActionListBuilderBase::ActionGroup& Action = AllActions.GetAction( CurTypeIndex );
for ( int32 ActionIndex = 0; ActionIndex < Action.Actions.Num(); ActionIndex++ )
{
Action.Actions[ActionIndex]->AddReferencedObjects(Collector);
}
}
}
bool SGraphActionMenu::HandleSelection( TSharedPtr< FGraphActionNode > &InSelectedItem, ESelectInfo::Type InSelectionType )
{
bool bResult = false;
if( OnActionSelected.IsBound() )
{
if ( InSelectedItem.IsValid() && InSelectedItem->IsActionNode() )
{
OnActionSelected.Execute(InSelectedItem->Actions, InSelectionType);
bResult = true;
}
else
{
OnActionSelected.Execute(TArray< TSharedPtr<FEdGraphSchemaAction> >(), InSelectionType);
bResult = true;
}
}
return bResult;
}
Copying //UE4/Dev-Blueprints to Dev-Main (//UE4/Dev-Main) #lockdown Nick.Penwarden ========================== MAJOR FEATURES + CHANGES ========================== Change 2806214 on 2015/12/16 by Michael.Schoell Connection draw policy refactored into a factory system. PR#1663 #jira UE-22123 - GitHub 1663 : Implement PinConnectionPolicy factory system Change 2806845 on 2015/12/17 by Maciej.Mroz Blueprint C++ Conversion: fixed varioaus problem. Some limitations of included header list were reverted :( Any compilation-time optimization are premature (as far as Orion cannot be compiled). #codereview Dan.Oconnor Change 2806892 on 2015/12/17 by Maciej.Mroz Blueprint C++ Conversion: fixed compilation error "fatal error C1001: An internal error has occurred in the compiler." Change 2807020 on 2015/12/17 by Michael.Schoell Recursively expand all tree items in graph context menus or My Blueprint window by holding shift when clicking on an arrow. Change 2807639 on 2015/12/17 by Maciej.Mroz BP C++ conversion: Fix for const-correctness. Included generated headers in structs. Change 2807880 on 2015/12/17 by Mike.Beach PR #1865 : Render Kismet graph pin color box with alpha https://github.com/EpicGames/UnrealEngine/pull/1865 #github 1865 #1865 #jira UE-23863 Change 2808538 on 2015/12/18 by Maciej.Mroz Workaround for UE-24467. Some corrupted files can be opened and fixed. #codereivew Dan.Oconnor, Michael.Schoell Change 2808540 on 2015/12/18 by Maciej.Mroz BP C++ conversion: fixed const-correctness related issues Change 2809333 on 2015/12/18 by Phillip.Kavan [UE-23452] SCS/UCS component instancing optimizations change summary: - added FArchive::FCustomPropertyListNode - modified various parts of serialization code to accomodate custom property lists - added cooked component instancing data + supporting APIs to UBlueprintGeneratedClass Change 2810216 on 2015/12/21 by Maciej.Mroz Blueprint C++ Conversion: - Fixed Compiler Error C2026 (too big string) - Fixed a lot of errors related to const correctness. "NativeConst" and "NativeConstTemplateArg" MetaData added. - Fixed bunch of problems with TEnumAsByte - Fixed for error C2059: syntax error : 'bad suffix on number' - when struct name starts with a digit - Fixed int -> bool conversion #codereview Mike.Beach, Dan.Oconnor, Steve.Robb Change 2810397 on 2015/12/21 by Maciej.Mroz Blueprint C++ Conversion: Fixed Compiler Error C1091 (too big string) Change 2810638 on 2015/12/21 by Dan.Oconnor Timers for measuring VM overhead, stats for tracking individual script functions improved (now nicludes ProcessEvent). Stats system is expected to avoid double counting for object and function stats. Change 2811038 on 2015/12/22 by Phillip.Kavan [UE-23452] Fix uninitialized variables in cooked component data. - should eliminate crashes introduced by last change Change 2811054 on 2015/12/22 by Phillip.Kavan [UE-23452] Additional fix for uninitialized USTRUCT property. Change 2811254 on 2015/12/22 by Dan.Oconnor More script function detail in stats Change 2811325 on 2015/12/22 by Maciej.Mroz Blueprint C++ Conversion: improved: ExcludedBlueprintTypes list #codereview Dan.Oconnor Change 2812316 on 2015/12/24 by Michael.Schoell Added more ByValue/ByRef test cases and added a way to dump all tests that are no longer succeeding. Tests Include: Verification that functions take and can modify by-ref values. ForEach loop verification. Tests for verifying that current functionality will continue to work after COPY injection and that "expected" functionality will work (and currently doesn't). More MegaArray tests. Change 2812318 on 2015/12/24 by Michael.Schoell Usability fix: Duplicating graphs in the MyBP window will now open and focus the new graph. Change 2813179 on 2015/12/29 by Michael.Schoell When promoting pins to variables, will no longer carry bIsReference, bIsConst, or bIsWeakPointer value to the new variable's type. Change 2813435 on 2015/12/30 by Michael.Schoell Submitting by-value/by-ref testing BP with more tests: More "Set Members..." tests to catch edge cases. More Array tests. Change 2813441 on 2015/12/30 by Michael.Schoell [CL 2842166 by Mike Beach in Main branch]
2016-01-25 13:25:51 -05:00
void SGraphActionMenu::OnSetExpansionRecursive(TSharedPtr<FGraphActionNode> InTreeNode, bool bInIsItemExpanded)
{
if (InTreeNode.IsValid() && InTreeNode->Children.Num())
{
TreeView->SetItemExpansion(InTreeNode, bInIsItemExpanded);
for (TSharedPtr<FGraphActionNode> Child : InTreeNode->Children)
{
OnSetExpansionRecursive(Child, bInIsItemExpanded);
}
}
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE