// 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(FModuleManager::LoadModuleChecked("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 >) .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 SNewClassDialog::MakeParentClassListViewWidget(TSharedPtr ParentClassItem, const TSharedRef& OwnerTable) { if ( !ensure(ParentClassItem.IsValid()) ) { return SNew( STableRow>, OwnerTable ); } if ( !ParentClassItem->ParentClassInfo.IsSet() ) { return SNew( STableRow>, 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>, 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 TemplateItem ) { // Advance to the name page const int32 NamePageIdx = 1; if ( MainWizard->CanShowPage(NamePageIdx) ) { MainWizard->ShowPage(NamePageIdx); } } void SNewClassDialog::OnClassSelected(TSharedPtr 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 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 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, 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 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 ContainingWindow = FSlateApplication::Get().FindWidgetWindow( AsShared(), WidgetPath); if ( ContainingWindow.IsValid() ) { ContainingWindow->RequestDestroyWindow(); } } #undef LOCTEXT_NAMESPACE