Files
UnrealEngineUWP/Engine/Source/Developer/SuperSearch/Private/SSuperSearch.cpp
Ben Marsh 20bf0eb6a1 Updating copyright notices to 2017 (copying from //Tasks/UE4/Dev-Copyright-2017).
#rb none
#lockdown Nick.Penwarden

[CL 3226823 by Ben Marsh in Main branch]
2016-12-08 08:52:44 -05:00

754 lines
22 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "SSuperSearch.h"
#include "HAL/PlatformProcess.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Layout/WidgetPath.h"
#include "SlateOptMacros.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SMenuAnchor.h"
#include "Widgets/Views/SListView.h"
#if WITH_EDITOR
#include "EditorStyleSet.h"
#include "Engine/Blueprint.h"
#endif
#include "Interfaces/IHttpResponse.h"
#include "HttpModule.h"
#include "PlatformHttp.h"
#include "Widgets/Input/SSearchBox.h"
#if WITH_EDITOR
#include "ARFilter.h"
#include "AssetRegistryModule.h"
#include "IIntroTutorials.h"
#include "EditorTutorial.h"
#include "EngineAnalytics.h"
#include "AnalyticsEventAttribute.h"
#include "Interfaces/IAnalyticsProvider.h"
#endif
#include "Misc/Base64.h"
static TSharedRef<FSearchEntry> OtherCategory(new FSearchEntry());
static TSharedRef<FSearchEntry> AskQuestionEntry (new FSearchEntry());
#define LOCTEXT_NAMESPACE "SuperSearch"
SSuperSearchBox::SSuperSearchBox()
: SelectedSuggestion(INDEX_NONE)
, bIgnoreUIUpdate(false)
{
CategoryToIconMap.Add("Documentation", FName("LevelEditor.BrowseDocumentation") );
CategoryToIconMap.Add("Wiki", FName("MainFrame.VisitWiki") );
CategoryToIconMap.Add("Answerhub", FName("MainFrame.VisitSearchForAnswersPage") );
CategoryToIconMap.Add("API", FName("LevelEditor.BrowseAPIReference") );
CategoryToIconMap.Add("Tutorials", FName("LevelEditor.Tutorials") );
OtherCategory->Title = NSLOCTEXT("SuperSearchBox", "OtherCategory", "Help").ToString();
OtherCategory->bCategory = true;
AskQuestionEntry->Title = NSLOCTEXT("SuperSearchBox", "AskQuestion", "Ask Online").ToString();
AskQuestionEntry->URL = "https://answers.unrealengine.com/questions/ask.html";
AskQuestionEntry->bCategory = false;
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSuperSearchBox::Construct( const FArguments& InArgs )
{
CurrentSearchEngine = InArgs._SearchEngine;
if (InArgs._Style.IsSet() && InArgs._Style.GetValue() != nullptr)
{
Style = *InArgs._Style.GetValue();
}
else
{
Style.SetForegroundColor(FCoreStyle::Get().GetSlateColor("InvertedForeground"));
Style.SetComboBoxStyle(FCoreStyle::Get().GetWidgetStyle<FComboBoxStyle>("ComboBox"));
Style.SetTextBlockStyle(FCoreStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText"));
Style.SetSearchBoxStyle(FCoreStyle::Get().GetWidgetStyle<FSearchBoxStyle>("SearchBox"));
}
ChildSlot
[
SAssignNew( SuggestionBox, SMenuAnchor )
.Placement( InArgs._SuggestionListPlacement )
.Method( EPopupMethod::UseCurrentWindow )
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SAssignNew(InputText, SSearchBox)
.Style(&Style.SearchBoxStyle)
.OnTextCommitted(this, &SSuperSearchBox::OnTextCommitted)
.HintText(NSLOCTEXT("SuperSearchBox", "HelpHint", "Search For Help"))
.OnTextChanged(this, &SSuperSearchBox::OnTextChanged)
.SelectAllTextWhenFocused(false)
.DelayChangeNotificationsWhileTyping(true)
.MinDesiredWidth(200)
]
]
.MenuContent
(
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("Menu.Background"))
.Padding( FMargin(2) )
[
SNew(SBox)
[
SAssignNew(SuggestionListView, SListView< TSharedPtr<FSearchEntry> >)
.ListItemsSource(&Suggestions)
.SelectionMode( ESelectionMode::Single ) // Ideally the mouse over would not highlight while keyboard controls the UI
.OnGenerateRow(this, &SSuperSearchBox::MakeSuggestionListItemWidget)
.OnMouseButtonClick(this, &SSuperSearchBox::SuggestionSelectionChanged)
.ItemHeight(18)
]
]
)
.OnMenuOpenChanged(this, &SSuperSearchBox::OnMenuOpenChanged)
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSuperSearchBox::SetSearchEngine(ESearchEngine SearchEngine)
{
CurrentSearchEngine = SearchEngine;
}
int32 GetNumRealSuggestions(const TArray< TSharedPtr<FSearchEntry> > & Suggestions)
{
int32 NumSuggestions = 0;
for (auto Entry : Suggestions)
{
if (Entry->bCategory == false)
{
++NumSuggestions;
}
}
return NumSuggestions;
}
void SSuperSearchBox::OnMenuOpenChanged( bool bIsOpen )
{
#if WITH_EDITOR
if (bIsOpen == false)
{
if ( FEngineAnalytics::IsAvailable() )
{
TArray< FAnalyticsEventAttribute > SearchAttribs;
SearchAttribs.Add(FAnalyticsEventAttribute(TEXT("SearchTerm"), InputText->GetText().ToString()));
SearchAttribs.Add(FAnalyticsEventAttribute(TEXT("NumFoundResults"), GetNumRealSuggestions(Suggestions)));
SearchAttribs.Add(FAnalyticsEventAttribute(TEXT("ClickedTitle"), EntryClicked.Get() ? EntryClicked->Title : ""));
SearchAttribs.Add(FAnalyticsEventAttribute(TEXT("ClickedOnSomething"), EntryClicked.IsValid()));
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.SuperSearch"), SearchAttribs);
}
InputText->SetText(FText::GetEmpty());
}
#endif
EntryClicked = nullptr;
}
/** Input string should have no leading & or ? */
static FString AddQueryStringToURL(FString const& URL, TCHAR const* StringToAdd)
{
if (StringToAdd)
{
// split url into path, querystring, and anchor parts
FString Anchor;
FString Path;
FString QueryString;
FString PathAndQueryString;
if (URL.Split(TEXT("#"), &PathAndQueryString, &Anchor) == false)
{
PathAndQueryString = URL;
}
if (PathAndQueryString.Split(TEXT("?"), &Path, &QueryString) == false)
{
Path = URL;
}
if (QueryString.IsEmpty())
{
QueryString = FString::Printf(TEXT("?%s"), StringToAdd);
}
else
{
QueryString = FString::Printf(TEXT("%s&%s"), *QueryString, StringToAdd);
}
// reassemble and return
return Path + QueryString + Anchor;
}
return URL;
}
void SSuperSearchBox::ActOnSuggestion(TSharedPtr<FSearchEntry> SearchEntry, FString const& Category)
{
if (SearchEntry->bCategory == false)
{
EntryClicked = SearchEntry;
#if WITH_EDITOR
// Broadcast text change to anyone registered for it
FSuperSearchModule& SuperSearchModule = FModuleManager::LoadModuleChecked< FSuperSearchModule >(TEXT("SuperSearch"));
SuperSearchModule.GetActOnSearchTextClicked().Broadcast(SearchEntry);
// See if the search has tutorial hits
if (SearchEntry->URL.IsEmpty())
{
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *(SearchEntry->AssetData.ObjectPath.ToString()));
if( Blueprint != nullptr )
{
UEditorTutorial* Tutorial = Blueprint->GeneratedClass->GetDefaultObject<UEditorTutorial>();
if( Tutorial!= nullptr)
{
IIntroTutorials& IntroTutorials = IIntroTutorials::Get();
FWidgetPath OutWidgetPath;
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(SuggestionBox.ToSharedRef(), OutWidgetPath);
IntroTutorials.LaunchTutorial(Tutorial, IIntroTutorials::ETutorialStartType::TST_RESTART, ParentWindow);
}
}
}
else
#endif
{
// Check for documentation hits
if (Category == TEXT("documentation"))
{
// append some tracking data to the URL
FString const FinalURL = AddQueryStringToURL(SearchEntry->URL, TEXT("utm_source=editor&utm_medium=search&utm_campaign=user_initiated"));
FPlatformProcess::LaunchURL(*FinalURL, NULL, NULL);
}
else
{
FPlatformProcess::LaunchURL(*SearchEntry->URL, NULL, NULL);
}
}
SuggestionBox->SetIsOpen(false);
}
}
void SSuperSearchBox::SuggestionSelectionChanged(TSharedPtr<FSearchEntry> NewValue)
{
if(bIgnoreUIUpdate)
{
return;
}
if (NewValue.Get() == NULL || NewValue->bCategory)
{
return;
}
int32 CurrentCategory = INDEX_NONE;
for(int32 i = 0; i < Suggestions.Num(); ++i)
{
if (Suggestions[i]->bCategory)
{
CurrentCategory = i;
}
if(NewValue == Suggestions[i])
{
SelectedSuggestion = i;
MarkActiveSuggestion();
FString const Category = CurrentCategory != INDEX_NONE ? Suggestions[CurrentCategory]->Title : FString();
ActOnSuggestion(NewValue, Category);
break;
}
}
}
TSharedRef<ITableRow> SSuperSearchBox::MakeSuggestionListItemWidget(TSharedPtr<FSearchEntry> Suggestion, const TSharedRef<STableViewBase>& OwnerTable)
{
check(Suggestion.IsValid());
FString Left, Right, Combined;
if (Suggestion->bCategory)
{
Combined = "- ";
}
if(Suggestion->Title.Split(TEXT("\t"), &Left, &Right))
{
Combined += Left + Right;
}
else
{
Combined += Suggestion->Title;
}
bool bCategory = Suggestion->bCategory;
TSharedPtr<SWidget> IconWidget;
if (bCategory)
{
#if WITH_EDITOR
FName* IconResult = CategoryToIconMap.Find(Combined);
SAssignNew(IconWidget, SImage)
.Image(FEditorStyle::GetBrush(IconResult ? *IconResult : FName("MainFrame.VisitEpicGamesDotCom") ));
#endif
}
else
{
SAssignNew(IconWidget, SSpacer);
}
return
SNew(STableRow< TSharedPtr<FString> >, OwnerTable)
.ShowSelection(bCategory == false)
.Padding(FMargin(bCategory ? 2.5 : 16, 2.5, 2.5, 2.5))
[
SNew(SHorizontalBox)
#if WITH_EDITOR
+ SHorizontalBox::Slot()
.Padding(FMargin(0,0,2.5,0))
[
IconWidget.ToSharedRef()
]
#endif
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString(Combined))
#if WITH_EDITOR
.TextStyle(&FEditorStyle::Get().GetWidgetStyle<FTextBlockStyle>(bCategory ? "SuperSearchCategoryText" : "NormalText"))
#endif
.MinDesiredWidth(600)
]
];
}
void SSuperSearchBox::OnTextChanged(const FText& InText)
{
if(bIgnoreUIUpdate)
{
return;
}
const FString& InputTextStr = InputText->GetText().ToString();
if(!InputTextStr.IsEmpty())
{
TSharedRef<class IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
FString UrlEncodedString = FPlatformHttp::UrlEncode(InText.ToString()); //we need to url encode for special characters (especially other languages)
if ( CurrentSearchEngine == ESearchEngine::Bing )
{
//search through bing for online content
static FString AccountKey = FBase64::Encode(TEXT(":RSY8G4W5Fnb+baWUUq9N2eYa5nWuu2tKhNEoJzQEykY"));
static FString AuthToken = FString::Printf(TEXT("Basic %s"), *AccountKey);
// use partial response to only look at items and things we care about for items right now (title,link and label name)
const FText QueryURL = FText::Format(FText::FromString("https://api.datamarket.azure.com/Data.ashx/Bing/Search/v1/Web?$format=json&Query=%27site%3Aunrealengine.com%20{0}%27"), FText::FromString(UrlEncodedString));
//save http request into map to ensure correct ordering
FText& Query = RequestQueryMap.FindOrAdd(HttpRequest);
Query = InText;
// kick off http request to read friends
HttpRequest->OnProcessRequestComplete().BindRaw(this, &SSuperSearchBox::Query_HttpRequestComplete);
HttpRequest->SetURL(QueryURL.ToString());
//HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
HttpRequest->SetHeader(TEXT("Authorization"), AuthToken);
HttpRequest->SetVerb(TEXT("GET"));
HttpRequest->ProcessRequest();
}
else
//search through google for online content
{
// use partial response to only look at items and things we care about for items right now (title,link and label name)
const FText QueryURL = FText::Format(FText::FromString("https://www.googleapis.com/customsearch/v1?key=AIzaSyCMGfdDaSfjqv5zYoS0mTJnOT3e9MURWkU&cx=009868829633250020713:y7tfd8hlcgg&fields=items(title,link,labels/name)&q={0}+less:forums"), FText::FromString(UrlEncodedString));
//save http request into map to ensure correct ordering
FText& Query = RequestQueryMap.FindOrAdd(HttpRequest);
Query = InText;
// kick off http request to read friends
HttpRequest->OnProcessRequestComplete().BindRaw(this, &SSuperSearchBox::Query_HttpRequestComplete);
HttpRequest->SetURL(QueryURL.ToString());
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
HttpRequest->SetVerb(TEXT("GET"));
HttpRequest->ProcessRequest();
}
#if WITH_EDITOR
//search local tutorials
{
FAssetRegistryModule& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FARFilter Filter;
Filter.ClassNames.Add(UBlueprint::StaticClass()->GetFName());
Filter.bRecursiveClasses = true;
Filter.TagsAndValues.Add(TEXT("ParentClass"), FString::Printf(TEXT("%s'%s'"), *UClass::StaticClass()->GetName(), *UEditorTutorial::StaticClass()->GetPathName()));
TArray<FAssetData> AssetData;
AssetRegistry.Get().GetAssets(Filter, AssetData);
//add search result into cache
FSearchResults& SearchResults = SearchResultsCache.FindOrAdd(InText.ToString());
TArray<FSearchEntry>& TutorialResults = SearchResults.TutorialResults;
TutorialResults.Empty();
for (const FAssetData& Asset : AssetData)
{
const FString* SearchTag = Asset.TagsAndValues.Find("SearchTags");
const FString* ResultTitle = Asset.TagsAndValues.Find("Title");
if( ResultTitle)
{
if (ResultTitle->Contains(InText.ToString()))
{
FSearchEntry SearchEntry;
SearchEntry.Title = *ResultTitle;
SearchEntry.URL = "";
SearchEntry.bCategory = false;
SearchEntry.AssetData = Asset;
TutorialResults.Add(SearchEntry);
}
}
// If the asset has search tags, search them
if ((SearchTag) && (SearchTag->IsEmpty()== false))
{
TArray<FString> SearchTags;
SearchTag->ParseIntoArray(SearchTags,TEXT(","));
//trim any xs spaces off the strings.
for (int32 iTag = 0; iTag < SearchTags.Num() ; iTag++)
{
SearchTags[iTag] = SearchTags[iTag].Trim();
SearchTags[iTag] = SearchTags[iTag].TrimTrailing();
}
if (SearchTags.Find(InText.ToString()) != INDEX_NONE)
{
FSearchEntry SearchEntry;
if (ResultTitle)
{
SearchEntry.Title = *ResultTitle;
}
SearchEntry.URL = "";
SearchEntry.bCategory = false;
SearchEntry.AssetData = Asset;
TutorialResults.Add(SearchEntry);
}
}
}
}
#endif
}
else
{
ClearSuggestions();
}
}
void SSuperSearchBox::OnTextCommitted( const FText& InText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnEnter)
{
if (SelectedSuggestion >= 1)
{
// find the category
FString Category;
for (int32 i = SelectedSuggestion; i >= 0; --i)
{
if (Suggestions[i]->bCategory)
{
Category = Suggestions[i]->Title;
break;
}
}
ActOnSuggestion(Suggestions[SelectedSuggestion], Category);
}
else
{
OnTextChanged(InText);
}
}
}
void SSuperSearchBox::Query_HttpRequestComplete(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded)
{
bool bResult = false;
FString ResponseStr, ErrorStr;
if (bSucceeded && HttpResponse.IsValid())
{
ResponseStr = HttpResponse->GetContentAsString();
if (EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode()))
{
// Create the Json parser
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(ResponseStr);
if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid())
{
if (FText * QueryText = RequestQueryMap.Find(HttpRequest))
{
const TArray<TSharedPtr<FJsonValue> > * JsonItems = nullptr;
//add search result into cache
FSearchResults & SearchResults = SearchResultsCache.FindOrAdd(QueryText->ToString());
FCategoryResults & OnlineResults = SearchResults.OnlineResults;
OnlineResults.Empty(); //reset online results since we just got updated
// with bing search result
if ( CurrentSearchEngine == ESearchEngine::Bing )
{
int32 iFitResultCount = 0;
TSharedPtr<FJsonValue> Field = JsonObject->TryGetField(TEXT("d"));
if ( Field.IsValid() && Field->AsObject()->TryGetArrayField(TEXT("results"), JsonItems) )
{
// The default bing search list the top 50 results, we need to filter out the 10 from wiki, docs and answers.
for ( TSharedPtr<FJsonValue> JsonItem : *JsonItems )
{
const TSharedPtr<FJsonObject> & ItemObject = JsonItem->AsObject();
FSearchEntry SearchEntry;
SearchEntry.Title = ItemObject->GetStringField("Title");
SearchEntry.URL = ItemObject->GetStringField("Url");
SearchEntry.bCategory = false;
FString LabelString;
bool bValidLabel = true;
if ( SearchEntry.URL.Contains(TEXT("://docs.unrealengine.com")) )
{
LabelString = TEXT("documentation");
}
else if ( SearchEntry.URL.Contains(TEXT("://wiki.unrealengine.com")) )
{
LabelString = TEXT("wiki");
}
else if ( SearchEntry.URL.Contains(TEXT("://answers.unrealengine.com")) )
{
LabelString = TEXT("answers");
}
else
{
bValidLabel = false;
}
if ( bValidLabel )
{
TArray<FSearchEntry> & SuggestionCategory = OnlineResults.FindOrAdd(LabelString);
SuggestionCategory.Add(SearchEntry);
if ( ++iFitResultCount > 9 )
{
break;
}
}
}
}
}
// with google search result
if (JsonObject->TryGetArrayField(TEXT("items"), JsonItems))
{
for (TSharedPtr<FJsonValue> JsonItem : *JsonItems)
{
const TSharedPtr<FJsonObject> & ItemObject = JsonItem->AsObject();
FSearchEntry SearchEntry;
SearchEntry.Title = ItemObject->GetStringField("title");
SearchEntry.URL = ItemObject->GetStringField("link");
SearchEntry.bCategory = false;
bool bValidLabel = false;
TArray<TSharedPtr<FJsonValue> > Labels = ItemObject->GetArrayField(TEXT("labels"));
for (TSharedPtr<FJsonValue> Label : Labels)
{
const TSharedPtr<FJsonObject> & LabelObject = Label->AsObject();
FString LabelString = LabelObject->GetStringField(TEXT("name"));
TArray<FSearchEntry> & SuggestionCategory = OnlineResults.FindOrAdd(LabelString);
SuggestionCategory.Add(SearchEntry);
}
}
}
bResult = true;
}
}
}
else
{
ErrorStr = FString::Printf(TEXT("Invalid response. code=%d error=%s"),
HttpResponse->GetResponseCode(), *ResponseStr);
}
}
if (bResult)
{
UpdateSuggestions();
}
}
FReply SSuperSearchBox::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& KeyEvent )
{
if(SuggestionBox->IsOpen() && Suggestions.Num())
{
if(KeyEvent.GetKey() == EKeys::Up || KeyEvent.GetKey() == EKeys::Down)
{
if(KeyEvent.GetKey() == EKeys::Up)
{
//if we're at the top swing around
if(SelectedSuggestion == 1)
{
SelectedSuggestion = Suggestions.Num() - 1;
}
else
{
// got one up
--SelectedSuggestion;
//make sure we're not selecting category
if (Suggestions[SelectedSuggestion]->bCategory)
{
//we know that category is never shown empty so above us will be fine
--SelectedSuggestion;
}
}
}
if(KeyEvent.GetKey() == EKeys::Down)
{
if(SelectedSuggestion < Suggestions.Num() - 1)
{
// go one down, possibly from edit control to top
++SelectedSuggestion;
//make sure we're not selecting category
if (Suggestions[SelectedSuggestion]->bCategory)
{
//we know that category is never shown empty so below us will be fine
++SelectedSuggestion;
}
}
else
{
// back to edit control
SelectedSuggestion = 1;
}
}
MarkActiveSuggestion();
return FReply::Handled();
}
}
return FReply::Unhandled();
}
void UpdateSuggestionHelper(const FText & CategoryLabel, const TArray<FSearchEntry> & Elements, TArray<TSharedPtr< FSearchEntry > > & OutSuggestions)
{
if (Elements.Num())
{
OutSuggestions.Add(MakeShareable(FSearchEntry::MakeCategoryEntry(CategoryLabel.ToString())));
}
for (uint32 i = 0; i < (uint32)Elements.Num(); ++i)
{
OutSuggestions.Add(MakeShareable(new FSearchEntry(Elements[i])));
}
}
void SSuperSearchBox::UpdateSuggestions()
{
const FText& Query = InputText->GetText();
FSearchResults* SearchResults = SearchResultsCache.Find(Query.ToString());
//go through and build new suggestion list for list view widget
ClearSuggestions();
//still waiting on results for current query
if (SearchResults == nullptr)
{
return;
}
//first tutorials
UpdateSuggestionHelper(NSLOCTEXT("SuperSearch", "tutorials", "Tutorials"), SearchResults->TutorialResults, Suggestions);
//then docs
UpdateSuggestionHelper(NSLOCTEXT("SuperSearch", "docs", "Documentation"), SearchResults->OnlineResults.FindOrAdd(TEXT("documentation")), Suggestions);
//then api
UpdateSuggestionHelper(NSLOCTEXT("SuperSearch", "API", "API"), SearchResults->OnlineResults.FindOrAdd(TEXT("api")), Suggestions);
//then wiki
UpdateSuggestionHelper(NSLOCTEXT("SuperSearch", "wiki", "Wiki"), SearchResults->OnlineResults.FindOrAdd(TEXT("wiki")), Suggestions);
//then answerhub
UpdateSuggestionHelper(NSLOCTEXT("SuperSearch", "answers", "Answerhub"), SearchResults->OnlineResults.FindOrAdd(TEXT("answers")), Suggestions);
FSuperSearchModule& SuperSearchModule = FModuleManager::LoadModuleChecked< FSuperSearchModule >(TEXT("SuperSearch"));
//Broadcast to anyone registered
SuperSearchModule.GetSearchTextChanged().Broadcast(InputText->GetText().ToString(),Suggestions);
//finally add other category
Suggestions.Add(OtherCategory);
Suggestions.Add(AskQuestionEntry);
SelectedSuggestion = 1;
// Ideally if the selection box is open the output window is not changing it's window title (flickers)
SuggestionBox->SetIsOpen(true, false);
MarkActiveSuggestion();
// Force the textbox back into focus.
FSlateApplication::Get().SetKeyboardFocus(InputText.ToSharedRef(), EFocusCause::SetDirectly);
}
void SSuperSearchBox::MarkActiveSuggestion()
{
bIgnoreUIUpdate = true;
if(SelectedSuggestion >= 1)
{
SuggestionListView->SetSelection(Suggestions[SelectedSuggestion]);
SuggestionListView->RequestScrollIntoView(Suggestions[SelectedSuggestion]); // Ideally this would only scroll if outside of the view
}
else
{
SuggestionListView->ClearSelection();
}
bIgnoreUIUpdate = false;
}
void SSuperSearchBox::ClearSuggestions()
{
SelectedSuggestion = INDEX_NONE;
Suggestions.Empty();
}
FString SSuperSearchBox::GetSelectionText() const
{
FString ret = Suggestions[SelectedSuggestion]->Title;
ret = ret.Replace(TEXT("\t"), TEXT(""));
return ret;
}
#undef LOCTEXT_NAMESPACE