Files
UnrealEngineUWP/Engine/Source/Editor/GameProjectGeneration/Private/SNewClassDialog.cpp
Jamie Dale e3c6911737 Generating a new class will now include the module header relative to a known include path
If the module is inside the Public or Classes folder, it is included directly (since all these folders are on the include path), otherwise it's included relative to the module source root (which is now on the include paths due to a recent UBT change).

ReviewedBy Ben.Donatelli

[CL 2109096 by Jamie Dale in Main branch]
2014-06-18 06:45:20 -04:00

1029 lines
33 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "GameProjectGenerationPrivatePCH.h"
#include "SourceCodeNavigation.h"
#include "ClassViewerModule.h"
#include "ClassViewerFilter.h"
#include "Editor/ClassViewer/Private/SClassViewer.h"
#include "DesktopPlatformModule.h"
#define LOCTEXT_NAMESPACE "GameProjectGeneration"
struct FParentClassItem
{
GameProjectUtils::FNewClassInfo ParentClassInfo;
FParentClassItem(const GameProjectUtils::FNewClassInfo& InParentClassInfo)
: ParentClassInfo(InParentClassInfo)
{}
};
class FNativeClassParentFilter : public IClassViewerFilter
{
public:
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
// You may not make native classes based on blueprint generated classes
const bool bIsBlueprintClass = (InClass->ClassGeneratedBy != NULL);
// UObject is special cased to be extensible since it would otherwise not be since it doesn't pass the API check (intrinsic class).
const bool bIsExplicitlyUObject = (InClass == UObject::StaticClass());
// Is this class in the same module as our current module?
const GameProjectUtils::FModuleContextInfo ModuleInfo = GameProjectUtils::GetCurrentModuleContextInfo();
const FString ClassModuleName = InClass->GetOutermost()->GetName().RightChop( FString(TEXT("/Script/")).Len() );
const bool bIsInDestinationModule = (ModuleInfo.ModuleName == ClassModuleName);
// You need API if you are either not UObject itself and you are not in the destination module
const bool bNeedsAPI = !bIsExplicitlyUObject && !bIsInDestinationModule;
// You may not make a class that is not DLL exported.
// MinimalAPI classes aren't compatible with the DLL export macro, but can still be used as a valid base
const bool bHasAPI = InClass->HasAnyClassFlags(CLASS_RequiredAPI) || InClass->HasAnyClassFlags(CLASS_MinimalAPI);
// @todo should we support interfaces?
const bool bIsInterface = InClass->IsChildOf(UInterface::StaticClass());
return !bIsBlueprintClass && (!bNeedsAPI || bHasAPI) && !bIsInterface;
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
return false;
}
};
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SNewClassDialog::Construct( const FArguments& InArgs )
{
ModuleInfo = GameProjectUtils::GetCurrentModuleContextInfo();
NewClassPath = GameProjectUtils::GetSourceRootPath(true/*bIncludeModuleName*/, ModuleInfo);
ClassLocation = GameProjectUtils::EClassLocation::UserDefined; // the first call to UpdateInputValidity will set this correctly based on NewClassPath
ParentClassInfo = GameProjectUtils::FNewClassInfo(InArgs._Class);
bShowFullClassTree = false;
LastPeriodicValidityCheckTime = 0;
PeriodicValidityCheckFrequency = 4;
bLastInputValidityCheckSuccessful = true;
bPreventPeriodicValidityChecksUntilNextChange = false;
SetupParentClassItems();
UpdateInputValidity();
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;
// Prevent creating native classes based on blueprint classes
Options.ClassFilter = MakeShareable(new FNativeClassParentFilter);
ClassViewer = StaticCastSharedRef<SClassViewer>(FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(Options, FOnClassPicked::CreateSP(this, &SNewClassDialog::OnAdvancedClassSelected)));
const float EditableTextHeight = 26.0f;
ChildSlot
[
SNew(SBorder)
.Padding(FMargin(26, 8))
.BorderImage( FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush") )
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
[
SAssignNew( MainWizard, SWizard)
.ShowPageList(false)
.CanFinish(this, &SNewClassDialog::CanFinish)
.FinishButtonText( LOCTEXT("FinishButtonText", "Create Class").ToString() )
.FinishButtonToolTip ( LOCTEXT("FinishButtonToolTip", "Creates the code files to add your new class.").ToString() )
.OnCanceled(this, &SNewClassDialog::CancelClicked)
.OnFinished(this, &SNewClassDialog::FinishClicked)
.InitialPageIndex(ParentClassInfo.IsSet() ? 1 : 0)
// Choose parent class
+SWizard::Page()
[
SNew(SVerticalBox)
// Title
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 20, 0, 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( FText::Format( LOCTEXT("ChooseParentClassDescription", "You are about to add a C++ source code file. To compile these files you must have {0} installed."), FSourceCodeNavigation::GetSuggestedSourceCodeIDE() ) )
]
// 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)
.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()
.VAlign(VAlign_Center)
.Padding(0, 0, 12, 0)
[
SNew(STextBlock)
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
.Text( LOCTEXT( "ParentClassLabel", "Selected Class" ) )
]
// Class selection preview
+SHorizontalBox::Slot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text( this, &SNewClassDialog::GetSelectedParentClassName )
]
]
]
// Name class
+SWizard::Page()
.OnEnter(this, &SNewClassDialog::OnNamePageEntered)
[
SNew(SVerticalBox)
// Title
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 20, 0, 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( LOCTEXT("ClassNameDetails", "When you click the \"Create\" button below, a header (.h) file and a source (.cpp) file will be made using this name.") )
]
// 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)
[
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)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(FEditorStyle::Get(), "Property.ToggleButton.Start")
.IsEnabled(this, &SNewClassDialog::CanChangeClassLocation)
.IsChecked(this, &SNewClassDialog::IsClassLocationActive, GameProjectUtils::EClassLocation::Public)
.OnCheckStateChanged(this, &SNewClassDialog::OnClassLocationChanged, GameProjectUtils::EClassLocation::Public)
.ToolTipText(this, &SNewClassDialog::GetClassLocationTooltip, GameProjectUtils::EClassLocation::Public)
[
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")
.IsEnabled(this, &SNewClassDialog::CanChangeClassLocation)
.IsChecked(this, &SNewClassDialog::IsClassLocationActive, GameProjectUtils::EClassLocation::Private)
.OnCheckStateChanged(this, &SNewClassDialog::OnClassLocationChanged, GameProjectUtils::EClassLocation::Private)
.ToolTipText(this, &SNewClassDialog::GetClassLocationTooltip, GameProjectUtils::EClassLocation::Private)
[
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(VAlign_Center)
.Padding(0, 0, 12, 0)
[
SNew(STextBlock)
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
.Text( LOCTEXT( "PathLabel", "Path" ).ToString() )
]
// Path edit box
+SGridPanel::Slot(1, 1)
.Padding(0.0f, 3.0f)
.VAlign(VAlign_Center)
[
SNew(SBox)
.HeightOverride(EditableTextHeight)
[
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)
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
.Text( LOCTEXT( "HeaderFileLabel", "Header File" ).ToString() )
]
// Header output text
+SGridPanel::Slot(1, 2)
.Padding(0.0f, 3.0f)
.VAlign(VAlign_Center)
[
SNew(SBox)
.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)
.TextStyle( FEditorStyle::Get(), "NewClassDialog.SelectedParentClassLabel" )
.Text( LOCTEXT( "SourceFileLabel", "Source File" ).ToString() )
]
// Source output text
+SGridPanel::Slot(1, 3)
.Padding(0.0f, 3.0f)
.VAlign(VAlign_Center)
[
SNew(SBox)
.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 ))
]
]
]
]
// IDE download information
+SVerticalBox::Slot()
.Padding(0, 5)
.AutoHeight()
[
SNew(SBorder)
.Visibility( this, &SNewClassDialog::GetGlobalErrorLabelVisibility )
.BorderImage( FEditorStyle::GetBrush("NewClassDialog.ErrorLabelBorder") )
.Content()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text( this, &SNewClassDialog::GetGlobalErrorLabelText )
.TextStyle( FEditorStyle::Get(), "NewClassDialog.ErrorLabelFont" )
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SHyperlink)
.Text( FText::Format( LOCTEXT("IDEDownloadLinkText", "Download {0}"), FSourceCodeNavigation::GetSuggestedSourceCodeIDE() ) )
.OnNavigate( this, &SNewClassDialog::OnDownloadIDEClicked, FSourceCodeNavigation::GetSuggestedSourceCodeIDEDownloadURL() )
.Visibility( this, &SNewClassDialog::GetGlobalErrorLabelIDELinkVisibility )
]
]
]
]
];
// Select the first item
if ( InArgs._Class == NULL && ParentClassItemsSource.Num() > 0 )
{
ParentClassListView->SetSelection(ParentClassItemsSource[0], ESelectInfo::Direct);
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SNewClassDialog::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, 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 int32 ItemHeight = 64;
const int32 DescriptionIndent = 32;
return
SNew( STableRow<TSharedPtr<FParentClassItem>>, OwnerTable )
.Style(FEditorStyle::Get(), "NewClassDialog.ParentClassListView.TableRow")
[
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)
.ColorAndOpacity(FSlateColor::UseForeground())
.Image( ClassBrush )
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.TextStyle( FEditorStyle::Get(), "NewClassDialog.ParentClassItemTitle" )
.Text(ClassName)
]
]
+SVerticalBox::Slot()
.FillHeight(1.f)
.Padding(DescriptionIndent, 0, 0, 0)
[
SNew(STextBlock)
//.AutoWrapText(true)
.Text(ClassDescription)
]
]
];
}
FString SNewClassDialog::GetSelectedParentClassName() const
{
return ParentClassInfo.IsSet() ? ParentClassInfo.GetClassName() : TEXT("");
}
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 = GameProjectUtils::FNewClassInfo();
}
}
void SNewClassDialog::OnAdvancedClassSelected(UClass* Class)
{
ParentClassListView->ClearSelection();
ParentClassInfo = GameProjectUtils::FNewClassInfo(Class);
}
ESlateCheckBoxState::Type SNewClassDialog::IsFullClassTreeChecked() const
{
return bShowFullClassTree ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
}
void SNewClassDialog::OnFullClassTreeChanged(ESlateCheckBoxState::Type NewCheckedState)
{
bShowFullClassTree = (NewCheckedState == ESlateCheckBoxState::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;
}
FString SNewClassDialog::GetNameErrorLabelText() const
{
if ( !bLastInputValidityCheckSuccessful )
{
return LastInputValidityErrorText.ToString();
}
return TEXT("");
}
EVisibility SNewClassDialog::GetGlobalErrorLabelVisibility() const
{
return GetGlobalErrorLabelText().IsEmpty() ? EVisibility::Hidden : EVisibility::Visible;
}
EVisibility SNewClassDialog::GetGlobalErrorLabelIDELinkVisibility() const
{
return FSourceCodeNavigation::IsCompilerAvailable() ? EVisibility::Collapsed : EVisibility::Visible;
}
FString 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() ).ToString();
}
return TEXT("");
}
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("My%s"), ParentClassName.IsEmpty() ? TEXT("Class") : *ParentClassName);
// 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, EKeyboardFocusCause::SetDirectly);
}
FString SNewClassDialog::GetNameClassTitle() const
{
FString ParentClassName = GetSelectedParentClassName();
if(ParentClassName.IsEmpty() || ParentClassName == "None")
{
ParentClassName = TEXT("Class");
}
return FText::Format( LOCTEXT( "NameClassTitle", "Name Your New {0}" ), FText::FromString(ParentClassName) ).ToString();
}
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();
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());
FString HeaderFilePath;
FString CppFilePath;
FText FailReason;
if ( GameProjectUtils::AddCodeToProject(NewClassName, NewClassPath, ParentClassInfo, HeaderFilePath, CppFilePath, FailReason) )
{
// Prevent periodic validity checks. This is to prevent a brief error message about the class already existing while you are exiting.
bPreventPeriodicValidityChecksUntilNextChange = true;
if ( HeaderFilePath.IsEmpty() || CppFilePath.IsEmpty() || !FSlateApplication::Get().SupportsSourceAccess() )
{
// 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("AddCodeSuccess", "Successfully added class {0}."), FText::FromString(NewClassName) );
FMessageDialog::Open(EAppMsgType::Ok, Message);
}
else
{
// Code successfully added, notify the user and ask about opening the IDE now
const FText Message = FText::Format( LOCTEXT("AddCodeSuccessWithSync", "Successfully added class {0}. Would you like to edit the code now?"), FText::FromString(NewClassName) );
if ( FMessageDialog::Open(EAppMsgType::YesNo, Message) == EAppReturnType::Yes )
{
TArray<FString> SourceFiles;
SourceFiles.Add(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*HeaderFilePath));
SourceFiles.Add(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*CppFilePath));
FSourceCodeNavigation::OpenSourceFiles(SourceFiles);
}
}
// 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", "Failed to add class {0}. {1}"), FText::FromString(NewClassName), FailReason );
FMessageDialog::Open(EAppMsgType::Ok, Message);
}
}
void SNewClassDialog::OnDownloadIDEClicked(FString URL)
{
FPlatformProcess::LaunchURL( *URL, NULL, NULL );
}
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;
UpdateInputValidity();
}
}
return FReply::Handled();
}
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));
}
FText SNewClassDialog::GetClassLocationTooltip(GameProjectUtils::EClassLocation InLocation) const
{
if(CanChangeClassLocation())
{
switch(InLocation)
{
case GameProjectUtils::EClassLocation::Public:
return LOCTEXT("ClassLocation_Public", "A public class can be included and used inside other modules in addition to the module it resides in");
case GameProjectUtils::EClassLocation::Private:
return LOCTEXT("ClassLocation_Private", "A private class can only be included and used within the module it resides in");
default:
break;
}
}
return LOCTEXT("ClassLocation_UserDefined", "Your project is either not using a Public and Private source layout, or you're explicitly creating your class outside of the Public or Private folder");
}
ESlateCheckBoxState::Type SNewClassDialog::IsClassLocationActive(GameProjectUtils::EClassLocation InLocation) const
{
return (ClassLocation == InLocation) ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked;
}
void SNewClassDialog::OnClassLocationChanged(ESlateCheckBoxState::Type InCheckedState, GameProjectUtils::EClassLocation InLocation)
{
if(InCheckedState == ESlateCheckBoxState::Checked)
{
const FString AbsoluteClassPath = FPaths::ConvertRelativePathToFull(NewClassPath) / ""; // Ensure trailing /
FString ModuleName;
GameProjectUtils::EClassLocation TmpClassLocation = GameProjectUtils::EClassLocation::UserDefined;
GameProjectUtils::GetClassLocation(AbsoluteClassPath, ModuleName, TmpClassLocation, ModuleInfo);
const FString BaseRootPath = GameProjectUtils::GetSourceRootPath(false/*bIncludeModuleName*/, ModuleInfo);
const FString RootPath = BaseRootPath / ModuleName / ""; // Ensure trailing /
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);
}
break;
case GameProjectUtils::EClassLocation::Private:
if(AbsoluteClassPath.StartsWith(PublicPath))
{
NewClassPath = AbsoluteClassPath.Replace(*PublicPath, *PrivatePath);
}
else if(AbsoluteClassPath.StartsWith(RootPath))
{
NewClassPath = AbsoluteClassPath.Replace(*RootPath, *PrivatePath);
}
break;
default:
break;
}
// Will update ClassVisibility correctly
UpdateInputValidity();
}
}
bool SNewClassDialog::CanChangeClassLocation() const
{
return ClassLocation != GameProjectUtils::EClassLocation::UserDefined;
}
void SNewClassDialog::UpdateInputValidity()
{
bLastInputValidityCheckSuccessful = true;
// Validate the path first since this has the side effect of updating the UI
FString ModuleName;
bLastInputValidityCheckSuccessful = GameProjectUtils::CalculateSourcePaths(NewClassPath, ModuleName, CalculatedClassHeaderName, CalculatedClassSourceName, ModuleInfo, &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, ModuleName, ClassLocation, ModuleInfo);
// 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 )
{
bLastInputValidityCheckSuccessful = GameProjectUtils::IsValidClassNameForCreation(NewClassName, ModuleInfo, LastInputValidityErrorText);
}
LastPeriodicValidityCheckTime = FSlateApplication::Get().GetCurrentTime();
// Since this function was invoked, periodic validity checks should be re-enabled if they were disabled.
bPreventPeriodicValidityChecksUntilNextChange = false;
}
const GameProjectUtils::FNewClassInfo& SNewClassDialog::GetSelectedParentClassInfo() const
{
return ParentClassInfo;
}
void SNewClassDialog::SetupParentClassItems()
{
TArray<GameProjectUtils::FNewClassInfo> FeaturedClasses;
// Add an empty class
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(GameProjectUtils::FNewClassInfo::EClassType::EmptyCpp));
// @todo make this ini configurable
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(ACharacter::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(APawn::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(AActor::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(APlayerCameraManager::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(APlayerController::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(AGameMode::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(AWorldSettings::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(AHUD::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(APlayerState::StaticClass()));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(AGameState::StaticClass()));
// Add the extra non-UObject classes
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(GameProjectUtils::FNewClassInfo::EClassType::SlateWidget));
FeaturedClasses.Add(GameProjectUtils::FNewClassInfo(GameProjectUtils::FNewClassInfo::EClassType::SlateWidgetStyle));
for ( auto ClassIt = FeaturedClasses.CreateConstIterator(); ClassIt; ++ClassIt )
{
ParentClassItemsSource.Add( MakeShareable( new FParentClassItem(*ClassIt) ) );
}
}
void SNewClassDialog::CloseContainingWindow()
{
FWidgetPath WidgetPath;
TSharedPtr<SWindow> ContainingWindow = FSlateApplication::Get().FindWidgetWindow( AsShared(), WidgetPath);
if ( ContainingWindow.IsValid() )
{
ContainingWindow->RequestDestroyWindow();
}
}
#undef LOCTEXT_NAMESPACE