2014-09-12 05:28:34 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "IntroTutorialsPrivatePCH.h"
# include "TutorialText.h"
# include "IDocumentation.h"
# include "ISourceCodeAccessModule.h"
# include "ContentBrowserModule.h"
# include "DesktopPlatformModule.h"
# include "Editor/MainFrame/Public/MainFrame.h"
# include "EditorTutorial.h"
# include "SourceCodeNavigation.h"
2014-09-18 08:09:29 -04:00
# include "TutorialImageDecorator.h"
2014-09-18 08:10:29 -04:00
# include "EngineAnalytics.h"
# include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
2014-09-12 05:28:34 -04:00
# define LOCTEXT_NAMESPACE "TutorialText"
TArray < TSharedPtr < FHyperlinkTypeDesc > > FTutorialText : : HyperlinkDescs ;
/**
* This is a custom decorator used to allow arbitrary styling of text within a rich - text editor
* This is required since normal text styling can only work with known styles from a given Slate style - set
*/
class FTextStyleDecorator : public ITextDecorator
{
public :
static TSharedRef < FTextStyleDecorator > Create ( )
{
return MakeShareable ( new FTextStyleDecorator ( ) ) ;
}
virtual ~ FTextStyleDecorator ( )
{
}
virtual bool Supports ( const FTextRunParseResults & RunParseResult , const FString & Text ) const override
{
return ( RunParseResult . Name = = TEXT ( " TextStyle " ) ) ;
}
virtual TSharedRef < ISlateRun > Create ( const TSharedRef < class FTextLayout > & TextLayout , const FTextRunParseResults & RunParseResult , const FString & OriginalText , const TSharedRef < FString > & InOutModelText , const ISlateStyle * Style ) override
{
FRunInfo RunInfo ( RunParseResult . Name ) ;
for ( const TPair < FString , FTextRange > & Pair : RunParseResult . MetaData )
{
RunInfo . MetaData . Add ( Pair . Key , OriginalText . Mid ( Pair . Value . BeginIndex , Pair . Value . EndIndex - Pair . Value . BeginIndex ) ) ;
}
FTextRange ModelRange ;
ModelRange . BeginIndex = InOutModelText - > Len ( ) ;
* InOutModelText + = OriginalText . Mid ( RunParseResult . ContentRange . BeginIndex , RunParseResult . ContentRange . EndIndex - RunParseResult . ContentRange . BeginIndex ) ;
ModelRange . EndIndex = InOutModelText - > Len ( ) ;
return FSlateTextRun : : Create ( RunInfo , InOutModelText , FTextStyleAndName : : CreateTextBlockStyle ( RunInfo ) , ModelRange ) ;
}
} ;
static void OnBrowserLinkClicked ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url )
{
2014-09-18 08:10:29 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " BrowserLink " ) , * Url ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Rocket.Tutorials.BrowserLinkClicked " ) , EventAttributes ) ;
}
2014-09-12 05:28:34 -04:00
FPlatformProcess : : LaunchURL ( * * Url , nullptr , nullptr ) ;
}
}
static void OnDocLinkClicked ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url )
{
2014-09-18 08:10:29 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " DocLink " ) , * Url ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Rocket.Tutorials.DocLinkClicked " ) , EventAttributes ) ;
}
2014-09-12 05:28:34 -04:00
IDocumentation : : Get ( ) - > Open ( * Url ) ;
}
}
static void ParseTutorialLink ( const FString & InternalLink )
{
UBlueprint * Blueprint = LoadObject < UBlueprint > ( nullptr , * InternalLink ) ;
if ( Blueprint & & Blueprint - > GeneratedClass )
{
FIntroTutorials & IntroTutorials = FModuleManager : : GetModuleChecked < FIntroTutorials > ( TEXT ( " IntroTutorials " ) ) ;
IMainFrameModule & MainFrameModule = FModuleManager : : LoadModuleChecked < IMainFrameModule > ( TEXT ( " MainFrame " ) ) ;
const bool bRestart = true ;
IntroTutorials . LaunchTutorial ( Blueprint - > GeneratedClass - > GetDefaultObject < UEditorTutorial > ( ) , bRestart , MainFrameModule . GetParentWindow ( ) ) ;
2014-09-18 08:10:29 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " TutorialLink " ) , InternalLink ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Rocket.Tutorials.TutorialLinkClicked " ) , EventAttributes ) ;
}
2014-09-12 05:28:34 -04:00
}
}
static void OnTutorialLinkClicked ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url )
{
ParseTutorialLink ( * Url ) ;
}
}
static void ParseCodeLink ( const FString & InternalLink )
{
// Tokens used by the code parsing. Details in the parse section
static const FString ProjectSpecifier ( TEXT ( " [PROJECTPATH] " ) ) ;
static const FString ProjectPathSpecifier ( TEXT ( " [PROJECT] " ) ) ;
static const FString EnginePathSpecifier ( TEXT ( " [ENGINEPATH] " ) ) ;
FString Path ;
int32 Line = 0 ;
int32 Col = 0 ;
TArray < FString > Tokens ;
InternalLink . ParseIntoArray ( & Tokens , TEXT ( " , " ) , 0 ) ;
int32 TokenStringsCount = Tokens . Num ( ) ;
if ( TokenStringsCount > 0 )
{
Path = Tokens [ 0 ] ;
}
if ( TokenStringsCount > 1 )
{
TTypeFromString < int32 > : : FromString ( Line , * Tokens [ 1 ] ) ;
}
if ( TokenStringsCount > 2 )
{
TTypeFromString < int32 > : : FromString ( Col , * Tokens [ 2 ] ) ;
}
ISourceCodeAccessModule & SourceCodeAccessModule = FModuleManager : : LoadModuleChecked < ISourceCodeAccessModule > ( " SourceCodeAccess " ) ;
ISourceCodeAccessor & SourceCodeAccessor = SourceCodeAccessModule . GetAccessor ( ) ;
if ( Path . Contains ( EnginePathSpecifier ) = = true )
{
// replace engine path specifier with path to engine
Path . ReplaceInline ( * EnginePathSpecifier , * FPaths : : EngineDir ( ) ) ;
}
if ( Path . Contains ( ProjectSpecifier ) = = true )
{
// replace project specifier with path to project
Path . ReplaceInline ( * ProjectSpecifier , FApp : : GetGameName ( ) ) ;
}
if ( Path . Contains ( ProjectPathSpecifier ) = = true )
{
// replace project specifier with path to project
Path . ReplaceInline ( * ProjectPathSpecifier , * FPaths : : GetProjectFilePath ( ) ) ;
}
Path = FPaths : : ConvertRelativePathToFull ( Path ) ;
SourceCodeAccessor . OpenFileAtLine ( Path , Line , Col ) ;
2014-09-18 08:10:29 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " CodeLink " ) , InternalLink ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Rocket.Tutorials.CodeLinkClicked " ) , EventAttributes ) ;
}
2014-09-12 05:28:34 -04:00
}
static void OnCodeLinkClicked ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url )
{
ParseCodeLink ( * Url ) ;
}
}
static void ParseAssetLink ( const FString & InternalLink , const FString * Action )
{
2014-09-16 10:26:12 -04:00
UObject * RequiredObject = LoadObject < UObject > ( nullptr , * InternalLink ) ;
2014-09-12 05:28:34 -04:00
if ( RequiredObject ! = nullptr )
{
if ( Action & & * Action = = TEXT ( " select " ) )
{
FContentBrowserModule & ContentBrowserModule = FModuleManager : : Get ( ) . LoadModuleChecked < FContentBrowserModule > ( " ContentBrowser " ) ;
TArray < UObject * > AssetToBrowse ;
AssetToBrowse . Add ( RequiredObject ) ;
ContentBrowserModule . Get ( ) . SyncBrowserToAssets ( AssetToBrowse ) ;
}
else
{
FAssetEditorManager : : Get ( ) . OpenEditorForAsset ( RequiredObject ) ;
}
2014-09-18 08:10:29 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > EventAttributes ;
EventAttributes . Add ( FAnalyticsEventAttribute ( TEXT ( " AssetLink " ) , InternalLink ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Rocket.Tutorials.AssetLinkClicked " ) , EventAttributes ) ;
}
2014-09-12 05:28:34 -04:00
}
}
static void OnAssetLinkClicked ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
const FString * Action = Metadata . Find ( TEXT ( " action " ) ) ;
if ( Url )
{
ParseAssetLink ( * Url , Action ) ;
}
}
static TSharedRef < IToolTip > OnGenerateDocTooltip ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
FText DisplayText ;
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url ! = nullptr )
{
DisplayText = FText : : Format ( LOCTEXT ( " DocLinkPattern " , " View Documentation: {0} " ) , FText : : FromString ( * Url ) ) ;
}
else
{
DisplayText = LOCTEXT ( " UnknownLink " , " Empty Hyperlink " ) ;
}
FString UrlString ;
if ( Url ! = nullptr )
{
UrlString = * Url ;
}
// urls for rich tooltips must start with Shared/
if ( UrlString . Len ( ) > 0 & & ! UrlString . StartsWith ( TEXT ( " Shared " ) ) )
{
UrlString = FString ( TEXT ( " Shared " ) ) / UrlString ;
}
FString ExcerptString ;
const FString * Excerpt = Metadata . Find ( TEXT ( " excerpt " ) ) ;
if ( Excerpt ! = nullptr )
{
ExcerptString = * Excerpt ;
}
return IDocumentation : : Get ( ) - > CreateToolTip ( DisplayText , nullptr , UrlString , ExcerptString ) ;
}
static FText OnGetAssetTooltipText ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url ! = nullptr )
{
const FString * Action = Metadata . Find ( TEXT ( " action " ) ) ;
return FText : : Format ( LOCTEXT ( " AssetLinkPattern " , " {0} asset: {1} " ) , ( Action = = nullptr | | * Action = = TEXT ( " select " ) ) ? LOCTEXT ( " AssetOpenDesc " , " Open " ) : LOCTEXT ( " AssetFindDesc " , " Find " ) , FText : : FromString ( * Url ) ) ;
}
return LOCTEXT ( " InvalidAssetLink " , " Invalid Asset Link " ) ;
}
static FText OnGetCodeTooltipText ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url ! = nullptr )
{
const bool bUseShortIDEName = true ;
return FText : : Format ( LOCTEXT ( " CodeLinkPattern " , " Open code in {0}: {1} " ) , FSourceCodeNavigation : : GetSuggestedSourceCodeIDE ( ) , FText : : FromString ( * Url ) ) ;
}
return LOCTEXT ( " InvalidCodeLink " , " Invalid Code Link " ) ;
}
static FText OnGetTutorialTooltipText ( const FSlateHyperlinkRun : : FMetadata & Metadata )
{
const FString * Url = Metadata . Find ( TEXT ( " href " ) ) ;
if ( Url ! = nullptr )
{
return FText : : Format ( LOCTEXT ( " TutorialLinkPattern " , " Open tutorial: {0} " ) , FText : : FromString ( * Url ) ) ;
}
return LOCTEXT ( " InvalidTutorialLink " , " Invalid Tutorial Link " ) ;
}
void FTutorialText : : GetRichTextDecorators ( TArray < TSharedRef < class ITextDecorator > > & OutDecorators )
{
Initialize ( ) ;
for ( const auto & HyperlinkDesc : HyperlinkDescs )
{
OutDecorators . Add ( FHyperlinkDecorator : : Create ( HyperlinkDesc - > Id , HyperlinkDesc - > OnClickedDelegate , HyperlinkDesc - > TooltipTextDelegate , HyperlinkDesc - > TooltipDelegate ) ) ;
}
OutDecorators . Add ( FTextStyleDecorator : : Create ( ) ) ;
2014-09-18 08:09:29 -04:00
OutDecorators . Add ( FTutorialImageDecorator : : Create ( ) ) ;
2014-09-12 05:28:34 -04:00
}
void FTutorialText : : Initialize ( )
{
if ( HyperlinkDescs . Num ( ) = = 0 )
{
HyperlinkDescs . Add ( MakeShareable ( new FHyperlinkTypeDesc (
EHyperlinkType : : Browser ,
LOCTEXT ( " BrowserLinkTypeLabel " , " URL " ) ,
LOCTEXT ( " BrowserLinkTypeTooltip " , " A link that opens a browser window (e.g. http://www.unrealengine.com) " ) ,
TEXT ( " browser " ) ,
FSlateHyperlinkRun : : FOnClick : : CreateStatic ( & OnBrowserLinkClicked ) ) ) ) ;
HyperlinkDescs . Add ( MakeShareable ( new FHyperlinkTypeDesc (
EHyperlinkType : : UDN ,
LOCTEXT ( " UDNLinkTypeLabel " , " UDN " ) ,
LOCTEXT ( " UDNLinkTypeTooltip " , " A link that opens some UDN documentation (e.g. /Engine/Blueprints/UserGuide/Types/ClassBlueprint) " ) ,
TEXT ( " udn " ) ,
FSlateHyperlinkRun : : FOnClick : : CreateStatic ( & OnDocLinkClicked ) ,
FSlateHyperlinkRun : : FOnGetTooltipText ( ) ,
FSlateHyperlinkRun : : FOnGenerateTooltip : : CreateStatic ( & OnGenerateDocTooltip ) ) ) ) ;
HyperlinkDescs . Add ( MakeShareable ( new FHyperlinkTypeDesc (
EHyperlinkType : : Asset ,
LOCTEXT ( " AssetLinkTypeLabel " , " Asset " ) ,
LOCTEXT ( " AssetLinkTypeTooltip " , " A link that opens an asset (e.g. /Game/StaticMeshes/SphereMesh.SphereMesh) " ) ,
TEXT ( " asset " ) ,
FSlateHyperlinkRun : : FOnClick : : CreateStatic ( & OnAssetLinkClicked ) ,
FSlateHyperlinkRun : : FOnGetTooltipText : : CreateStatic ( & OnGetAssetTooltipText ) ) ) ) ;
HyperlinkDescs . Add ( MakeShareable ( new FHyperlinkTypeDesc (
EHyperlinkType : : Code ,
LOCTEXT ( " CodeLinkTypeLabel " , " Code " ) ,
LOCTEXT ( " CodeLinkTypeTooltip " , " A link that opens code in your selected IDE. \n For example: [PROJECTPATH]/Private/SourceFile.cpp,1,1. \n The numbers correspond to line number and column number. \n You can use [PROJECT], [PROJECTPATH] and [ENGINEPATH] tags to make paths. " ) ,
TEXT ( " code " ) ,
FSlateHyperlinkRun : : FOnClick : : CreateStatic ( & OnCodeLinkClicked ) ,
FSlateHyperlinkRun : : FOnGetTooltipText : : CreateStatic ( & OnGetCodeTooltipText ) ) ) ) ;
HyperlinkDescs . Add ( MakeShareable ( new FHyperlinkTypeDesc (
EHyperlinkType : : Tutorial ,
LOCTEXT ( " TutorialLinkTypeLabel " , " Tutorial " ) ,
LOCTEXT ( " TutorialLinkTypeTooltip " , " A type of asset link that opens another tutorial, e.g. /Game/Tutorials/StaticMeshTutorial.StaticMeshTutorial " ) ,
TEXT ( " tutorial " ) ,
FSlateHyperlinkRun : : FOnClick : : CreateStatic ( & OnTutorialLinkClicked ) ,
FSlateHyperlinkRun : : FOnGetTooltipText : : CreateStatic ( & OnGetTutorialTooltipText ) ) ) ) ;
}
}
const TArray < TSharedPtr < FHyperlinkTypeDesc > > & FTutorialText : : GetHyperLinkDescs ( )
{
Initialize ( ) ;
return HyperlinkDescs ;
}
# undef LOCTEXT_NAMESPACE