// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "InternationalizationSettingsModulePrivatePCH.h" #define LOCTEXT_NAMESPACE "InternationalizationSettingsModelDetails" /** Functions for sorting the languages */ struct FCompareCultureByNativeLanguage { static FText GetCultureNativeLanguageText( const TSharedPtr Culture ) { check( Culture.IsValid() ); const FString Language = Culture->GetNativeLanguage(); return FText::FromString(Language); } FORCEINLINE bool operator()( const TSharedPtr A, const TSharedPtr B ) const { check( A.IsValid() ); check( B.IsValid() ); return( GetCultureNativeLanguageText( A ).CompareToCaseIgnored( GetCultureNativeLanguageText( B ) ) ) < 0; } }; /** Functions for sorting the regions */ struct FCompareCultureByNativeRegion { static FText GetCultureNativeRegionText( const TSharedPtr Culture ) { check( Culture.IsValid() ); FString Region = Culture->GetNativeRegion(); if ( Region.IsEmpty() ) { // Fallback to displaying the language, if no region is available return LOCTEXT("NoSpecificRegionOption", "Non-Specific Region"); } return FText::FromString(Region); } FORCEINLINE bool operator()( const TSharedPtr A, const TSharedPtr B ) const { check( A.IsValid() ); check( B.IsValid() ); // Non-Specific Region should appear before all else. if(A->GetNativeRegion().IsEmpty()) { return true; } // Non-Specific Region should appear before all else. if(B->GetNativeRegion().IsEmpty()) { return false; } // Compare native region strings. return( GetCultureNativeRegionText( A ).CompareToCaseIgnored( GetCultureNativeRegionText( B ) ) ) < 0; } }; TSharedRef FInternationalizationSettingsModelDetails::MakeInstance() { TSharedRef InternationalizationSettingsModelDetails = MakeShareable(new FInternationalizationSettingsModelDetails()); return InternationalizationSettingsModelDetails; } FInternationalizationSettingsModelDetails::~FInternationalizationSettingsModelDetails() { check(Model.IsValid()); Model->OnSettingChanged().RemoveAll(this); } void FInternationalizationSettingsModelDetails::OnSettingsChanged() { FInternationalization& I18N = FInternationalization::Get(); check(Model.IsValid()); FString SavedCultureName = Model->GetCultureName(); if ( !SavedCultureName.IsEmpty() && SavedCultureName != I18N.GetCurrentCulture()->GetName() ) { SelectedCulture = I18N.GetCulture(SavedCultureName); RequiresRestart = true; } else { SelectedCulture = I18N.GetCurrentCulture(); RequiresRestart = false; } RefreshAvailableLanguages(); LanguageComboBox->SetSelectedItem(SelectedLanguage); RefreshAvailableRegions(); RegionComboBox->SetSelectedItem(SelectedCulture); } void FInternationalizationSettingsModelDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { FInternationalization& I18N = FInternationalization::Get(); TArray< TWeakObjectPtr > ObjectsBeingCustomized; DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); check(ObjectsBeingCustomized.Num() == 1); if(ObjectsBeingCustomized[0].IsValid()) { Model = Cast(ObjectsBeingCustomized[0].Get()); } check(Model.IsValid()); Model->OnSettingChanged().AddRaw(this, &FInternationalizationSettingsModelDetails::OnSettingsChanged); // If the saved culture is not the same as the actual current culture, a restart is needed to sync them fully and properly. FString SavedCultureName = Model->GetCultureName(); if ( !SavedCultureName.IsEmpty() && SavedCultureName != I18N.GetCurrentCulture()->GetName() ) { SelectedCulture = I18N.GetCulture(SavedCultureName); RequiresRestart = true; } else { SelectedCulture = I18N.GetCurrentCulture(); RequiresRestart = false; } // Populate all our cultures RefreshAvailableCultures(); IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory("Internationalization"); const FText LanguageToolTipText = LOCTEXT("EditorLanguageTooltip", "Change the Editor language (requires restart to take effect)"); // For use in the Slate macros below, the type must be typedef'd to compile. typedef TSharedPtr ThreadSafeCulturePtr; CategoryBuilder.AddCustomRow("Language") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorLanguageLabel", "Language")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(LanguageToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(LanguageComboBox, SComboBox< ThreadSafeCulturePtr > ) .OptionsSource( &AvailableLanguages ) .InitiallySelectedItem(SelectedLanguage) .OnGenerateWidget(this, &FInternationalizationSettingsModelDetails::OnLanguageGenerateWidget, &DetailBuilder) .ToolTipText(LanguageToolTipText) .OnSelectionChanged(this, &FInternationalizationSettingsModelDetails::OnLanguageSelectionChanged) .Content() [ SNew(STextBlock) .Text(this, &FInternationalizationSettingsModelDetails::GetCurrentLanguageText) .Font(DetailBuilder.GetDetailFont()) ] ]; const FText RegionToolTipText = LOCTEXT("EditorRegionTooltip", "Change the Editor region (requires restart to take effect)"); CategoryBuilder.AddCustomRow("Region") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorRegionLabel", "Region")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(RegionToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(RegionComboBox, SComboBox< ThreadSafeCulturePtr > ) .OptionsSource( &AvailableRegions ) .InitiallySelectedItem(SelectedCulture) .OnGenerateWidget(this, &FInternationalizationSettingsModelDetails::OnRegionGenerateWidget, &DetailBuilder) .ToolTipText(RegionToolTipText) .OnSelectionChanged(this, &FInternationalizationSettingsModelDetails::OnRegionSelectionChanged) .IsEnabled(this, &FInternationalizationSettingsModelDetails::IsRegionSelectionAllowed) .Content() [ SNew(STextBlock) .Text(this, &FInternationalizationSettingsModelDetails::GetCurrentRegionText) .Font(DetailBuilder.GetDetailFont()) ] ]; const FText FieldNamesToolTipText = LOCTEXT("EditorFieldNamesTooltip", "Toggle showing localized field names (requires restart to take effect)"); CategoryBuilder.AddCustomRow("FieldNames") .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding( FMargin( 0, 1, 0, 1 ) ) .FillWidth(1.0f) [ SNew(STextBlock) .Text(LOCTEXT("EditorFieldNamesLabel", "Use Localized Field Names")) .Font(DetailBuilder.GetDetailFont()) .ToolTipText(FieldNamesToolTipText) ] ] .ValueContent() .MaxDesiredWidth(300.0f) [ SAssignNew(FieldNamesCheckBox, SCheckBox) .IsChecked(Model->ShouldLoadLocalizedPropertyNames() ? ESlateCheckBoxState::Checked : ESlateCheckBoxState::Unchecked) .ToolTipText(FieldNamesToolTipText) .OnCheckStateChanged(this, &FInternationalizationSettingsModelDetails::ShoudLoadLocalizedFieldNamesCheckChanged) ]; CategoryBuilder.AddCustomRow("RestartWarning") .Visibility( TAttribute(this, &FInternationalizationSettingsModelDetails::GetInternationalizationRestartRowVisibility) ) .WholeRowContent() .HAlign(HAlign_Center) [ SNew( SHorizontalBox ) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ SNew( SImage ) .Image( FCoreStyle::Get().GetBrush("Icons.Warning") ) ] +SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew( STextBlock ) .Text( LOCTEXT("RestartWarningText", "Changes require restart to take effect.") ) .Font(DetailBuilder.GetDetailFont()) ] ]; } void FInternationalizationSettingsModelDetails::RefreshAvailableCultures() { AvailableCultures.Empty(); FInternationalization::Get().GetCulturesWithAvailableLocalization(FPaths::GetEditorLocalizationPaths(), AvailableCultures, true); // Update our selected culture based on the available choices if ( !AvailableCultures.Contains( SelectedCulture ) ) { SelectedCulture = NULL; } RefreshAvailableLanguages(); } void FInternationalizationSettingsModelDetails::RefreshAvailableLanguages() { AvailableLanguages.Empty(); FString SelectedLanguageName; if ( SelectedCulture.IsValid() ) { SelectedLanguageName = SelectedCulture->GetNativeLanguage(); } // Setup the language list for( const auto& Culture : AvailableCultures ) { const FString CultureRegionName = Culture->GetNativeRegion(); if ( CultureRegionName.IsEmpty() ) { AvailableLanguages.Add( Culture ); // Do we have a match for the base language const FString CultureLanguageName = Culture->GetNativeLanguage(); if ( SelectedLanguageName == CultureLanguageName) { SelectedLanguage = Culture; } } } AvailableLanguages.Sort( FCompareCultureByNativeLanguage() ); RefreshAvailableRegions(); } void FInternationalizationSettingsModelDetails::RefreshAvailableRegions() { AvailableRegions.Empty(); TSharedPtr DefaultCulture = NULL; if ( SelectedLanguage.IsValid() ) { const FString SelectedLanguageName = SelectedLanguage->GetNativeLanguage(); // Setup the region list for( const auto& Culture : AvailableCultures ) { const FString CultureLanguageName = Culture->GetNativeLanguage(); if ( SelectedLanguageName == CultureLanguageName) { AvailableRegions.Add( Culture ); // If this doesn't have a valid region... assume it's the default const FString CultureRegionName = Culture->GetNativeRegion(); if ( CultureRegionName.IsEmpty() ) { DefaultCulture = Culture; } } } AvailableRegions.Sort( FCompareCultureByNativeRegion() ); } // If we have a preferred default (or there's only one in the list), select that now if ( DefaultCulture.IsValid() || AvailableCultures.Num() == 1 ) { TSharedPtr Culture = DefaultCulture.IsValid() ? DefaultCulture : AvailableCultures.Last(); // Set it as our default region, if one hasn't already been chosen if ( !SelectedCulture.IsValid() && RegionComboBox.IsValid() ) { // We have to update the combo box like this, otherwise it'll do a null selection when we next click on it RegionComboBox->SetSelectedItem( Culture ); } } if ( RegionComboBox.IsValid() ) { RegionComboBox->RefreshOptions(); } } FText FInternationalizationSettingsModelDetails::GetCurrentLanguageText() const { if( SelectedLanguage.IsValid() ) { return FCompareCultureByNativeLanguage::GetCultureNativeLanguageText(SelectedLanguage); } return FText::GetEmpty(); } TSharedRef FInternationalizationSettingsModelDetails::OnLanguageGenerateWidget( TSharedPtr Culture, IDetailLayoutBuilder* DetailBuilder ) const { return SNew(STextBlock) .Text(FCompareCultureByNativeLanguage::GetCultureNativeLanguageText(Culture)) .Font(DetailBuilder->GetDetailFont()); } void FInternationalizationSettingsModelDetails::OnLanguageSelectionChanged( TSharedPtr Culture, ESelectInfo::Type SelectionType ) { SelectedLanguage = Culture; SelectedCulture = NULL; RegionComboBox->ClearSelection(); RefreshAvailableRegions(); } FText FInternationalizationSettingsModelDetails::GetCurrentRegionText() const { if( SelectedCulture.IsValid() ) { return FCompareCultureByNativeRegion::GetCultureNativeRegionText(SelectedCulture); } return FText::GetEmpty(); } TSharedRef FInternationalizationSettingsModelDetails::OnRegionGenerateWidget( TSharedPtr Culture, IDetailLayoutBuilder* DetailBuilder ) const { return SNew(STextBlock) .Text(FCompareCultureByNativeRegion::GetCultureNativeRegionText(Culture)) .Font(DetailBuilder->GetDetailFont()); } void FInternationalizationSettingsModelDetails::OnRegionSelectionChanged( TSharedPtr Culture, ESelectInfo::Type SelectionType ) { SelectedCulture = Culture; HandleShutdownPostPackagesSaved(); } bool FInternationalizationSettingsModelDetails::IsRegionSelectionAllowed() const { return SelectedLanguage.IsValid(); } EVisibility FInternationalizationSettingsModelDetails::GetInternationalizationRestartRowVisibility() const { return RequiresRestart ? EVisibility::Visible : EVisibility::Collapsed; } void FInternationalizationSettingsModelDetails::ShoudLoadLocalizedFieldNamesCheckChanged(ESlateCheckBoxState::Type CheckState) { HandleShutdownPostPackagesSaved(); } void FInternationalizationSettingsModelDetails::HandleShutdownPostPackagesSaved() { if ( SelectedCulture.IsValid() ) { check(Model.IsValid()); Model->SetCultureName(SelectedCulture->GetName()); Model->ShouldLoadLocalizedPropertyNames(FieldNamesCheckBox->IsChecked()); if(SelectedCulture != FInternationalization::Get().GetCurrentCulture()) { RequiresRestart = true; } else { RequiresRestart = false; } } } #undef LOCTEXT_NAMESPACE