You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Slate Widget was failing, because of missing Slate dependencies. Testing introduced a couple of problems which all was fixed by this CL: 1. I introduced AdditionalDependencies in .uproject file and change "Add Code To Project..." procedure to fill this array if needed. UBT reads this field and builds the project with required modules. Needed for Slate classes. 2. Changed UHT to #include missing headers in generated.h files if it was missing an include for it's super class. It was causing problems if we were trying to add a subclass of BrushShape -- BrushShape.h didn't have #include "Brush.h" and UBrushShape was inheriting from UBrush. 3. Above problems also occured for Slate classes, but not all of them was UCLASSes, so I had to fixed that manually. 4. "Add Code To Project..." functionality was not invalidating UBT makefiles, which lead to omitting new source files during hot-reloading (even thought it was reporting a success). This change also should improve a bit performance, cause right now there is no "gathering" step -- there is only invalidate step which is a lot quicker. 5. Fixed "Selected Class Source" link to source class in Slate Widget and Slate Widget Style class. #codereview Robert.Manuszewski [CL 2481488 by Jaroslaw Palczynski in Main branch]
1493 lines
49 KiB
C++
1493 lines
49 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "GameProjectGenerationPrivatePCH.h"
|
|
#include "SourceCodeNavigation.h"
|
|
#include "ClassViewerModule.h"
|
|
#include "ClassViewerFilter.h"
|
|
#include "Editor/ContentBrowser/Public/ContentBrowserModule.h"
|
|
#include "Editor/ClassViewer/Private/SClassViewer.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Editor/Documentation/Public/IDocumentation.h"
|
|
#include "EditorClassUtils.h"
|
|
#include "SWizard.h"
|
|
#include "SHyperlink.h"
|
|
#include "TutorialMetaData.h"
|
|
#include "GameFramework/GameMode.h"
|
|
#include "GameFramework/GameState.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Camera/PlayerCameraManager.h"
|
|
#include "GameFramework/HUD.h"
|
|
#include "GameFramework/PlayerState.h"
|
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "AssetEditorManager.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "GameProjectGeneration"
|
|
|
|
|
|
struct FParentClassItem
|
|
{
|
|
FNewClassInfo ParentClassInfo;
|
|
|
|
FParentClassItem(const FNewClassInfo& InParentClassInfo)
|
|
: ParentClassInfo(InParentClassInfo)
|
|
{}
|
|
};
|
|
|
|
class FNativeClassParentFilter : public IClassViewerFilter
|
|
{
|
|
public:
|
|
FNativeClassParentFilter()
|
|
{
|
|
ProjectModules = GameProjectUtils::GetCurrentProjectModules();
|
|
}
|
|
|
|
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
|
|
{
|
|
// We allow a class that belongs to any module in the current project, as you don't actually choose the destination module until after you've selected your parent class
|
|
return GameProjectUtils::IsValidBaseClassForCreation(InClass, ProjectModules);
|
|
}
|
|
|
|
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
/** The list of currently available modules for this project */
|
|
TArray<FModuleContextInfo> ProjectModules;
|
|
};
|
|
|
|
static void FindPublicEngineHeaderFiles(TArray<FString>& OutFiles, const FString& Path)
|
|
{
|
|
TArray<FString> ModuleDirs;
|
|
IFileManager::Get().FindFiles(ModuleDirs, *(Path / TEXT("*")), false, true);
|
|
for (const FString& ModuleDir : ModuleDirs)
|
|
{
|
|
IFileManager::Get().FindFilesRecursive(OutFiles, *(Path / ModuleDir / TEXT("Classes")), TEXT("*.h"), true, false, false);
|
|
IFileManager::Get().FindFilesRecursive(OutFiles, *(Path / ModuleDir / TEXT("Public")), TEXT("*.h"), true, false, false);
|
|
}
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void SNewClassDialog::Construct( const FArguments& InArgs )
|
|
{
|
|
ClassDomain = InArgs._ClassDomain;
|
|
|
|
{
|
|
TArray<FModuleContextInfo> CurrentModules = GameProjectUtils::GetCurrentProjectModules();
|
|
check(CurrentModules.Num()); // this should never happen since GetCurrentProjectModules is supposed to add a dummy runtime module if the project currently has no modules
|
|
|
|
AvailableModules.Reserve(CurrentModules.Num());
|
|
for(const FModuleContextInfo& ModuleInfo : CurrentModules)
|
|
{
|
|
AvailableModules.Emplace(MakeShareable(new FModuleContextInfo(ModuleInfo)));
|
|
}
|
|
}
|
|
|
|
// If we've been given an initial path that maps to a valid project module, use that as our initial module and path
|
|
|
|
if (ClassDomain == EClassDomain::Blueprint)
|
|
{
|
|
NewClassPath = InArgs._InitialPath.IsEmpty() ? TEXT("/Game") : InArgs._InitialPath;
|
|
}
|
|
else if(!InArgs._InitialPath.IsEmpty())
|
|
{
|
|
const FString AbsoluteInitialPath = FPaths::ConvertRelativePathToFull(InArgs._InitialPath);
|
|
for(const auto& AvailableModule : AvailableModules)
|
|
{
|
|
if(AbsoluteInitialPath.StartsWith(AvailableModule->ModuleSourcePath))
|
|
{
|
|
SelectedModuleInfo = AvailableModule;
|
|
NewClassPath = AbsoluteInitialPath;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DefaultClassPrefix = InArgs._DefaultClassPrefix;
|
|
DefaultClassName = InArgs._DefaultClassName;
|
|
|
|
// If we didn't get given a valid path override (see above), try and automatically work out the best default module
|
|
// If we have a runtime module with the same name as our project, then use that
|
|
// Otherwise, set out default target module as the first runtime module in the list
|
|
if(ClassDomain == EClassDomain::Native && !SelectedModuleInfo.IsValid())
|
|
{
|
|
const FString ProjectName = FApp::GetGameName();
|
|
for(const auto& AvailableModule : AvailableModules)
|
|
{
|
|
if(AvailableModule->ModuleName == ProjectName)
|
|
{
|
|
SelectedModuleInfo = AvailableModule;
|
|
break;
|
|
}
|
|
|
|
if(AvailableModule->ModuleType == EHostType::Runtime)
|
|
{
|
|
SelectedModuleInfo = AvailableModule;
|
|
// keep going in case we find a better match
|
|
}
|
|
}
|
|
|
|
if (!SelectedModuleInfo.IsValid())
|
|
{
|
|
// No runtime modules? Just take the first available module then
|
|
SelectedModuleInfo = AvailableModules[0];
|
|
}
|
|
|
|
NewClassPath = SelectedModuleInfo->ModuleSourcePath;
|
|
}
|
|
|
|
ClassLocation = GameProjectUtils::EClassLocation::UserDefined; // the first call to UpdateInputValidity will set this correctly based on NewClassPath
|
|
|
|
ParentClassInfo = FNewClassInfo(InArgs._Class);
|
|
|
|
bShowFullClassTree = false;
|
|
|
|
LastPeriodicValidityCheckTime = 0;
|
|
PeriodicValidityCheckFrequency = 4;
|
|
bLastInputValidityCheckSuccessful = true;
|
|
bPreventPeriodicValidityChecksUntilNextChange = false;
|
|
|
|
FClassViewerInitializationOptions Options;
|
|
Options.Mode = EClassViewerMode::ClassPicker;
|
|
Options.DisplayMode = EClassViewerDisplayMode::TreeView;
|
|
Options.bIsActorsOnly = false;
|
|
Options.bIsPlaceableOnly = false;
|
|
Options.bIsBlueprintBaseOnly = false;
|
|
Options.bShowUnloadedBlueprints = false;
|
|
Options.bShowNoneOption = false;
|
|
Options.bShowObjectRootClass = true;
|
|
Options.bExpandRootNodes = true;
|
|
|
|
if (InArgs._ClassViewerFilter.IsValid())
|
|
{
|
|
Options.ClassFilter = InArgs._ClassViewerFilter;
|
|
}
|
|
else if (InArgs._ClassDomain == EClassDomain::Native)
|
|
{
|
|
// Prevent creating native classes based on blueprint classes
|
|
Options.ClassFilter = MakeShareable(new FNativeClassParentFilter());
|
|
}
|
|
|
|
// Only show the Object root class if it's a valid base (this helps keep the tree clean)
|
|
if (Options.ClassFilter.IsValid() && !Options.ClassFilter->IsClassAllowed(Options, UObject::StaticClass(), MakeShareable(new FClassViewerFilterFuncs)))
|
|
{
|
|
Options.bShowObjectRootClass = false;
|
|
}
|
|
|
|
ClassViewer = StaticCastSharedRef<SClassViewer>(FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(Options, FOnClassPicked::CreateSP(this, &SNewClassDialog::OnAdvancedClassSelected)));
|
|
|
|
// Make sure the featured classes all pass the active class filter
|
|
TArray<FNewClassInfo> ValidatedFeaturedClasses;
|
|
ValidatedFeaturedClasses.Reserve(InArgs._FeaturedClasses.Num());
|
|
for (const FNewClassInfo& FeaturedClassInfo : InArgs._FeaturedClasses)
|
|
{
|
|
if (FeaturedClassInfo.ClassType != FNewClassInfo::EClassType::UObject || ClassViewer->IsClassAllowed(FeaturedClassInfo.BaseClass))
|
|
{
|
|
ValidatedFeaturedClasses.Add(FeaturedClassInfo);
|
|
}
|
|
}
|
|
|
|
SetupParentClassItems(ValidatedFeaturedClasses);
|
|
UpdateInputValidity();
|
|
|
|
TSharedRef<SWidget> DocWidget = IDocumentation::Get()->CreateAnchor(TAttribute<FString>(this, &SNewClassDialog::GetSelectedParentDocLink));
|
|
DocWidget->SetVisibility(TAttribute<EVisibility>(this, &SNewClassDialog::GetDocLinkVisibility));
|
|
|
|
const float EditableTextHeight = 26.0f;
|
|
|
|
IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser").Get();
|
|
|
|
FPathPickerConfig BlueprintPathConfig;
|
|
if (ClassDomain == EClassDomain::Blueprint)
|
|
{
|
|
BlueprintPathConfig.DefaultPath = InArgs._InitialPath;
|
|
BlueprintPathConfig.bFocusSearchBoxWhenOpened = false;
|
|
BlueprintPathConfig.bAllowContextMenu = false;
|
|
BlueprintPathConfig.bAllowClassesFolder = false;
|
|
BlueprintPathConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SNewClassDialog::OnBlueprintPathSelected);
|
|
}
|
|
|
|
OnAddedToProject = InArgs._OnAddedToProject;
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(18)
|
|
.BorderImage( FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush") )
|
|
[
|
|
SNew(SVerticalBox)
|
|
.AddMetaData<FTutorialMetaData>(TEXT("AddCodeMajorAnchor"))
|
|
|
|
+SVerticalBox::Slot()
|
|
[
|
|
SAssignNew( MainWizard, SWizard)
|
|
.ShowPageList(false)
|
|
.CanFinish(this, &SNewClassDialog::CanFinish)
|
|
.FinishButtonText( ClassDomain == EClassDomain::Native ? LOCTEXT("FinishButtonText_Native", "Create Class") : LOCTEXT("FinishButtonText_Blueprint", "Create Blueprint Class") )
|
|
.FinishButtonToolTip (
|
|
ClassDomain == EClassDomain::Native ?
|
|
LOCTEXT("FinishButtonToolTip_Native", "Creates the code files to add your new class.") :
|
|
LOCTEXT("FinishButtonToolTip_Blueprint", "Creates the new Blueprint class based on the specified parent class.")
|
|
)
|
|
.OnCanceled(this, &SNewClassDialog::CancelClicked)
|
|
.OnFinished(this, &SNewClassDialog::FinishClicked)
|
|
.InitialPageIndex(ParentClassInfo.IsSet() ? 1 : 0)
|
|
.PageFooter()
|
|
[
|
|
// Get IDE information
|
|
SNew(SBorder)
|
|
.Visibility( this, &SNewClassDialog::GetGlobalErrorLabelVisibility )
|
|
.BorderImage( FEditorStyle::GetBrush("NewClassDialog.ErrorLabelBorder") )
|
|
.Padding(FMargin(0, 5))
|
|
.Content()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(2.f)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("MessageLog.Warning"))
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( this, &SNewClassDialog::GetGlobalErrorLabelText )
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.ErrorLabelFont" )
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.AutoWidth()
|
|
.Padding(5.f, 0.f)
|
|
[
|
|
SNew(SGetSuggestedIDEWidget)
|
|
]
|
|
]
|
|
]
|
|
|
|
// Choose parent class
|
|
+SWizard::Page()
|
|
.CanShow(!ParentClassInfo.IsSet()) // We can't move to this widget page if we've been given a parent class to use
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Title
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.PageTitle" )
|
|
.Text( LOCTEXT( "ParentClassTitle", "Choose Parent Class" ) )
|
|
]
|
|
|
|
// Title spacer
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 2, 0, 8)
|
|
[
|
|
SNew(SSeparator)
|
|
]
|
|
|
|
// Page description and view options
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 10)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(
|
|
ClassDomain == EClassDomain::Native ?
|
|
LOCTEXT("ChooseParentClassDescription_Native", "This will add a C++ header and source code file to your game project.") :
|
|
LOCTEXT("ChooseParentClassDescription_Blueprint", "This will add a new Blueprint class to your game project.")
|
|
)
|
|
]
|
|
|
|
// Full tree checkbox
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4, 0, 0, 0)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked( this, &SNewClassDialog::IsFullClassTreeChecked )
|
|
.OnCheckStateChanged( this, &SNewClassDialog::OnFullClassTreeChanged )
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( LOCTEXT( "FullClassTree", "Show All Classes" ) )
|
|
]
|
|
]
|
|
]
|
|
|
|
// Add Code list
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1.f)
|
|
.Padding(0, 10)
|
|
[
|
|
SNew(SBorder)
|
|
.AddMetaData<FTutorialMetaData>(TEXT("AddCodeOptions"))
|
|
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
[
|
|
// Basic view
|
|
SAssignNew(ParentClassListView, SListView< TSharedPtr<FParentClassItem> >)
|
|
.ListItemsSource(&ParentClassItemsSource)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
.ClearSelectionOnClick(false)
|
|
.OnGenerateRow(this, &SNewClassDialog::MakeParentClassListViewWidget)
|
|
.OnMouseButtonDoubleClick( this, &SNewClassDialog::OnParentClassItemDoubleClicked )
|
|
.OnSelectionChanged(this, &SNewClassDialog::OnClassSelected)
|
|
.Visibility(this, &SNewClassDialog::GetBasicParentClassVisibility)
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
[
|
|
// Advanced view
|
|
SNew(SBox)
|
|
.Visibility(this, &SNewClassDialog::GetAdvancedParentClassVisibility)
|
|
[
|
|
ClassViewer.ToSharedRef()
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Class selection
|
|
+SVerticalBox::Slot()
|
|
.Padding(30, 2)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Class label
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
|
|
.Text( LOCTEXT( "ParentClassLabel", "Selected Class" ) )
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.TextStyle(FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel")
|
|
.Text(LOCTEXT("ParentClassSourceLabel", "Selected Class Source"))
|
|
]
|
|
]
|
|
|
|
// Class selection preview
|
|
+SHorizontalBox::Slot()
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( this, &SNewClassDialog::GetSelectedParentClassName )
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
DocWidget
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Bottom)
|
|
.HAlign(HAlign_Left)
|
|
.Padding(0.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(SHyperlink)
|
|
.Style(FEditorStyle::Get(), "Common.GotoNativeCodeHyperlink")
|
|
.OnNavigate(this, &SNewClassDialog::OnEditCodeClicked)
|
|
.Text(this, &SNewClassDialog::GetSelectedParentClassFilename)
|
|
.ToolTipText(FText::Format(LOCTEXT("GoToCode_ToolTip", "Click to open this source file in {0}"), FSourceCodeNavigation::GetSuggestedSourceCodeIDE()))
|
|
.Visibility(this, &SNewClassDialog::GetSourceHyperlinkVisibility)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Name class
|
|
+SWizard::Page()
|
|
.OnEnter(this, &SNewClassDialog::OnNamePageEntered)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Title
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.PageTitle" )
|
|
.Text( this, &SNewClassDialog::GetNameClassTitle )
|
|
]
|
|
|
|
// Title spacer
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 2, 0, 8)
|
|
[
|
|
SNew(SSeparator)
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1.f)
|
|
.Padding(0, 10)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 5)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ClassNameDescription", "Enter a name for your new class. Class names may only contain alphanumeric characters, and may not contain a space.") )
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 2)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( ClassDomain == EClassDomain::Native ?
|
|
LOCTEXT("ClassNameDetails_Native", "When you click the \"Create\" button below, a header (.h) file and a source (.cpp) file will be made using this name.") :
|
|
LOCTEXT("ClassNameDetails_Blueprint", "When you click the \"Create\" button below, a new Blueprint class will be created.")
|
|
)
|
|
]
|
|
|
|
// Name Error label
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 5)
|
|
[
|
|
// Constant height, whether the label is visible or not
|
|
SNew(SBox).HeightOverride(20)
|
|
[
|
|
SNew(SBorder)
|
|
.Visibility( this, &SNewClassDialog::GetNameErrorLabelVisibility )
|
|
.BorderImage( FEditorStyle::GetBrush("NewClassDialog.ErrorLabelBorder") )
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( this, &SNewClassDialog::GetNameErrorLabelText )
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.ErrorLabelFont" )
|
|
]
|
|
]
|
|
]
|
|
|
|
// Properties
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop"))
|
|
.BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f ))
|
|
.Padding(FMargin(6.0f, 4.0f, 7.0f, 4.0f))
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0)
|
|
[
|
|
SNew(SGridPanel)
|
|
.FillColumn(1, 1.0f)
|
|
|
|
// Name label
|
|
+SGridPanel::Slot(0, 0)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
|
|
.Text( LOCTEXT( "NameLabel", "Name" ) )
|
|
]
|
|
|
|
// Name edit box
|
|
+SGridPanel::Slot(1, 0)
|
|
.Padding(0.0f, 3.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBox)
|
|
.HeightOverride(EditableTextHeight)
|
|
.AddMetaData<FTutorialMetaData>(TEXT("ClassName"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SAssignNew( ClassNameEditBox, SEditableTextBox)
|
|
.Text( this, &SNewClassDialog::OnGetClassNameText )
|
|
.OnTextChanged( this, &SNewClassDialog::OnClassNameTextChanged )
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SAssignNew(AvailableModulesCombo, SComboBox<TSharedPtr<FModuleContextInfo>>)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.ToolTipText( LOCTEXT("ModuleComboToolTip", "Choose the target module for your new class") )
|
|
.OptionsSource( &AvailableModules )
|
|
.InitiallySelectedItem( SelectedModuleInfo )
|
|
.OnSelectionChanged( this, &SNewClassDialog::SelectedModuleComboBoxSelectionChanged )
|
|
.OnGenerateWidget( this, &SNewClassDialog::MakeWidgetForSelectedModuleCombo )
|
|
[
|
|
SNew(STextBlock)
|
|
.Text( this, &SNewClassDialog::GetSelectedModuleComboText )
|
|
]
|
|
]
|
|
|
|
// Native C++ properties
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SCheckBox)
|
|
.Style(FEditorStyle::Get(), "Property.ToggleButton.Start")
|
|
.IsChecked(this, &SNewClassDialog::IsClassLocationActive, GameProjectUtils::EClassLocation::Public)
|
|
.OnCheckStateChanged(this, &SNewClassDialog::OnClassLocationChanged, GameProjectUtils::EClassLocation::Public)
|
|
.ToolTipText(LOCTEXT("ClassLocation_Public", "A public class can be included and used inside other modules in addition to the module it resides in"))
|
|
[
|
|
SNew(SBox)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Left)
|
|
.Padding(FMargin(4.0f, 0.0f, 3.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("Public", "Public"))
|
|
.ColorAndOpacity(this, &SNewClassDialog::GetClassLocationTextColor, GameProjectUtils::EClassLocation::Public)
|
|
]
|
|
]
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SCheckBox)
|
|
.Style(FEditorStyle::Get(), "Property.ToggleButton.End")
|
|
.IsChecked(this, &SNewClassDialog::IsClassLocationActive, GameProjectUtils::EClassLocation::Private)
|
|
.OnCheckStateChanged(this, &SNewClassDialog::OnClassLocationChanged, GameProjectUtils::EClassLocation::Private)
|
|
.ToolTipText(LOCTEXT("ClassLocation_Private", "A private class can only be included and used within the module it resides in"))
|
|
[
|
|
SNew(SBox)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.Padding(FMargin(3.0f, 0.0f, 4.0f, 0.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("Private", "Private"))
|
|
.ColorAndOpacity(this, &SNewClassDialog::GetClassLocationTextColor, GameProjectUtils::EClassLocation::Private)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Path label
|
|
+SGridPanel::Slot(0, 1)
|
|
.VAlign(ClassDomain == EClassDomain::Blueprint ? VAlign_Top : VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
|
|
.Text( LOCTEXT( "PathLabel", "Path" ) )
|
|
]
|
|
|
|
// Path edit box
|
|
+SGridPanel::Slot(1, 1)
|
|
.Padding(0.0f, 3.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Blueprint Class asset path
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0)
|
|
[
|
|
SNew(SBox)
|
|
// Height override to force the visibility of a scrollbar (our parent is autoheight)
|
|
.HeightOverride(200)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Visible : EVisibility::Collapsed)
|
|
[
|
|
ContentBrowser.CreatePathPicker(BlueprintPathConfig)
|
|
]
|
|
]
|
|
|
|
// Native C++ path
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBox)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.HeightOverride(EditableTextHeight)
|
|
.AddMetaData<FTutorialMetaData>(TEXT("Path"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.Text(this, &SNewClassDialog::OnGetClassPathText)
|
|
.OnTextChanged(this, &SNewClassDialog::OnClassPathTextChanged)
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(6.0f, 1.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked(this, &SNewClassDialog::HandleChooseFolderButtonClicked)
|
|
.Text( LOCTEXT( "BrowseButtonText", "Choose Folder" ) )
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Header output label
|
|
+SGridPanel::Slot(0, 2)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
|
|
.Text( LOCTEXT( "HeaderFileLabel", "Header File" ) )
|
|
]
|
|
|
|
// Header output text
|
|
+SGridPanel::Slot(1, 2)
|
|
.Padding(0.0f, 3.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBox)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.VAlign(VAlign_Center)
|
|
.HeightOverride(EditableTextHeight)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SNewClassDialog::OnGetClassHeaderFileText)
|
|
]
|
|
]
|
|
|
|
// Source output label
|
|
+SGridPanel::Slot(0, 3)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 12, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
|
|
.Text( LOCTEXT( "SourceFileLabel", "Source File" ) )
|
|
]
|
|
|
|
// Source output text
|
|
+SGridPanel::Slot(1, 3)
|
|
.Padding(0.0f, 3.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBox)
|
|
.Visibility(ClassDomain == EClassDomain::Blueprint ? EVisibility::Collapsed : EVisibility::Visible)
|
|
.VAlign(VAlign_Center)
|
|
.HeightOverride(EditableTextHeight)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SNewClassDialog::OnGetClassSourceFileText)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FMargin(0.0f, 3.0f, 0.0f, 0.0f))
|
|
.BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryBottom"))
|
|
.BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f ))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Select the first item
|
|
if ( InArgs._Class == NULL && ParentClassItemsSource.Num() > 0 )
|
|
{
|
|
ParentClassListView->SetSelection(ParentClassItemsSource[0], ESelectInfo::Direct);
|
|
}
|
|
}
|
|
void SNewClassDialog::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
// Every few seconds, the class name/path is checked for validity in case the disk contents changed and the location is now valid or invalid.
|
|
// After class creation, periodic checks are disabled to prevent a brief message indicating that the class you created already exists.
|
|
// This feature is re-enabled if the user did not restart and began editing parameters again.
|
|
if ( !bPreventPeriodicValidityChecksUntilNextChange && (InCurrentTime > LastPeriodicValidityCheckTime + PeriodicValidityCheckFrequency) )
|
|
{
|
|
UpdateInputValidity();
|
|
}
|
|
}
|
|
|
|
TSharedRef<ITableRow> SNewClassDialog::MakeParentClassListViewWidget(TSharedPtr<FParentClassItem> ParentClassItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
if ( !ensure(ParentClassItem.IsValid()) )
|
|
{
|
|
return SNew( STableRow<TSharedPtr<FParentClassItem>>, OwnerTable );
|
|
}
|
|
|
|
if ( !ParentClassItem->ParentClassInfo.IsSet() )
|
|
{
|
|
return SNew( STableRow<TSharedPtr<FParentClassItem>>, OwnerTable );
|
|
}
|
|
|
|
const FString ClassName = ParentClassItem->ParentClassInfo.GetClassName();
|
|
const FString ClassDescription = ParentClassItem->ParentClassInfo.GetClassDescription();
|
|
const FSlateBrush* const ClassBrush = ParentClassItem->ParentClassInfo.GetClassIcon();
|
|
const UClass* Class = ParentClassItem->ParentClassInfo.BaseClass;
|
|
|
|
const int32 ItemHeight = 64;
|
|
const int32 DescriptionIndent = 32;
|
|
return
|
|
SNew( STableRow<TSharedPtr<FParentClassItem>>, OwnerTable )
|
|
.Style(FEditorStyle::Get(), "NewClassDialog.ParentClassListView.TableRow")
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(FText::FromString(ClassDescription), nullptr, FEditorClassUtils::GetDocumentationPage(Class), FEditorClassUtils::GetDocumentationExcerpt(Class)))
|
|
[
|
|
SNew(SBox).HeightOverride(ItemHeight)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.Padding(8)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 4, 0)
|
|
[
|
|
SNew(SImage)
|
|
.Image(ClassBrush)
|
|
]
|
|
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle( FEditorStyle::Get(), "NewClassDialog.ParentClassItemTitle" )
|
|
.Text(FText::FromString(ClassName))
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1.f)
|
|
.Padding(DescriptionIndent, 0, 0, 0)
|
|
[
|
|
SNew(STextBlock)
|
|
//.AutoWrapText(true)
|
|
.Text(FText::FromString(ClassDescription))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
FText SNewClassDialog::GetSelectedParentClassName() const
|
|
{
|
|
return ParentClassInfo.IsSet() ? FText::FromString(ParentClassInfo.GetClassName()) : FText::GetEmpty();
|
|
}
|
|
|
|
FString GetClassHeaderPath(const UClass* Class)
|
|
{
|
|
if (Class)
|
|
{
|
|
FString ClassHeaderPath;
|
|
if (FSourceCodeNavigation::FindClassHeaderPath(Class, ClassHeaderPath) && IFileManager::Get().FileSize(*ClassHeaderPath) != INDEX_NONE)
|
|
{
|
|
return ClassHeaderPath;
|
|
}
|
|
}
|
|
return FString();
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetSourceHyperlinkVisibility() const
|
|
{
|
|
if (ClassDomain == EClassDomain::Blueprint)
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
|
|
return (ParentClassInfo.GetBaseClassHeaderFilename().Len() > 0 ? EVisibility::Visible : EVisibility::Hidden);
|
|
}
|
|
|
|
FText SNewClassDialog::GetSelectedParentClassFilename() const
|
|
{
|
|
const FString ClassHeaderPath = ParentClassInfo.GetBaseClassHeaderFilename();
|
|
if (ClassHeaderPath.Len() > 0)
|
|
{
|
|
return FText::FromString(FPaths::GetCleanFilename(*ClassHeaderPath));
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetDocLinkVisibility() const
|
|
{
|
|
return (ParentClassInfo.BaseClass == nullptr || FEditorClassUtils::GetDocumentationLink(ParentClassInfo.BaseClass).IsEmpty() ? EVisibility::Hidden : EVisibility::Visible);
|
|
}
|
|
|
|
FString SNewClassDialog::GetSelectedParentDocLink() const
|
|
{
|
|
return FEditorClassUtils::GetDocumentationLink(ParentClassInfo.BaseClass);
|
|
}
|
|
|
|
void SNewClassDialog::OnEditCodeClicked()
|
|
{
|
|
const FString ClassHeaderPath = ParentClassInfo.GetBaseClassHeaderFilename();
|
|
if (ClassHeaderPath.Len() > 0)
|
|
{
|
|
const FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath);
|
|
FSourceCodeNavigation::OpenSourceFile(AbsoluteHeaderPath);
|
|
}
|
|
}
|
|
|
|
|
|
void SNewClassDialog::OnParentClassItemDoubleClicked( TSharedPtr<FParentClassItem> TemplateItem )
|
|
{
|
|
// Advance to the name page
|
|
const int32 NamePageIdx = 1;
|
|
if ( MainWizard->CanShowPage(NamePageIdx) )
|
|
{
|
|
MainWizard->ShowPage(NamePageIdx);
|
|
}
|
|
}
|
|
|
|
void SNewClassDialog::OnClassSelected(TSharedPtr<FParentClassItem> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if ( Item.IsValid() )
|
|
{
|
|
ClassViewer->ClearSelection();
|
|
ParentClassInfo = Item->ParentClassInfo;
|
|
}
|
|
else
|
|
{
|
|
ParentClassInfo = FNewClassInfo();
|
|
}
|
|
}
|
|
|
|
void SNewClassDialog::OnAdvancedClassSelected(UClass* Class)
|
|
{
|
|
ParentClassListView->ClearSelection();
|
|
ParentClassInfo = FNewClassInfo(Class);
|
|
}
|
|
|
|
ECheckBoxState SNewClassDialog::IsFullClassTreeChecked() const
|
|
{
|
|
return bShowFullClassTree ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SNewClassDialog::OnFullClassTreeChanged(ECheckBoxState NewCheckedState)
|
|
{
|
|
bShowFullClassTree = (NewCheckedState == ECheckBoxState::Checked);
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetBasicParentClassVisibility() const
|
|
{
|
|
return bShowFullClassTree ? EVisibility::Collapsed : EVisibility::Visible;
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetAdvancedParentClassVisibility() const
|
|
{
|
|
return bShowFullClassTree ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetNameErrorLabelVisibility() const
|
|
{
|
|
return GetNameErrorLabelText().IsEmpty() ? EVisibility::Hidden : EVisibility::Visible;
|
|
}
|
|
|
|
FText SNewClassDialog::GetNameErrorLabelText() const
|
|
{
|
|
if ( !bLastInputValidityCheckSuccessful )
|
|
{
|
|
return LastInputValidityErrorText;
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
EVisibility SNewClassDialog::GetGlobalErrorLabelVisibility() const
|
|
{
|
|
return GetGlobalErrorLabelText().IsEmpty() ? EVisibility::Collapsed : EVisibility::Visible;
|
|
}
|
|
|
|
FText SNewClassDialog::GetGlobalErrorLabelText() const
|
|
{
|
|
if ( !FSourceCodeNavigation::IsCompilerAvailable() )
|
|
{
|
|
return FText::Format( LOCTEXT("NoCompilerFound", "No compiler was found. In order to use C++ code, you must first install {0}."), FSourceCodeNavigation::GetSuggestedSourceCodeIDE() );
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
void SNewClassDialog::OnNamePageEntered()
|
|
{
|
|
// Set the default class name based on the selected parent class, eg MyActor
|
|
const FString ParentClassName = ParentClassInfo.GetClassNameCPP();
|
|
const FString PotentialNewClassName = FString::Printf(TEXT("%s%s"),
|
|
DefaultClassPrefix.IsEmpty() ? TEXT("My") : *DefaultClassPrefix,
|
|
DefaultClassName.IsEmpty() ? (ParentClassName.IsEmpty() ? TEXT("Class") : *ParentClassName) : *DefaultClassName);
|
|
|
|
// Only set the default if the user hasn't changed the class name from the previous default
|
|
if(LastAutoGeneratedClassName.IsEmpty() || NewClassName == LastAutoGeneratedClassName)
|
|
{
|
|
NewClassName = PotentialNewClassName;
|
|
LastAutoGeneratedClassName = PotentialNewClassName;
|
|
}
|
|
|
|
UpdateInputValidity();
|
|
|
|
// Steal keyboard focus to accelerate name entering
|
|
FSlateApplication::Get().SetKeyboardFocus(ClassNameEditBox, EFocusCause::SetDirectly);
|
|
}
|
|
|
|
FText SNewClassDialog::GetNameClassTitle() const
|
|
{
|
|
static const FString NoneString = TEXT("None");
|
|
|
|
const FText ParentClassName = GetSelectedParentClassName();
|
|
if(!ParentClassName.IsEmpty() && ParentClassName.ToString() != NoneString)
|
|
{
|
|
return FText::Format( LOCTEXT( "NameClassTitle", "Name Your New {0}" ), ParentClassName );
|
|
}
|
|
|
|
return LOCTEXT( "NameClassGenericTitle", "Name Your New Class" );
|
|
}
|
|
|
|
FText SNewClassDialog::OnGetClassNameText() const
|
|
{
|
|
return FText::FromString(NewClassName);
|
|
}
|
|
|
|
void SNewClassDialog::OnClassNameTextChanged(const FText& NewText)
|
|
{
|
|
NewClassName = NewText.ToString();
|
|
UpdateInputValidity();
|
|
}
|
|
|
|
FText SNewClassDialog::OnGetClassPathText() const
|
|
{
|
|
return FText::FromString(NewClassPath);
|
|
}
|
|
|
|
void SNewClassDialog::OnClassPathTextChanged(const FText& NewText)
|
|
{
|
|
NewClassPath = NewText.ToString();
|
|
|
|
// If the user has selected a path which matches the root of a known module, then update our selected module to be that module
|
|
for(const auto& AvailableModule : AvailableModules)
|
|
{
|
|
if(NewClassPath.StartsWith(AvailableModule->ModuleSourcePath))
|
|
{
|
|
SelectedModuleInfo = AvailableModule;
|
|
AvailableModulesCombo->SetSelectedItem(SelectedModuleInfo);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateInputValidity();
|
|
}
|
|
|
|
void SNewClassDialog::OnBlueprintPathSelected(const FString& NewPath)
|
|
{
|
|
NewClassPath = NewPath;
|
|
UpdateInputValidity();
|
|
}
|
|
|
|
FText SNewClassDialog::OnGetClassHeaderFileText() const
|
|
{
|
|
return FText::FromString(CalculatedClassHeaderName);
|
|
}
|
|
|
|
FText SNewClassDialog::OnGetClassSourceFileText() const
|
|
{
|
|
return FText::FromString(CalculatedClassSourceName);
|
|
}
|
|
|
|
void SNewClassDialog::CancelClicked()
|
|
{
|
|
CloseContainingWindow();
|
|
}
|
|
|
|
bool SNewClassDialog::CanFinish() const
|
|
{
|
|
return bLastInputValidityCheckSuccessful && ParentClassInfo.IsSet() && FSourceCodeNavigation::IsCompilerAvailable();
|
|
}
|
|
|
|
void SNewClassDialog::FinishClicked()
|
|
{
|
|
check(CanFinish());
|
|
|
|
if (ClassDomain == EClassDomain::Blueprint)
|
|
{
|
|
FString PackagePath = NewClassPath / NewClassName;
|
|
|
|
if (!ParentClassInfo.BaseClass)
|
|
{
|
|
// @todo show fail reason in error label
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AddCodeFailed_Blueprint_NoBase", "No parent class has been specified. Failed to generate new Blueprint class."));
|
|
}
|
|
else if (FindObject<UBlueprint>(ANY_PACKAGE, *PackagePath))
|
|
{
|
|
// @todo show fail reason in error label
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AddCodeFailed_Blueprint_AlreadyExists", "The chosen Blueprint class already exists, please try again with a different name."));
|
|
}
|
|
else if (!NewClassPath.IsEmpty() && !NewClassName.IsEmpty())
|
|
{
|
|
UPackage* Package = CreatePackage(NULL, *PackagePath);
|
|
if (Package)
|
|
{
|
|
// Create and init a new Blueprint
|
|
UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(const_cast<UClass*>(ParentClassInfo.BaseClass), Package, FName(*NewClassName), BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass());
|
|
if (NewBP)
|
|
{
|
|
// Notify the asset registry
|
|
FAssetRegistryModule::AssetCreated(NewBP);
|
|
|
|
// Mark the package dirty...
|
|
Package->MarkPackageDirty();
|
|
|
|
OnAddedToProject.ExecuteIfBound( NewClassName, PackagePath, FString() );
|
|
|
|
// Sync the content browser to the new asset
|
|
TArray<UObject*> SyncAssets;
|
|
SyncAssets.Add(NewBP);
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
ContentBrowserModule.Get().SyncBrowserToAssets(SyncAssets);
|
|
|
|
// Open the editor for the new asset
|
|
FAssetEditorManager::Get().OpenEditorForAsset(NewBP);
|
|
|
|
// Successfully created the code and potentially opened the IDE. Close the dialog.
|
|
CloseContainingWindow();
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// @todo show fail reason in error label
|
|
// Failed to add blueprint
|
|
const FText Message = FText::Format( LOCTEXT("AddCodeFailed_Blueprint", "Failed to create package for class {0}. Please try again with a different name."), FText::FromString(NewClassName) );
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
}
|
|
else
|
|
{
|
|
|
|
FString HeaderFilePath;
|
|
FString CppFilePath;
|
|
|
|
FText FailReason;
|
|
const TSet<FString>& DisallowedHeaderNames = FSourceCodeNavigation::GetSourceFileDatabase().GetDisallowedHeaderNames();
|
|
if (GameProjectUtils::AddCodeToProject(NewClassName, NewClassPath, *SelectedModuleInfo, ParentClassInfo, DisallowedHeaderNames, HeaderFilePath, CppFilePath, FailReason))
|
|
{
|
|
OnAddedToProject.ExecuteIfBound( NewClassName, NewClassPath, SelectedModuleInfo->ModuleName );
|
|
|
|
// Prevent periodic validity checks. This is to prevent a brief error message about the class already existing while you are exiting.
|
|
bPreventPeriodicValidityChecksUntilNextChange = true;
|
|
|
|
// Display a nag if we didn't automatically hot-reload for the newly added class
|
|
const bool bWasHotReloaded = GEditor->AccessEditorUserSettings().bAutomaticallyHotReloadNewClasses;
|
|
if( bWasHotReloaded )
|
|
{
|
|
FNotificationInfo Notification( FText::Format( LOCTEXT("AddedClassSuccessNotification", "Added new class {0}"), FText::FromString(NewClassName) ) );
|
|
FSlateNotificationManager::Get().AddNotification( Notification );
|
|
}
|
|
|
|
if ( HeaderFilePath.IsEmpty() || CppFilePath.IsEmpty() || !FSlateApplication::Get().SupportsSourceAccess() )
|
|
{
|
|
if( !bWasHotReloaded )
|
|
{
|
|
// Code successfully added, notify the user. We are either running on a platform that does not support source access or a file was not given so don't ask about editing the file
|
|
const FText Message = FText::Format(
|
|
LOCTEXT("AddCodeSuccessWithHotReload", "Successfully added class {0}, however you must recompile {1} before it will appear in the Content Browser.")
|
|
, FText::FromString(NewClassName), FText::FromString(SelectedModuleInfo->ModuleName) );
|
|
FMessageDialog::Open(EAppMsgType::Ok, Message);
|
|
}
|
|
else
|
|
{
|
|
// Code was added and hot reloaded into the editor, but the user doesn't have a code IDE installed so we can't open the file to edit it now
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bEditSourceFilesNow = false;
|
|
if( bWasHotReloaded )
|
|
{
|
|
// Code was hot reloaded, so always edit the new classes now
|
|
bEditSourceFilesNow = true;
|
|
}
|
|
else
|
|
{
|
|
// Code successfully added, notify the user and ask about opening the IDE now
|
|
const FText Message = FText::Format(
|
|
LOCTEXT("AddCodeSuccessWithHotReloadAndSync", "Successfully added class {0}, however you must recompile {1} before it will appear in the Content Browser.\n\nWould you like to edit the code now?")
|
|
, FText::FromString(NewClassName), FText::FromString(SelectedModuleInfo->ModuleName) );
|
|
bEditSourceFilesNow = ( FMessageDialog::Open( EAppMsgType::YesNo, Message ) == EAppReturnType::Yes );
|
|
}
|
|
|
|
if( bEditSourceFilesNow )
|
|
{
|
|
TArray<FString> SourceFiles;
|
|
SourceFiles.Add(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*HeaderFilePath));
|
|
SourceFiles.Add(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*CppFilePath));
|
|
|
|
FSourceCodeNavigation::OpenSourceFiles(SourceFiles);
|
|
}
|
|
}
|
|
|
|
// Sync the content browser to the new class
|
|
UPackage* const ClassPackage = FindPackage(nullptr, *(FString("/Script/") + SelectedModuleInfo->ModuleName));
|
|
if ( ClassPackage )
|
|
{
|
|
UClass* const NewClass = static_cast<UClass*>(FindObjectWithOuter(ClassPackage, UClass::StaticClass(), *NewClassName));
|
|
if ( NewClass )
|
|
{
|
|
TArray<UObject*> SyncAssets;
|
|
SyncAssets.Add(NewClass);
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
ContentBrowserModule.Get().SyncBrowserToAssets(SyncAssets);
|
|
}
|
|
}
|
|
|
|
// Successfully created the code and potentially opened the IDE. Close the dialog.
|
|
CloseContainingWindow();
|
|
}
|
|
else
|
|
{
|
|
// @todo show fail reason in error label
|
|
// Failed to add code
|
|
const FText Message = FText::Format( LOCTEXT("AddCodeFailed_Native", "Failed to add or compile class {0}. {1}\n\nWould you like to open the Output Log to see more details?"), FText::FromString(NewClassName), FailReason );
|
|
if( FMessageDialog::Open(EAppMsgType::YesNo, Message) == EAppReturnType::Yes )
|
|
{
|
|
FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply SNewClassDialog::HandleChooseFolderButtonClicked()
|
|
{
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
if ( DesktopPlatform )
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
void* ParentWindowWindowHandle = (ParentWindow.IsValid()) ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr;
|
|
|
|
FString FolderName;
|
|
const FString Title = LOCTEXT("NewClassBrowseTitle", "Choose a source location").ToString();
|
|
const bool bFolderSelected = DesktopPlatform->OpenDirectoryDialog(
|
|
ParentWindowWindowHandle,
|
|
Title,
|
|
NewClassPath,
|
|
FolderName
|
|
);
|
|
|
|
if ( bFolderSelected )
|
|
{
|
|
if ( !FolderName.EndsWith(TEXT("/")) )
|
|
{
|
|
FolderName += TEXT("/");
|
|
}
|
|
|
|
NewClassPath = FolderName;
|
|
|
|
// If the user has selected a path which matches the root of a known module, then update our selected module to be that module
|
|
for(const auto& AvailableModule : AvailableModules)
|
|
{
|
|
if(NewClassPath.StartsWith(AvailableModule->ModuleSourcePath))
|
|
{
|
|
SelectedModuleInfo = AvailableModule;
|
|
AvailableModulesCombo->SetSelectedItem(SelectedModuleInfo);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateInputValidity();
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FText SNewClassDialog::GetSelectedModuleComboText() const
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("ModuleName"), FText::FromString(SelectedModuleInfo->ModuleName));
|
|
Args.Add(TEXT("ModuleType"), FText::FromString(EHostType::ToString(SelectedModuleInfo->ModuleType)));
|
|
return FText::Format(LOCTEXT("ModuleComboEntry", "{ModuleName} ({ModuleType})"), Args);
|
|
}
|
|
|
|
void SNewClassDialog::SelectedModuleComboBoxSelectionChanged(TSharedPtr<FModuleContextInfo> Value, ESelectInfo::Type SelectInfo)
|
|
{
|
|
const FString& OldModulePath = SelectedModuleInfo->ModuleSourcePath;
|
|
const FString& NewModulePath = Value->ModuleSourcePath;
|
|
|
|
SelectedModuleInfo = Value;
|
|
|
|
// Update the class path to be rooted to the new module location
|
|
const FString AbsoluteClassPath = FPaths::ConvertRelativePathToFull(NewClassPath) / ""; // Ensure trailing /
|
|
if(AbsoluteClassPath.StartsWith(OldModulePath))
|
|
{
|
|
NewClassPath = AbsoluteClassPath.Replace(*OldModulePath, *NewModulePath);
|
|
}
|
|
|
|
UpdateInputValidity();
|
|
}
|
|
|
|
TSharedRef<SWidget> SNewClassDialog::MakeWidgetForSelectedModuleCombo(TSharedPtr<FModuleContextInfo> Value)
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("ModuleName"), FText::FromString(Value->ModuleName));
|
|
Args.Add(TEXT("ModuleType"), FText::FromString(EHostType::ToString(Value->ModuleType)));
|
|
return SNew(STextBlock)
|
|
.Text(FText::Format(LOCTEXT("ModuleComboEntry", "{ModuleName} ({ModuleType})"), Args));
|
|
}
|
|
|
|
FSlateColor SNewClassDialog::GetClassLocationTextColor(GameProjectUtils::EClassLocation InLocation) const
|
|
{
|
|
return (ClassLocation == InLocation) ? FSlateColor(FLinearColor(0, 0, 0)) : FSlateColor(FLinearColor(0.72f, 0.72f, 0.72f, 1.f));
|
|
}
|
|
|
|
ECheckBoxState SNewClassDialog::IsClassLocationActive(GameProjectUtils::EClassLocation InLocation) const
|
|
{
|
|
return (ClassLocation == InLocation) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SNewClassDialog::OnClassLocationChanged(ECheckBoxState InCheckedState, GameProjectUtils::EClassLocation InLocation)
|
|
{
|
|
if(InCheckedState == ECheckBoxState::Checked)
|
|
{
|
|
const FString AbsoluteClassPath = FPaths::ConvertRelativePathToFull(NewClassPath) / ""; // Ensure trailing /
|
|
|
|
GameProjectUtils::EClassLocation TmpClassLocation = GameProjectUtils::EClassLocation::UserDefined;
|
|
GameProjectUtils::GetClassLocation(AbsoluteClassPath, *SelectedModuleInfo, TmpClassLocation);
|
|
|
|
const FString RootPath = SelectedModuleInfo->ModuleSourcePath;
|
|
const FString PublicPath = RootPath / "Public" / ""; // Ensure trailing /
|
|
const FString PrivatePath = RootPath / "Private" / ""; // Ensure trailing /
|
|
|
|
// Update the class path to be rooted to the Public or Private folder based on InVisibility
|
|
switch(InLocation)
|
|
{
|
|
case GameProjectUtils::EClassLocation::Public:
|
|
if(AbsoluteClassPath.StartsWith(PrivatePath))
|
|
{
|
|
NewClassPath = AbsoluteClassPath.Replace(*PrivatePath, *PublicPath);
|
|
}
|
|
else if(AbsoluteClassPath.StartsWith(RootPath))
|
|
{
|
|
NewClassPath = AbsoluteClassPath.Replace(*RootPath, *PublicPath);
|
|
}
|
|
else
|
|
{
|
|
NewClassPath = PublicPath;
|
|
}
|
|
break;
|
|
|
|
case GameProjectUtils::EClassLocation::Private:
|
|
if(AbsoluteClassPath.StartsWith(PublicPath))
|
|
{
|
|
NewClassPath = AbsoluteClassPath.Replace(*PublicPath, *PrivatePath);
|
|
}
|
|
else if(AbsoluteClassPath.StartsWith(RootPath))
|
|
{
|
|
NewClassPath = AbsoluteClassPath.Replace(*RootPath, *PrivatePath);
|
|
}
|
|
else
|
|
{
|
|
NewClassPath = PrivatePath;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Will update ClassVisibility correctly
|
|
UpdateInputValidity();
|
|
}
|
|
}
|
|
|
|
void SNewClassDialog::UpdateInputValidity()
|
|
{
|
|
bLastInputValidityCheckSuccessful = true;
|
|
|
|
if (ClassDomain == EClassDomain::Blueprint)
|
|
{
|
|
bLastInputValidityCheckSuccessful = GameProjectUtils::IsValidClassNameForCreation(NewClassName, LastInputValidityErrorText);
|
|
ClassLocation = GameProjectUtils::EClassLocation::UserDefined;
|
|
if (bLastInputValidityCheckSuccessful)
|
|
{
|
|
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName).Get();
|
|
if (AssetRegistry.GetAssetByObjectPath(*(NewClassPath / NewClassName)).IsValid())
|
|
{
|
|
bLastInputValidityCheckSuccessful = false;
|
|
LastInputValidityErrorText = FText::Format(LOCTEXT("AssetAlreadyExists", "An asset called {0} already exists in {1}."), FText::FromString(NewClassName), FText::FromString(NewClassPath));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Validate the path first since this has the side effect of updating the UI
|
|
bLastInputValidityCheckSuccessful = GameProjectUtils::CalculateSourcePaths(NewClassPath, *SelectedModuleInfo, CalculatedClassHeaderName, CalculatedClassSourceName, &LastInputValidityErrorText);
|
|
CalculatedClassHeaderName /= ParentClassInfo.GetHeaderFilename(NewClassName);
|
|
CalculatedClassSourceName /= ParentClassInfo.GetSourceFilename(NewClassName);
|
|
|
|
// If the source paths check as succeeded, check to see if we're using a Public/Private class
|
|
if(bLastInputValidityCheckSuccessful)
|
|
{
|
|
GameProjectUtils::GetClassLocation(NewClassPath, *SelectedModuleInfo, ClassLocation);
|
|
|
|
// We only care about the Public and Private folders
|
|
if(ClassLocation != GameProjectUtils::EClassLocation::Public && ClassLocation != GameProjectUtils::EClassLocation::Private)
|
|
{
|
|
ClassLocation = GameProjectUtils::EClassLocation::UserDefined;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClassLocation = GameProjectUtils::EClassLocation::UserDefined;
|
|
}
|
|
|
|
// Validate the class name only if the path is valid
|
|
if ( bLastInputValidityCheckSuccessful )
|
|
{
|
|
const TSet<FString>& DisallowedHeaderNames = FSourceCodeNavigation::GetSourceFileDatabase().GetDisallowedHeaderNames();
|
|
bLastInputValidityCheckSuccessful = GameProjectUtils::IsValidClassNameForCreation(NewClassName, *SelectedModuleInfo, DisallowedHeaderNames, LastInputValidityErrorText);
|
|
}
|
|
|
|
// Validate that the class is valid for the currently selected module
|
|
// As a project can have multiple modules, this lets us update the class validity as the user changes the target module
|
|
if ( bLastInputValidityCheckSuccessful && ParentClassInfo.BaseClass )
|
|
{
|
|
bLastInputValidityCheckSuccessful = GameProjectUtils::IsValidBaseClassForCreation(ParentClassInfo.BaseClass, *SelectedModuleInfo);
|
|
if ( !bLastInputValidityCheckSuccessful )
|
|
{
|
|
LastInputValidityErrorText = FText::Format(
|
|
LOCTEXT("NewClassError_InvalidBaseClassForModule", "{0} cannot be used as a base class in the {1} module. Please make sure that {0} is API exported."),
|
|
FText::FromString(ParentClassInfo.BaseClass->GetName()),
|
|
FText::FromString(SelectedModuleInfo->ModuleName)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
LastPeriodicValidityCheckTime = FSlateApplication::Get().GetCurrentTime();
|
|
|
|
// Since this function was invoked, periodic validity checks should be re-enabled if they were disabled.
|
|
bPreventPeriodicValidityChecksUntilNextChange = false;
|
|
}
|
|
|
|
const FNewClassInfo& SNewClassDialog::GetSelectedParentClassInfo() const
|
|
{
|
|
return ParentClassInfo;
|
|
}
|
|
|
|
void SNewClassDialog::SetupParentClassItems(const TArray<FNewClassInfo>& UserSpecifiedFeaturedClasses)
|
|
{
|
|
TArray<FNewClassInfo> DefaultFeaturedClasses;
|
|
const TArray<FNewClassInfo>* ArrayToUse = &UserSpecifiedFeaturedClasses;
|
|
|
|
// Setup the featured classes list
|
|
if (ArrayToUse->Num() == 0)
|
|
{
|
|
DefaultFeaturedClasses = ClassDomain == EClassDomain::Native ? FFeaturedClasses::AllNativeClasses() : FFeaturedClasses::ActorClasses();
|
|
ArrayToUse = &DefaultFeaturedClasses;
|
|
}
|
|
|
|
for (const auto& Featured : *ArrayToUse)
|
|
{
|
|
ParentClassItemsSource.Add( MakeShareable( new FParentClassItem(Featured) ) );
|
|
}
|
|
}
|
|
|
|
void SNewClassDialog::CloseContainingWindow()
|
|
{
|
|
FWidgetPath WidgetPath;
|
|
TSharedPtr<SWindow> ContainingWindow = FSlateApplication::Get().FindWidgetWindow( AsShared(), WidgetPath);
|
|
|
|
if ( ContainingWindow.IsValid() )
|
|
{
|
|
ContainingWindow->RequestDestroyWindow();
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|