// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "TaskBrowserPrivatePCH.h" #include "STaskBrowser.h" #include "STaskColumn.h" #include "STaskComplete.h" #include "STaskSettings.h" #include "../Public/TaskDatabase.h" #include "../Public/TaskDataManager.h" #if PLATFORM_WINDOWS #include "AllowWindowsPlatformTypes.h" #include #include #include #include "HideWindowsPlatformTypes.h" // Link with the Wintrust.lib file. #pragma comment( lib, "Crypt32.lib" ) #pragma comment( lib, "Iphlpapi.lib" ) #endif #if PLATFORM_MAC #include #include #include #include #include #include #endif #define LOCTEXT_NAMESPACE "STaskBrowser" DEFINE_LOG_CATEGORY_STATIC( LogTaskBrowser, Log, All ); /** * Reads the mac address for the computer * * @param MacAddr the buffer that receives the mac address * @param MacAddrLen (in) the size of the dest buffer, (out) the size of the data that was written * * @return true if the address was read, false if it failed to get the address */ static bool GetMacAddress(uint8* MacAddr,uint32& MacAddrLen) { TArray MacAddrArray = FPlatformMisc::GetMacAddress(); if (MacAddrLen < (uint32)MacAddrArray.Num() || MacAddrArray.Num() == 0) { return false; } MacAddrLen = MacAddrArray.Num(); FMemory::Memcpy(MacAddr, MacAddrArray.GetData(), MacAddrLen); return true; } /** * Encrypts a buffer using the crypto API * * @param SrcBuffer the source data being encrypted * @param SrcLen the size of the buffer in bytes * @param DestBuffer (out) chunk of memory that is written to * @param DestLen (in) the size of the dest buffer, (out) the size of the encrypted data * * @return true if the encryption worked, false otherwise */ static bool EncryptBuffer(const uint8* SrcBuffer,const uint32 SrcLen,uint8* DestBuffer,uint32& DestLen) { bool bEncryptedOk = false; #if PLATFORM_MAC unsigned long long MacAddress = 0; uint32 AddressSize = sizeof(unsigned long long); GetMacAddress((uint8*)&MacAddress,AddressSize); unsigned long long Entropy = 5148284414757334885ull; Entropy ^= MacAddress; uint8 Key[kCCKeySizeAES128]; check(kCCKeySizeAES128 == 2*sizeof(unsigned long long)); FMemory::Memcpy(Key,&Entropy,sizeof(Entropy)); FMemory::Memcpy(Key+sizeof(Entropy),&Entropy,sizeof(Entropy)); size_t OutBufferSize = SrcLen + kCCBlockSizeAES128; uint8* OutBuffer = (uint8*)FMemory::Malloc(OutBufferSize); FMemory::Memset(OutBuffer,0,OutBufferSize); size_t BytesEncrypted = 0; CCCryptorStatus Status = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, Key, kCCKeySizeAES128, NULL, SrcBuffer, SrcLen, OutBuffer, OutBufferSize, &BytesEncrypted); if (Status == kCCSuccess) { DestLen = BytesEncrypted; FMemory::Memcpy(DestBuffer,OutBuffer,DestLen); bEncryptedOk = true; } else { UE_LOG(LogTaskBrowser, Log, TEXT("CCCrypt failed w/ 0x%08x"), Status); } FMemory::Free(OutBuffer); #elif PLATFORM_WINDOWS DATA_BLOB SourceBlob, EntropyBlob, FinalBlob; // Set up the datablob to encrypt SourceBlob.cbData = SrcLen; SourceBlob.pbData = (uint8*)SrcBuffer; // Get the mac address for mixing into the entropy (ties the encryption to a location) ULONGLONG MacAddress = 0; uint32 AddressSize = sizeof(ULONGLONG); GetMacAddress((uint8*)&MacAddress,AddressSize); // Set up the entropy blob (changing this breaks all previous encrypted buffers!) ULONGLONG Entropy = 5148284414757334885ui64; Entropy ^= MacAddress; EntropyBlob.cbData = sizeof(ULONGLONG); EntropyBlob.pbData = (uint8*)&Entropy; // Zero the output data FMemory::Memzero(&FinalBlob,sizeof(DATA_BLOB)); // Now encrypt the data if (CryptProtectData(&SourceBlob, NULL, &EntropyBlob, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &FinalBlob)) { if (FinalBlob.cbData <= DestLen) { // Copy the final results DestLen = FinalBlob.cbData; FMemory::Memcpy(DestBuffer,FinalBlob.pbData,DestLen); bEncryptedOk = true; } // Free the encryption buffer LocalFree(FinalBlob.pbData); } else { uint32 Error = GetLastError(); UE_LOG(LogTaskBrowser, Log, TEXT("CryptProtectData failed w/ 0x%08x"), Error); } #else unimplemented(); #endif return bEncryptedOk; } /** * Decrypts a buffer using the crypto API * * @param SrcBuffer the source data being decrypted * @param SrcLen the size of the buffer in bytes * @param DestBuffer (out) chunk of memory that is written to * @param DestLen (in) the size of the dest buffer, (out) the size of the encrypted data * * @return true if the decryption worked, false otherwise */ static bool DecryptBuffer(const uint8* SrcBuffer,const uint32 SrcLen,uint8* DestBuffer,uint32& DestLen) { bool bDecryptedOk = false; #if PLATFORM_MAC unsigned long long MacAddress = 0; uint32 AddressSize = sizeof(unsigned long long); GetMacAddress((uint8*)&MacAddress,AddressSize); unsigned long long Entropy = 5148284414757334885ull; Entropy ^= MacAddress; uint8 Key[kCCKeySizeAES128]; check(kCCKeySizeAES128 == 2*sizeof(unsigned long long)); FMemory::Memcpy(Key,&Entropy,sizeof(Entropy)); FMemory::Memcpy(Key+sizeof(Entropy),&Entropy,sizeof(Entropy)); size_t OutBufferSize = SrcLen + kCCBlockSizeAES128; uint8* OutBuffer = (uint8*)FMemory::Malloc(OutBufferSize); FMemory::Memset(OutBuffer,0,OutBufferSize); size_t BytesDecrypted = 0; CCCryptorStatus Status = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, Key, kCCKeySizeAES128, NULL, SrcBuffer, SrcLen, OutBuffer, OutBufferSize, &BytesDecrypted); if (Status == kCCSuccess) { DestLen = BytesDecrypted; FMemory::Memcpy(DestBuffer,OutBuffer,DestLen); bDecryptedOk = true; } else { UE_LOG(LogTaskBrowser, Log, TEXT("CCCrypt failed w/ 0x%08x"), Status); } FMemory::Free(OutBuffer); #elif PLATFORM_WINDOWS DATA_BLOB SourceBlob, EntropyBlob, FinalBlob; // Set up the datablob to encrypt SourceBlob.cbData = SrcLen; SourceBlob.pbData = (uint8*)SrcBuffer; // Get the mac address for mixing into the entropy (ties the encryption to a location) ULONGLONG MacAddress = 0; uint32 AddressSize = sizeof(ULONGLONG); GetMacAddress((uint8*)&MacAddress,AddressSize); // Set up the entropy blob ULONGLONG Entropy = 5148284414757334885ui64; Entropy ^= MacAddress; EntropyBlob.cbData = sizeof(ULONGLONG); EntropyBlob.pbData = (uint8*)&Entropy; // Zero the output data FMemory::Memzero(&FinalBlob,sizeof(DATA_BLOB)); // Now encrypt the data if (CryptUnprotectData(&SourceBlob, NULL, &EntropyBlob, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &FinalBlob)) { if (FinalBlob.cbData <= DestLen) { // Copy the final results DestLen = FinalBlob.cbData; FMemory::Memcpy(DestBuffer,FinalBlob.pbData,DestLen); bDecryptedOk = true; } // Free the decryption buffer LocalFree(FinalBlob.pbData); } #else unimplemented(); #endif return bDecryptedOk; } ////////////////////////////////////////////////////////////////////////// // FTaskBrowserSettings FTaskBrowserSettings::FTaskBrowserSettings() : ServerName(), ServerPort( 80 ), UserName(), Password(), ProjectName(), bAutoConnectAtStartup( false ), bUseSingleSignOn( true ), DBFilterName(), bFilterOnlyOpen( true ), bFilterAssignedToMe( false ), bFilterCreatedByMe( false ), bFilterCurrentMap( false ), TaskListSortColumn( static_cast< int32 >( EField::Priority ) ), bTaskListSortAscending( true ) { // Load default settings for Epic task database server here // NOTE: Licensees, you may want to replace the following to suit your own needs GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "EpicDefaultServerName" ), ServerName, GEditorIni ); GConfig->GetInt( TEXT( "TaskBrowser" ), TEXT( "EpicDefaultServerPort" ), ServerPort, GEditorIni ); GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "EpicDefaultProjectName" ), ProjectName, GEditorIni ); GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "EpicDefaultDBFilterName" ), DBFilterName, GEditorIni ); } /** Loads settings from the configuration file */ void FTaskBrowserSettings::LoadSettings() { GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "ServerName" ), ServerName, GEditorUserSettingsIni ); GConfig->GetInt( TEXT( "TaskBrowser" ), TEXT( "ServerPort" ), ServerPort, GEditorUserSettingsIni ); GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "UserName" ), UserName, GEditorUserSettingsIni ); // Load encrypted password from disk FString EncryptedPasswordBlob = GConfig->GetStr( TEXT( "TaskBrowser" ), TEXT( "Password" ), GEditorUserSettingsIni ); Password = TEXT( "" ); const uint32 MaxEncryptedPasswordSize = 2048; uint8 EncryptedPasswordBuffer[ MaxEncryptedPasswordSize ]; if( FString::ToBlob( EncryptedPasswordBlob, EncryptedPasswordBuffer, MaxEncryptedPasswordSize ) ) { const uint32 MaxDecryptedPasswordSize = 2048; uint8 DecryptedPasswordBuffer[ MaxDecryptedPasswordSize ]; const uint32 ExpectedEncryptedPasswordSize = EncryptedPasswordBlob.Len() / 3; uint32 DecryptedPasswordSize = MaxDecryptedPasswordSize; if( DecryptBuffer( EncryptedPasswordBuffer, ExpectedEncryptedPasswordSize, DecryptedPasswordBuffer, DecryptedPasswordSize ) ) { FString DecryptedPassword = ( const TCHAR* )DecryptedPasswordBuffer; // Store password Password = DecryptedPassword; } } GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "ProjectName" ), ProjectName, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "AutoConnectAtStartup" ), bAutoConnectAtStartup, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "UseSingleSignOn" ), bUseSingleSignOn, GEditorUserSettingsIni ); GConfig->GetString( TEXT( "TaskBrowser" ), TEXT( "DBFilterName" ), DBFilterName, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "FilterOnlyOpen" ), bFilterOnlyOpen, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "FilterAssignedToMe" ), bFilterAssignedToMe, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "FilterCreatedByMe" ), bFilterCreatedByMe, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "FilterCurrentMap" ), bFilterCurrentMap, GEditorUserSettingsIni ); GConfig->GetInt( TEXT( "TaskBrowser" ), TEXT( "TaskListSortColumn" ), TaskListSortColumn, GEditorUserSettingsIni ); GConfig->GetBool( TEXT( "TaskBrowser" ), TEXT( "TaskListSortAscending" ), bTaskListSortAscending, GEditorUserSettingsIni ); } /** Saves settings to the configuration file */ void FTaskBrowserSettings::SaveSettings() { GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "ServerName" ), *ServerName, GEditorUserSettingsIni ); GConfig->SetInt( TEXT( "TaskBrowser" ), TEXT( "ServerPort" ), ServerPort, GEditorUserSettingsIni ); GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "UserName" ), *UserName, GEditorUserSettingsIni ); bool bHaveValidPassword = false; if( Password.Len() > 0 ) { // Store the user's password encrypted on disk const uint32 MaxEncryptedPasswordSize = 2048; uint8 EncryptedPasswordBuffer[ MaxEncryptedPasswordSize ]; uint32 EncryptedPasswordSize = MaxEncryptedPasswordSize; if( EncryptBuffer( ( const uint8* )&Password[ 0 ], ( Password.Len() + 1 ) * sizeof( TCHAR ), EncryptedPasswordBuffer, EncryptedPasswordSize ) ) { FString EncryptedPasswordBlob = FString::FromBlob( EncryptedPasswordBuffer, EncryptedPasswordSize ); GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "Password" ), *EncryptedPasswordBlob, GEditorUserSettingsIni ); bHaveValidPassword = true; } } if( !bHaveValidPassword ) { // Empty password GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "Password" ), TEXT( "" ), GEditorUserSettingsIni ); } GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "ProjectName" ), *ProjectName, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "AutoConnectAtStartup" ), bAutoConnectAtStartup, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "UseSingleSignOn" ), bUseSingleSignOn, GEditorUserSettingsIni ); GConfig->SetString( TEXT( "TaskBrowser" ), TEXT( "DBFilterName" ), *DBFilterName, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "FilterOnlyOpen" ), bFilterOnlyOpen, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "FilterAssignedToMe" ), bFilterAssignedToMe, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "FilterCreatedByMe" ), bFilterCreatedByMe, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "FilterCurrentMap" ), bFilterCurrentMap, GEditorUserSettingsIni ); GConfig->SetInt( TEXT( "TaskBrowser" ), TEXT( "TaskListSortColumn" ), TaskListSortColumn, GEditorUserSettingsIni ); GConfig->SetBool( TEXT( "TaskBrowser" ), TEXT( "TaskListSortAscending" ), bTaskListSortAscending, GEditorUserSettingsIni ); } /** Returns true if the current connection settings are valid */ bool FTaskBrowserSettings::AreConnectionSettingsValid() const { if( ServerName.Len() == 0 || ServerPort == 0 || UserName.Len() == 0 || Password.Len() == 0 || ProjectName.Len() == 0 ) { return false; } return true; } ////////////////////////////////////////////////////////////////////////// // FTaskOverview TMap< EField, FName > FTaskOverview::FieldNames; FTaskOverview::FTaskOverview( const FTaskDatabaseEntry& InEntry ) : FTaskDatabaseEntry() { Number = InEntry.Number; Priority = InEntry.Priority; Summary = InEntry.Summary; Status = InEntry.Status; CreatedBy = InEntry.CreatedBy; AssignedTo = InEntry.AssignedTo; } FString FTaskOverview::GetFieldEntry( const FName InName ) const { // Retrieve the key from the name specified PopulateFieldNames(); const EField* FieldPtr = FieldNames.FindKey( InName ); check( FieldPtr ); // Convert the field to it's equivalent property check( *FieldPtr > EField::Invalid ); check( *FieldPtr < EField::NumColumnIDs ); switch( *FieldPtr ) { case EField::Number: return FString::FromInt( Number ); case EField::Priority: return Priority; case EField::Summary: return Summary; case EField::Status: return Status; case EField::CreatedBy: return CreatedBy; case EField::AssignedTo: return AssignedTo; } return TEXT(""); } FName FTaskOverview::GetFieldName( const EField InField ) { // Retrieve the value from the field specified PopulateFieldNames(); const FName* NamePtr = FieldNames.Find( InField ); check( NamePtr ); return *NamePtr; } void FTaskOverview::PopulateFieldNames() { // Fill our map if it's empty if ( FieldNames.Num() == 0 ) { FieldNames.Add( EField::Number, FName(TEXT("Number")) ); FieldNames.Add( EField::Priority, FName(TEXT("Priority")) ); FieldNames.Add( EField::Summary, FName(TEXT("Summary")) ); FieldNames.Add( EField::Status, FName(TEXT("Status")) ); FieldNames.Add( EField::CreatedBy, FName(TEXT("CreatedBy")) ); FieldNames.Add( EField::AssignedTo, FName(TEXT("AssignedTo")) ); } } ////////////////////////////////////////////////////////////////////////// // STaskOverview class STaskTreeWidgetItem : public SMultiColumnTableRow< TSharedPtr > { public: SLATE_BEGIN_ARGS(STaskTreeWidgetItem) : _WidgetInfoToVisualize() {} SLATE_ARGUMENT( TSharedPtr, WidgetInfoToVisualize ) SLATE_END_ARGS() /** * Construct child widgets that comprise this widget. * * @param InArgs Declaration from which to construct this widget */ void Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView ) { this->WidgetInfo = InArgs._WidgetInfoToVisualize; SMultiColumnTableRow< TSharedPtr >::Construct( FSuperRowType::FArguments(), InOwnerTableView ); } /** @return Widget based on the column name */ virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override { const FString Entry = WidgetInfo.Get()->GetFieldEntry( ColumnName ); return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(2) [ SNew( STextBlock ) .Text( Entry ) ]; } protected: /** The info about the widget that we are visualizing */ TAttribute< TSharedPtr > WidgetInfo; }; ////////////////////////////////////////////////////////////////////////// // STaskBrowser /** * Construct the widget * * @param InArgs A declaration from which to construct the widget */ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void STaskBrowser::Construct(const FArguments& InArgs) { // Create task data manager instance TaskDataManager = MakeShareable( new FTaskDataManager( this ) ); // Load preferences FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); // Propagate server connection settings to the task data manager FTaskDataManagerConnectionSettings ConnectionSettings; { ConnectionSettings.ServerName = TBSettings.ServerName; ConnectionSettings.ServerPort = TBSettings.ServerPort; ConnectionSettings.UserName = TBSettings.UserName; ConnectionSettings.Password = TBSettings.Password; ConnectionSettings.ProjectName = TBSettings.ProjectName; } TaskDataManager->SetConnectionSettings( ConnectionSettings ); // Set column sorting rules EField eField = static_cast< EField >( TBSettings.TaskListSortColumn ); TaskListSortColumn = ( ( eField > EField::Invalid && eField < EField::NumColumnIDs ) ? eField : EField::Priority ); TaskListSortAscending = TBSettings.bTaskListSortAscending; TaskListNumSelectedAndOpen = -1; // Automatically connect to the server if we were asked to do that if( TBSettings.bAutoConnectAtStartup && TBSettings.AreConnectionSettingsValid() ) { TaskDataManager->AttemptConnection(); } // Standard paddings const FMargin BorderPadding( 6, 6 ); const FMargin ButtonPadding( 8, 4 ); const FMargin SmallPadding( 0, 0, 12, 0 ); const FMargin XLargePadding( 0, 0, 48, 0 ); this->ChildSlot [ SNew(SVerticalBox) +SVerticalBox::Slot() .Padding( BorderPadding ) .FillHeight(0.45f) [ SNew(SBorder) .BorderImage( FEditorStyle::GetBrush( "ToolPanel.GroupBorder" ) ) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding( BorderPadding ) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SAssignNew(MarkComplete, SButton) .ContentPadding( ButtonPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .IsEnabled_Raw( this, &STaskBrowser::GetMarkCompleteEnabled ) .OnClicked( this, &STaskBrowser::OnMarkCompleteClicked ) [ SNew(STextBlock) .Text( LOCTEXT("MarkComplete", "Mark Complete...") ) ] ] +SHorizontalBox::Slot() .AutoWidth() .Padding( XLargePadding ) [ SAssignNew(RefreshView, SButton) .ContentPadding( ButtonPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .OnClicked( this, &STaskBrowser::OnRefreshViewClicked ) [ SNew(STextBlock) .Text( LOCTEXT("RefreshView", "Refresh View") ) ] ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text( LOCTEXT("DatabaseFilter", "Database filter") ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SAssignNew(DatabaseFilter, STextComboBox) .OptionsSource( &DatabaseOptions ) .OnSelectionChanged( this, &STaskBrowser::OnDatabaseFilterSelect ) ] ] +SVerticalBox::Slot() .AutoHeight() .Padding( BorderPadding ) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SNew(STextBlock) .Text( LOCTEXT("Display", "Display") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(OpenOnly, SCheckBox) .OnCheckStateChanged( this, &STaskBrowser::OnOpenOnlyChanged ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SNew(STextBlock) .Text( LOCTEXT("OpenOnly", "Open only") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(AssignedToMe, SCheckBox) .OnCheckStateChanged( this, &STaskBrowser::OnAssignedToMeChanged ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SNew(STextBlock) .Text( LOCTEXT("AssignedToMe", "Assigned to me") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(CreatedByMe, SCheckBox) .OnCheckStateChanged( this, &STaskBrowser::OnCreatedByMeChanged ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SNew(STextBlock) .Text( LOCTEXT("CreatedByMe", "Created by me") ) ] +SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(CurrentMap, SCheckBox) .OnCheckStateChanged( this, &STaskBrowser::OnCurrentMapChanged ) ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SNew(STextBlock) .Text( LOCTEXT("CurrentMap", "Current map") ) ] ] +SVerticalBox::Slot() .Padding( BorderPadding ) .FillHeight( 1.0f ) [ SNew(SBox) .Padding( BorderPadding ) [ SAssignNew(TaskList, SListView< TSharedPtr >) // .SelectionMode(ESelectionMode::Single) .ItemHeight(24) .ListItemsSource( &TaskEntries ) .OnGenerateRow( this, &STaskBrowser::OnTaskGenerateRow ) .OnSelectionChanged( this, &STaskBrowser::OnTaskSelectionChanged ) .OnMouseButtonDoubleClick( this, &STaskBrowser::OnTaskDoubleClicked ) .HeaderRow ( SAssignNew(TaskHeaders, SHeaderRow) // @TODO as a workaround to columns not registering mouse clicks, use buttons instead + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::Number )) .FillWidth( 90.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::Number ) ] + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::Priority )) .FillWidth( 150.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::Priority ) ] + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::Summary )) .FillWidth( 360.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::Summary ) ] + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::AssignedTo )) .FillWidth( 150.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::AssignedTo ) ] + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::Status )) .FillWidth( 200.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::Status ) ] + SHeaderRow::Column(FTaskOverview::GetFieldName( EField::CreatedBy )) .FillWidth( 150.0f ) [ SNew(STaskColumn) .TaskBrowser( SharedThis( this ) ) .Field( EField::CreatedBy ) ] ) ] ] ] ] +SVerticalBox::Slot() .Padding( BorderPadding ) .FillHeight(0.55f) [ SNew(SBorder) .BorderImage( FEditorStyle::GetBrush( "ToolPanel.GroupBorder" ) ) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding( BorderPadding ) [ SAssignNew(Summary, SEditableTextBox) .IsReadOnly( true ) ] +SVerticalBox::Slot() .Padding( BorderPadding ) .FillHeight(1.0f) [ SAssignNew(Description, SEditableTextBox) .IsReadOnly( true ) ] ] ] +SVerticalBox::Slot() .AutoHeight() .Padding( BorderPadding ) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SAssignNew(Connect, SButton) .ContentPadding( ButtonPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .IsEnabled_Raw( this, &STaskBrowser::GetConnectEnabled ) .OnClicked( this, &STaskBrowser::OnConnectClicked ) [ SNew(STextBlock) .Text_Raw( this, &STaskBrowser::GetConnectText ) ] ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) [ SAssignNew(Settings, SButton) .ContentPadding( ButtonPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .OnClicked( this, &STaskBrowser::OnSettingsClicked ) [ SNew(STextBlock) .Text( LOCTEXT("Settings", "Settings...") ) ] ] +SHorizontalBox::Slot() .AutoWidth() .Padding( SmallPadding ) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text_Raw( this, &STaskBrowser::GetStatusText ) ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef STaskBrowser::GetContent() { return SharedThis( this ); } STaskBrowser::~STaskBrowser() { // Cleanup task data manager TaskDataManager.Reset(); } bool STaskBrowser::GetMarkCompleteEnabled() const { // Update the 'Fix' button bool bEnableFixButton = false; // Must be connected to the server if( TaskDatabaseSystem::IsConnected() ) { // Only enable the 'Mark as Fixed' button if at least one *OPEN* task is selected if( TaskListNumSelectedAndOpen > 0 ) { bEnableFixButton = true; } } return bEnableFixButton; } FReply STaskBrowser::OnMarkCompleteClicked() { const bool bOnlyOpenTasks = true; TArray< uint32 > TaskNumbersToFix; QuerySelectedTaskNumbers( TaskNumbersToFix, bOnlyOpenTasks ); if( TaskNumbersToFix.Num() > 0 ) { CompleteTaskDialog( TaskNumbersToFix ); } return FReply::Handled(); } FReply STaskBrowser::OnRefreshViewClicked() { // Update everything using fresh data from the server TaskDataManager->ClearTaskDataAndInitiateRefresh(); return FReply::Handled(); } void STaskBrowser::OnDatabaseFilterSelect( TSharedPtr InFilter, ESelectInfo::Type SelectInfo ) { // Update the active filter! TaskDataManager->ChangeActiveFilter( *InFilter ); // Save the new filter name in our preferences { FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.DBFilterName = *InFilter; TBSettings.SaveSettings(); } } void STaskBrowser::OnOpenOnlyChanged( const ESlateCheckBoxState::Type NewCheckedState ) { // Refresh the GUI. It will apply the updated filters. RefreshGUI( ETaskBrowserGUIRefreshOptions::RebuildTaskList ); // A filter was changed, so save updated settings to disk { FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.bFilterOnlyOpen = !!NewCheckedState; TBSettings.SaveSettings(); } } void STaskBrowser::OnAssignedToMeChanged( const ESlateCheckBoxState::Type NewCheckedState ) { // Refresh the GUI. It will apply the updated filters. RefreshGUI( ETaskBrowserGUIRefreshOptions::RebuildTaskList ); // A filter was changed, so save updated settings to disk { FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.bFilterAssignedToMe = !!NewCheckedState; TBSettings.SaveSettings(); } } void STaskBrowser::OnCreatedByMeChanged( const ESlateCheckBoxState::Type NewCheckedState ) { // Refresh the GUI. It will apply the updated filters. RefreshGUI( ETaskBrowserGUIRefreshOptions::RebuildTaskList ); // A filter was changed, so save updated settings to disk { FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.bFilterCreatedByMe = !!NewCheckedState; TBSettings.SaveSettings(); } } void STaskBrowser::OnCurrentMapChanged( const ESlateCheckBoxState::Type NewCheckedState ) { // Refresh the GUI. It will apply the updated filters. RefreshGUI( ETaskBrowserGUIRefreshOptions::RebuildTaskList ); // A filter was changed, so save updated settings to disk { FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.bFilterCurrentMap = !!NewCheckedState; TBSettings.SaveSettings(); } } TSharedRef STaskBrowser::OnTaskGenerateRow( TSharedPtr InTaskOverview, const TSharedRef& OwnerTable ) { return SNew(STaskTreeWidgetItem, OwnerTable) .WidgetInfoToVisualize(InTaskOverview); } void STaskBrowser::OnTaskSelectionChanged( TSharedPtr InTaskOverview, ESelectInfo::Type /*SelectInfo*/ ) { // Make sure the task data manager is keep track of this task if ( InTaskOverview.IsValid() ) { TaskDataManager->SetFocusedTaskNumber( InTaskOverview->Number ); } else { TaskDataManager->SetFocusedTaskNumber( INDEX_NONE ); } // Refresh the cached values for our Mark Complete button TaskListNumSelectedAndOpen = GetNumSelectedTasks( true ); RefreshGUI( ETaskBrowserGUIRefreshOptions::UpdateTaskDescription ); } int32 STaskBrowser::GetNumSelectedTasks( const bool bOnlyOpenTasks ) const { // Just retrieve how maybe we have of that type TArray< uint32 > SelectedTaskNumbers; QuerySelectedTaskNumbers( SelectedTaskNumbers, bOnlyOpenTasks ); const int32 iSelectedTaskNumbers = SelectedTaskNumbers.Num(); return iSelectedTaskNumbers; } void STaskBrowser::OnTaskDoubleClicked( TSharedPtr InTaskOverview ) { // Check to see if the bug status is "Open" if( InTaskOverview.IsValid() && InTaskOverview->Status.StartsWith( TaskDataManager->GetOpenTaskStatusPrefix() ) ) { // Add this task's number to our list of tasks to fix TArray< uint32 > TaskNumbersToFix; TaskNumbersToFix.Add( InTaskOverview->Number ); CompleteTaskDialog( TaskNumbersToFix ); } } FReply STaskBrowser::OnTaskColumnClicked( const EField InField ) { check( InField > EField::Invalid ); check( InField < EField::NumColumnIDs ) if( InField == TaskListSortColumn ) { // Clicking on the same column will flip the sort order TaskListSortAscending = !TaskListSortAscending; } else { // Clicking on a new column will set that column as current and reset the sort order. TaskListSortColumn = InField; TaskListSortAscending = true; } // Save the sorting configuration FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); TBSettings.TaskListSortColumn = static_cast< int32 >( TaskListSortColumn ); TBSettings.bTaskListSortAscending = TaskListSortAscending; TBSettings.SaveSettings(); // Refresh the GUI RefreshGUI( ETaskBrowserGUIRefreshOptions::SortTaskList ); return FReply::Handled(); } bool STaskBrowser::GetConnectEnabled() const { switch( TaskDataManager->GetConnectionStatus() ) { case ETaskDataManagerStatus::FailedToInit: case ETaskDataManagerStatus::Connecting: case ETaskDataManagerStatus::Disconnecting: return false; default: return true; } } FReply STaskBrowser::OnConnectClicked() { if( TaskDataManager->GetConnectionStatus() != ETaskDataManagerStatus::FailedToInit ) { if( TaskDataManager->GetConnectionStatus() == ETaskDataManagerStatus::ReadyToConnect || TaskDataManager->GetConnectionStatus() == ETaskDataManagerStatus::ConnectionFailed ) { // Did the user cancel the config dialog? bool bUserCancelled = false; // Load server settings from disk FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); // Make sure that everything is valid, otherwise pop up a settings dialog if( !TBSettings.AreConnectionSettingsValid() ) { // Give the user a chance to fix the settings SettingsDialog(); } if( TBSettings.AreConnectionSettingsValid() ) { // Connection failed, so allow the user to try again TaskDataManager->AttemptConnection(); } else { // Only warn the user if the user did not press cancel if( !bUserCancelled ) { // Warn the user that we'll need valid settings in order to connect to the server FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "TaskBrowser_Error_NeedValidConnectionSettings", "In order for the Task Browser to connect to the database server, you'll need to supply valid settings for the server name/port, user name and password, as well as the project (database) name to connect to. If you're not sure what settings to use then please ask a system administrator, or check the similar settings in desktop task management application's configuration." ) ); } } } else { // Queue a disconnect TaskDataManager->AttemptDisconnection(); } } return FReply::Handled(); } FText STaskBrowser::GetConnectText() const { switch( TaskDataManager->GetConnectionStatus() ) { case ETaskDataManagerStatus::FailedToInit: case ETaskDataManagerStatus::ReadyToConnect: case ETaskDataManagerStatus::ConnectionFailed: case ETaskDataManagerStatus::Disconnecting: return NSLOCTEXT("UnrealEd", "TaskBrowser_ConnectButtonLabel", "Connect" ); default: return NSLOCTEXT("UnrealEd", "TaskBrowser_DisconnectButtonLabel", "Disconnect" ); } } FReply STaskBrowser::OnSettingsClicked() { SettingsDialog(); return FReply::Handled(); } FText STaskBrowser::GetStatusText() const { switch( TaskDataManager->GetGUIStatus() ) { case ETaskDataManagerStatus::ReadyToConnect: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_ReadyToConnect", "Not connected. Click the Connect button to login to the task database." ); case ETaskDataManagerStatus::Connecting: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_Connecting", "Connecting to server and logging in..." ); case ETaskDataManagerStatus::Connected: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_Connected", "Connected to the task database." ); case ETaskDataManagerStatus::ConnectionFailed: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_ConnectionFailed", "Failed to connect to the task database. Click the Connect button to try again." ); case ETaskDataManagerStatus::Disconnecting: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_Disconnecting", "Disconnecting..." ); case ETaskDataManagerStatus::QueryingFilters: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_QueryingFilters", "Retrieving filters..." ); case ETaskDataManagerStatus::QueryingTasks: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_QueryingTasks", "Retrieving task list from server..." ); case ETaskDataManagerStatus::QueryingTaskDetails: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_QueryingTaskDetails", "Retrieving task information..." ); case ETaskDataManagerStatus::MarkingTaskComplete: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_MarkingTaskComplete", "Marking task as fixed..." ); default: return NSLOCTEXT("UnrealEd", "TaskBrowser_ServerStatus_FailedToInit", "Failed to initialize. No task database providers are available." ); } } void STaskBrowser::CompleteTaskDialog( TArray< uint32 >& TaskNumbersToFix ) { // Create a new modal settings dialog const TArray< FString > &ResolutionValues = TaskDataManager->GetResolutionValues(); FTaskResolutionData ResolutionData; TSharedRef WidgetWindow = SNew(SWindow) .Title( LOCTEXT("TaskBrowserComplete", "Mark Task Complete") ) .ClientSize( FVector2D(516, 273) ) .SizingRule( ESizingRule::FixedSize ) .SupportsMinimize(false) .SupportsMaximize(false); WidgetWindow->SetContent ( SNew(STaskComplete) .WidgetWindow(WidgetWindow) .ResolutionData( &ResolutionData ) .ResolutionValues( &ResolutionValues ) ); GEditor->EditorAddModalWindow(WidgetWindow); if( ResolutionData.IsValid() ) { // Queue these up to be marked as fixed! TaskDataManager->StartMarkingTasksComplete( TaskNumbersToFix, ResolutionData ); } } /** Spawn our settings modal dialog */ void STaskBrowser::SettingsDialog( void ) { // Create a new modal settings dialog TSharedRef WidgetWindow = SNew(SWindow) .Title( LOCTEXT("TaskBrowserSettings", "Task Browser Settings") ) .ClientSize( FVector2D(376, 327) ) .SizingRule( ESizingRule::FixedSize ) .SupportsMinimize(false) .SupportsMaximize(false); WidgetWindow->SetContent ( SNew(STaskSettings) .WidgetWindow(WidgetWindow) ); GEditor->EditorAddModalWindow(WidgetWindow); // Update the task data manager with any settings that may have changed FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); FTaskDataManagerConnectionSettings NewConnectionSettings; { NewConnectionSettings.ServerName = TBSettings.ServerName; NewConnectionSettings.ServerPort = TBSettings.ServerPort; NewConnectionSettings.UserName = TBSettings.UserName; NewConnectionSettings.Password = TBSettings.Password; NewConnectionSettings.ProjectName = TBSettings.ProjectName; } TaskDataManager->SetConnectionSettings( NewConnectionSettings ); } void STaskBrowser::QuerySelectedTaskNumbers( TArray< uint32 >& OutSelectedTaskNumbers, const bool bOnlyOpenTasks ) const { OutSelectedTaskNumbers.Reset(); // Create a list of task numbers that are currently selected in the task list TArray< TSharedPtr > SelectedTasks = TaskList->GetSelectedItems(); for ( int32 iSelectedTask = 0; iSelectedTask < SelectedTasks.Num(); iSelectedTask++ ) { TSharedPtr SelectedTask = SelectedTasks[ iSelectedTask ]; // Make sure the task isn't already completed if( !bOnlyOpenTasks || SelectedTask->Status.StartsWith( TaskDataManager->GetOpenTaskStatusPrefix() ) ) { // Add this task's number to our list of tasks OutSelectedTaskNumbers.Add( SelectedTask->Number ); } } } bool STaskBrowser::TaskListItemSort( const TSharedPtr TaskEntryA, const TSharedPtr TaskEntryB ) const { int SortResult = 0; check( TaskListSortColumn > EField::Invalid ); check( TaskListSortColumn < EField::NumColumnIDs ); switch( TaskListSortColumn ) { case EField::Number: if( TaskEntryA->Number != TaskEntryB->Number ) { SortResult = TaskEntryA->Number > TaskEntryB->Number ? 1 : -1; } break; case EField::Priority: SortResult = FCString::Stricmp( *TaskEntryA->Priority, *TaskEntryB->Priority ); break; case EField::Summary: SortResult = FCString::Stricmp( *TaskEntryA->Summary, *TaskEntryB->Summary ); break; case EField::AssignedTo: SortResult = FCString::Stricmp( *TaskEntryA->AssignedTo, *TaskEntryB->AssignedTo ); break; case EField::Status: SortResult = FCString::Stricmp( *TaskEntryA->Status, *TaskEntryB->Status ); break; case EField::CreatedBy: SortResult = FCString::Stricmp( *TaskEntryA->CreatedBy, *TaskEntryB->CreatedBy ); break; } // If the items had the same value, then fallback to secondary sort criteria // @todo: Support custom secondary sorts (stack of sorted columns and ascend/descend state) if( SortResult == 0 ) { if( TaskListSortColumn == EField::Number ) { // Secondary sort by priority SortResult = TaskEntryA->Number > TaskEntryB->Number ? 1 : -1; } else { // Secondary sort by name SortResult = FCString::Stricmp( *TaskEntryA->Summary, *TaskEntryB->Summary ); } } // Reverse the sort order if we were asked to do that if( !TaskListSortAscending ) { SortResult = -SortResult; } return ( SortResult > 0 ? true : false ); } void STaskBrowser::TaskListItemSort() { // Early out if there's no elements in the list if ( TaskEntries.Num() == 0 ) { return; } bool bChanged = false; // Did the list change at all? bool bSwapped = true; // Has the list changed this round?, keep sorting til it doesn't while( bSwapped ) { bSwapped = false; for ( int32 CurTaskIndex = 0; CurTaskIndex < TaskEntries.Num() - 1; ++CurTaskIndex ) { // Check to see if the elements need swapping TSharedPtr TaskEntryA = TaskEntries[ CurTaskIndex ]; TSharedPtr TaskEntryB = TaskEntries[ CurTaskIndex+1 ]; const bool bExchange = TaskListItemSort( TaskEntryA, TaskEntryB ); // Do we need to swap the two elements around? if ( bExchange ) { bChanged = true; bSwapped = true; TaskEntries[ CurTaskIndex ] = TaskEntryB; TaskEntries[ CurTaskIndex+1 ] = TaskEntryA; //FMemory::Memswap( TaskEntryA.Get(), TaskEntryB.Get(), sizeof( FTaskOverview ) ); } } } if ( bChanged ) { TaskList->RequestListRefresh(); } } void STaskBrowser::RefreshGUI( const ETaskBrowserGUIRefreshOptions::Type Options ) { if( Options & ETaskBrowserGUIRefreshOptions::RebuildFilterList ) { // Clear the contents of the list DatabaseOptions.Empty(); // Copy all of the filters over to our own cached array TArray< FString > FilterNames = TaskDataManager->GetCachedFilterNames(); for ( int32 iFilter = 0; iFilter < FilterNames.Num(); ++iFilter ) { const FString& CurFilterName = FilterNames[ iFilter ]; // Add this filter to our list DatabaseOptions.Add( MakeShareable( new FString( CurFilterName ) ) ); } // Do we have an active filter already? int32 SelectedFilterIndex = INDEX_NONE; if( TaskDataManager->GetActiveFilterName().Len() > 0 ) { verify( FilterNames.Find( TaskDataManager->GetActiveFilterName(), SelectedFilterIndex ) ); } else { // No active filter yet, so we'll set one up now FTaskBrowserSettings TBSettings; TBSettings.LoadSettings(); // Search for the default filter name if( !FilterNames.Find( TBSettings.DBFilterName, SelectedFilterIndex ) ) { // Not found... hrm.. if( FilterNames.Num() > 0 ) { // Just use the first available filter SelectedFilterIndex = 0; } } if( SelectedFilterIndex != INDEX_NONE ) { // Select the filter in the list DatabaseFilter->SetSelectedItem(DatabaseOptions[SelectedFilterIndex]); // Update task data manager TaskDataManager->ChangeActiveFilter( FilterNames[ SelectedFilterIndex ] ); } } } if( Options & ETaskBrowserGUIRefreshOptions::RebuildTaskList ) { // Grab a list of task numbers we already had selected TSet< int32 > SelectedTaskNumberSet; { const bool bOnlyOpenTasks = false; TArray< uint32 > SelectedTaskNumbers; QuerySelectedTaskNumbers( SelectedTaskNumbers, bOnlyOpenTasks ); // Add everything to a set so that we can do quick tests later on for ( int32 CurTaskIndex = 0; CurTaskIndex < SelectedTaskNumbers.Num(); ++CurTaskIndex ) { SelectedTaskNumberSet.Add( SelectedTaskNumbers[ CurTaskIndex ] ); } } // Check to see which 'display filters' are enabled const bool bOnlyOpen = OpenOnly->IsChecked(); const bool bAssignedToMe = AssignedToMe->IsChecked(); const bool bCreatedByMe = CreatedByMe->IsChecked(); const bool bCurrentMap = CurrentMap->IsChecked(); // Grab the current loaded map name from the editor // @todo: Register for event to auto refresh task list when map loaded or new? FString CurrentMaNamePtr = GWorld->GetMapName(); { const int32 MaxPrefixChars = 2; const int32 MaxSuffixChars = 3; if( CurrentMaNamePtr.Len() > MaxPrefixChars + 1 ) { // Clean up any small prefixes or suffixes on the map name int32 FirstUnderscore = CurrentMaNamePtr.Find( TEXT( "_" ) ); if( FirstUnderscore != INDEX_NONE ) { const int32 NumPrefixChars = FirstUnderscore; if( NumPrefixChars <= MaxPrefixChars ) { // Chop it! CurrentMaNamePtr = CurrentMaNamePtr.Mid( FirstUnderscore + 1 ); } } } // Chop off multiple small suffixes bool bCheckForSuffix = true; while( bCheckForSuffix ) { bCheckForSuffix = false; if( CurrentMaNamePtr.Len() > MaxSuffixChars + 1 ) { // Clean up any small prefixes or suffixes on the map name int32 LastUnderscore = CurrentMaNamePtr.Find( TEXT( "_" ), ESearchCase::CaseSensitive, ESearchDir::FromEnd ); if( LastUnderscore != INDEX_NONE ) { const int32 NumSuffixChars = CurrentMaNamePtr.Len() - LastUnderscore; if( NumSuffixChars <= MaxSuffixChars ) { // Chop it! CurrentMaNamePtr = CurrentMaNamePtr.Left( LastUnderscore ); // Check for another suffix! bCheckForSuffix = true; } } } } } // Clear everything TaskEntries.Empty(); TaskList->ClearSelection(); // Grab the current user's real name FString UserRealName = TaskDataManager->GetUserRealName(); // Copy all of the task entries to our own cached array const TArray< FTaskDatabaseEntry >& Tasks = TaskDataManager->GetCachedTaskArray(); for ( int32 CurTaskIndex = 0; CurTaskIndex < Tasks.Num(); ++CurTaskIndex ) { const FTaskDatabaseEntry& CurTask = Tasks[ CurTaskIndex ]; if( ( !bOnlyOpen || CurTask.Status.StartsWith( TaskDataManager->GetOpenTaskStatusPrefix() ) ) && ( !bAssignedToMe || CurTask.AssignedTo.Contains( UserRealName) ) && ( !bCreatedByMe || CurTask.CreatedBy.Contains( UserRealName) ) && ( !bCurrentMap || CurTask.Summary.Contains( CurrentMaNamePtr) ) ) { TSharedPtr NewTask = MakeShareable( new FTaskOverview( CurTask ) ); TaskEntries.Add( NewTask ); // Try to keep the currently selected task selected if( SelectedTaskNumberSet.Contains( CurTask.Number ) ) { TaskList->SetItemSelection( NewTask, true ); } } } TaskList->RequestListRefresh(); } if( Options & ( ETaskBrowserGUIRefreshOptions::ResetColumnSizes | ETaskBrowserGUIRefreshOptions::RebuildTaskList ) ) { // Update task list column sizes TaskHeaders->ResetColumnWidths(); } if( Options & ( ETaskBrowserGUIRefreshOptions::SortTaskList | ETaskBrowserGUIRefreshOptions::RebuildTaskList ) ) { // Sort the task list TaskListItemSort(); } if( Options & ETaskBrowserGUIRefreshOptions::UpdateTaskDescription ) { FString TaskSummary; FString TaskDescription; const uint32 TaskNumber = TaskDataManager->GetFocusedTaskNumber(); bool bHaveTask = false; if( TaskNumber != INDEX_NONE ) { // Do we have any cached results to display? const FTaskDatabaseEntryDetails* TaskDetails = TaskDataManager->FindCachedTaskDetails( TaskNumber ); if( TaskDetails != NULL ) { // We have the task details! TaskSummary = TaskDetails->Summary; TaskDescription = TaskDetails->Description; // Append the task number to the name TaskSummary += FString::Printf( TEXT( " (# %i)" ), TaskNumber ); bHaveTask = true; } } if( !bHaveTask ) { // OK we don't have the task description yet, but it may be on it's way! Let's check. if( TaskDataManager->GetGUIStatus() == ETaskDataManagerStatus::QueryingTaskDetails ) { // Try to find the name of the task in our cached task entry array for ( int32 CurTaskIndex = 0; CurTaskIndex < TaskEntries.Num(); ++CurTaskIndex ) { TSharedPtr CurTask = TaskEntries[ CurTaskIndex ]; if( CurTask->Number == TaskDataManager->GetFocusedTaskNumber() ) { // Found it! TaskSummary = CurTask->Summary; // Append the task number to the name TaskSummary += FString::Printf( TEXT( " (# %i)" ), TaskNumber ); break; } } // Let the user know we're downloading the task description now TaskDescription = NSLOCTEXT("UnrealEd", "TaskBrowser_WaitingForDescription", "\n(Downloading task details from server. Please wait...)" ).ToString(); } } // Update the task name text Summary->SetText( FText::FromString(TaskSummary) ); // Update the task description Description->SetText( FText::FromString(TaskDescription) ); } } void STaskBrowser::Callback_RefreshGUI( const ETaskBrowserGUIRefreshOptions::Type Options ) { // Refresh the GUI RefreshGUI( Options ); } #undef LOCTEXT_NAMESPACE