// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "LocalizationDashboardPrivatePCH.h" #include "SCulturePicker.h" #include "STableRow.h" #include "SSearchBox.h" #define LOCTEXT_NAMESPACE "CulturePicker" void SCulturePicker::Construct( const FArguments& InArgs ) { OnCultureSelectionChanged = InArgs._OnSelectionChanged; IsCulturePickable = InArgs._IsCulturePickable; TArray AllCultureNames; FInternationalization::Get().GetCultureNames(AllCultureNames); for (const auto& CultureName : AllCultureNames) { const FCulturePtr Culture = FInternationalization::Get().GetCulture(CultureName); if (Culture.IsValid()) { Cultures.AddUnique(Culture); const TArray ParentCultureNames = Culture->GetPrioritizedParentCultureNames(); for (const auto& ParentCultureName : ParentCultureNames) { const FCulturePtr ParentCulture = FInternationalization::Get().GetCulture(ParentCultureName); Cultures.AddUnique(ParentCulture); } } } BuildStockEntries(); RebuildEntries(); ChildSlot [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(SSearchBox) .HintText( LOCTEXT("SearchHintText", "Name/Abbreviation") ) .OnTextChanged(this, &SCulturePicker::OnFilterStringChanged) .DelayChangeNotificationsWhileTyping(true) ] +SVerticalBox::Slot() .FillHeight(1.0f) [ SAssignNew(TreeView, STreeView>) .SelectionMode(ESelectionMode::Single) .TreeItemsSource(&RootEntries) .OnGenerateRow(this, &SCulturePicker::OnGenerateRow) .OnGetChildren(this, &SCulturePicker::OnGetChildren) .OnSelectionChanged(this, &SCulturePicker::OnSelectionChanged) ] ]; const auto& IsInitialSelection = [&](const TSharedPtr& Entry) -> bool { return Entry->Culture == InArgs._InitialSelection; }; const TSharedPtr* InitialSelection = RootEntries.FindByPredicate(IsInitialSelection); if (InitialSelection) { TreeView->SetSelection(*InitialSelection); } } void SCulturePicker::RequestTreeRefresh() { RebuildEntries(); if (TreeView.IsValid()) { TreeView->RequestTreeRefresh(); } } void SCulturePicker::BuildStockEntries() { TArray StockCultures; TArray StockCultureNames; FInternationalization::Get().GetCultureNames(StockCultureNames); for (const auto& CultureName : StockCultureNames) { const FCulturePtr Culture = FInternationalization::Get().GetCulture(CultureName); if (Culture.IsValid()) { StockCultures.AddUnique(Culture); const TArray ParentCultureNames = Culture->GetPrioritizedParentCultureNames(); for (const auto& ParentCultureName : ParentCultureNames) { const FCulturePtr ParentCulture = FInternationalization::Get().GetCulture(ParentCultureName); StockCultures.AddUnique(ParentCulture); } } } TMap< FString, TSharedPtr > StockCultureEntries; StockCultureEntries.Reserve(StockCultures.Num()); StockEntries.Empty(StockCultures.Num()); StockEntries.Reserve(StockCultureEntries.Num()); for (const FCulturePtr& Culture : StockCultures) { const FString Name = Culture->GetName(); TSharedRef CultureEntry = MakeShareable( new FCultureEntry(Culture) ); // Add culture to entries. StockCultureEntries.Add( Name, CultureEntry ); // Add entry to list of root stock entries - it will be removed if someone has it as a child. StockEntries.Add( CultureEntry ); } // Update all parent entries to know about their child entries. for (auto& Pair : StockCultureEntries) { const TSharedPtr Entry = Pair.Value; const TArray ParentCultureNames = Entry->Culture->GetPrioritizedParentCultureNames(); TArray InvalidAncestors; for (const FString& ParentName : ParentCultureNames) { // Find existing parent entry. TSharedPtr ParentEntry; { const TSharedPtr* const PossibleParentEntry = StockCultureEntries.Find(ParentName); if (PossibleParentEntry) { ParentEntry = *PossibleParentEntry; } } // Skip unfound parents. if (!ParentEntry.IsValid()) { continue; } // Don't add self as child of self. if (Entry == ParentEntry) { continue; } // Invalidate ancestors of this parent entry. TArray ParentAncestorCultureNames = ParentEntry->Culture->GetPrioritizedParentCultureNames(); for (const auto& AncestorCultureName : ParentAncestorCultureNames) { // Don't treat this parent entry as a parent of itself. if (AncestorCultureName != ParentName) { InvalidAncestors.Add(AncestorCultureName); } } // Don't add ancestors of ancestors. if (InvalidAncestors.Contains(ParentName)) { continue; } // Add current entry to parent entry. ParentEntry->Children.Add( MakeShareable( new FCultureEntry(*Entry) ) ); // Remove the current entry from the base entries, as it is a child. StockEntries.Remove(Entry); } } // Sort entries. auto CultureEntryComparator = [](const TSharedPtr& LHS, const TSharedPtr& RHS) -> bool { return LHS->Culture->GetDisplayName() < RHS->Culture->GetDisplayName(); }; StockEntries.Sort(CultureEntryComparator); } void SCulturePicker::RebuildEntries() { RootEntries.Reset(); TFunctionRef >&, TArray< TSharedPtr >&)> DeepCopyAndFilter = [&](const TArray< TSharedPtr >& InEntries, TArray< TSharedPtr >& OutEntries) { for (const TSharedPtr& InEntry : InEntries) { // Set pickable flag. bool IsPickable = true; if (IsCulturePickable.IsBound()) { IsPickable = IsCulturePickable.Execute(InEntry->Culture); } TSharedRef OutEntry = MakeShareable(new FCultureEntry(InEntry->Culture, IsPickable)); // Recurse to children. DeepCopyAndFilter(InEntry->Children, OutEntry->Children); bool IsFilteredOut = false; if (!FilterString.IsEmpty()) { const FString Name = OutEntry->Culture->GetName(); const FString DisplayName = OutEntry->Culture->GetDisplayName(); IsFilteredOut = !Name.Contains(FilterString) && !DisplayName.Contains(FilterString); } // If has children, must be added. If it is not filtered and it is pickable, should be added. if ( OutEntry->Children.Num() != 0 || (!IsFilteredOut && IsPickable) ) { OutEntries.Add(OutEntry); } } }; DeepCopyAndFilter(StockEntries, RootEntries); } void SCulturePicker::OnFilterStringChanged(const FText& InFilterString) { FilterString = InFilterString.ToString(); RequestTreeRefresh(); } TSharedRef SCulturePicker::OnGenerateRow(TSharedPtr Entry, const TSharedRef& Table) { return SNew(STableRow< TSharedPtr >, Table) [ SNew(STextBlock) .Text( FText::FromString(Entry->Culture->GetDisplayName()) ) .ToolTip( SNew(SToolTip) .Content() [ SNew(STextBlock) .Text( FText::FromString(Entry->Culture->GetName()) ) .HighlightText( FText::FromString(FilterString) ) ] ) .HighlightText( FText::FromString(FilterString) ) .ColorAndOpacity( Entry->IsSelectable ? FSlateColor::UseForeground() : FSlateColor::UseSubduedForeground() ) // TODO: Appear differently if not selectable but present because it's part of the hierarchy. ]; } void SCulturePicker::OnGetChildren(TSharedPtr Entry, TArray< TSharedPtr >& Children) { // Add entries from children array. for (const auto& Child : Entry->Children) { Children.Add(Child); // Sort entries. auto CultureEntryComparator = [](const TSharedPtr& LHS, const TSharedPtr& RHS) -> bool { return LHS->Culture->GetDisplayName() < RHS->Culture->GetDisplayName(); }; Children.Sort(CultureEntryComparator); } } void SCulturePicker::OnSelectionChanged(TSharedPtr Entry, ESelectInfo::Type SelectInfo) { // Don't count as selection if the entry isn't actually selectable but is part of the hierarchy. if (Entry.IsValid() && Entry->IsSelectable) { OnCultureSelectionChanged.ExecuteIfBound( Entry->Culture, SelectInfo ); } } #undef LOCTEXT_NAMESPACE