// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/BitArray.h" #include "Containers/Set.h" #include "Containers/SparseArray.h" #include "Containers/UnrealString.h" #include "Containers/VersePath.h" #include "Delegates/Delegate.h" #include "HAL/PlatformCrt.h" #include "ISourceControlModule.h" #include "ISourceControlProvider.h" #include "ISourceControlState.h" #include "Input/Reply.h" #include "Internationalization/Text.h" #include "Layout/Visibility.h" #include "Math/Color.h" #include "Misc/Attribute.h" #include "Misc/Optional.h" #include "Misc/PackageName.h" #include "PackagesDialog.h" #include "Styling/SlateTypes.h" #include "Templates/SharedPointer.h" #include "Templates/TypeHash.h" #include "Templates/UnrealTemplate.h" #include "UObject/NameTypes.h" #include "UObject/Package.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/Views/SHeaderRow.h" #include "Widgets/Views/SListView.h" #include "Widgets/Views/STableRow.h" class ITableRow; class SCheckBox; class SHorizontalBox; class STableViewBase; class SWidget; class UObject; struct FGeometry; struct FKeyEvent; /** * Represents a button that will dynamically be added to the package dialog window */ class FPackageButton : public TSharedFromThis { public: FPackageButton(FPackagesDialogModule* InModule, EDialogReturnType InType, EDialogButtonStyle InStyle, const FText& InName, const FText& InToolTip, TAttribute InDisabled = false) : Module(InModule) , Name(InName) , ToolTip(InToolTip) , Type(InType) , Style(InStyle) , Clicked(false) , Disabled(InDisabled) { } /** * Gets called when the button is clicked */ FReply OnButtonClicked() { Clicked = true; Module->RemovePackagesDialog(); return FReply::Handled(); } /** * Returns if the button should be enabled * * @return True if the button is not disabled, false otherwise */ bool IsEnabled() const { return !Disabled.Get(); } /** * Gets the name of the button * * @return the name of the button */ FText GetName() const { return Name; } /** * Gets the tooltip for the button * * @return the tooltip for the button */ FText GetToolTip() const { return ToolTip; } /** * Returns if the button was clicked * * @return True if the button was clicked, false otherwise */ bool IsClicked() const { return Clicked; } /** * Gets the type of the button * * @return the type of the button */ EDialogReturnType GetType() const { return Type; } /** * Gets the style of the button * * @return the style of the button */ EDialogButtonStyle GetStyle() const { return Style; } /** * Sets if the button should be disabled * * @param InDisabled New disabled value */ void SetDisabled(bool InDisabled) { if( !Disabled.IsBound() ) { Disabled.Set( InDisabled ); } } /** * Resets this button state */ void Reset() { Clicked = false; } private: FPackagesDialogModule* Module; // Stores the module that contains this button FText Name; // Name of the button FText ToolTip; // Tool tip for this button EDialogReturnType Type; // Button type EDialogButtonStyle Style; // Button style bool Clicked; // Stores if the button was clicked to close the dialog TAttribute Disabled; // Stores if the button is disabled or not }; /** * Represents package item that is displayed as a checkbox inside the package dialog */ class FPackageItem : public TSharedFromThis { public: FPackageItem(UPackage* InPackage, FString&& InAssetDisplayName, FString&& InFileName, UE::Core::FVersePath&& InVersePath, FString&& InOwnerName, ECheckBoxState InState, bool InDisabled, FString&& InIconName, FString&& InIconToolTip) : Package(InPackage) , AssetDisplayName(MoveTemp(InAssetDisplayName)) , PackageName(FPackageName::ObjectPathToPackageName(InPackage->GetName())) , FileName(MoveTemp(InFileName)) , VersePath(MoveTemp(InVersePath)) , OwnerName(MoveTemp(InOwnerName)) , State(InState) , Disabled(InDisabled) , IconName(MoveTemp(InIconName)) , IconToolTip(MoveTemp(InIconToolTip)) { /** if the item is checked and disabled make the state Undetermined */ if(State == ECheckBoxState::Checked && Disabled) { State = ECheckBoxState::Undetermined; } } /** * Gets the display state of the item * * @return the item state */ ECheckBoxState OnGetDisplayCheckState() const { RefreshButtonCallback.ExecuteIfBound(); return State; } /** * Sets the item state * * @param InNewState the new state to set the item to */ void OnDisplayCheckStateChanged(ECheckBoxState InNewState) { State = InNewState; /** if the item is checked and disabled make the state Undetermined */ if( State == ECheckBoxState::Checked && Disabled ) { State = ECheckBoxState::Undetermined; } } /** * Sets refresh callback that should be called when the item's state change * * @param InRefreshButtonCallback the new callback */ void SetRefreshCallback(FSimpleDelegate InRefreshButtonCallback) { RefreshButtonCallback = InRefreshButtonCallback; } /** * Gets the state of the checkbox item * * @return the state of the checkbox item */ ECheckBoxState GetState() const { return State; } /** * Gets the package represented by this checkbox item * * @return the package represented by this checkbox item */ UPackage* GetPackage() const { return Package; } /** * Get the object belonging to the package, if any * * @return the object which is in the package, if any */ UObject* GetPackageObject() const; /** * Get whether the package contains multiple assets * * @return whether the package contains multiple assets */ bool HasMultipleAssets() const; /** * Checks to see if the checkbox item is disabled * * @return True if the checkbox item is disabled, false otherwise */ bool IsDisabled() const { return Disabled; } /** * Gets the display name of the primary asset item * * @return the display name of the asset item */ const FString& GetAssetDisplayName() const { return AssetDisplayName; } /** * Gets the name of the package item * * @return the name of the package item */ const FString& GetPackageName() const { return PackageName; } /** * Gets the name of the file item * * @return the name of the file item */ const FString& GetFileName() const { return FileName; } /** * Gets the Verse path of the primary asset item * * @return the Verse path of the primary asset item */ const UE::Core::FVersePath& GetVersePath() const { return VersePath; } /** * Gets the name of the file owner item * * @return the name of the file owner item */ const FString& GetOwnerName() const { return OwnerName; } /** * Gets the icon name of the checkbox item * * @return the icon name of the checkbox item */ const FString& GetIconName() const { return IconName; } /** * Get a string containing the name(s) of other users who have the file checked out * * @return names of other users who have the file checked out */ FString GetCheckedOutByString() const { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use); FString CheckedOutBy; if (SourceControlState.IsValid()) { SourceControlState->IsCheckedOutOther(&CheckedOutBy); // If not checked out in this branch, see if checked out or modified in other branches if (!CheckedOutBy.Len()) { // Check for modification first, as this is a more important state than only checked out if (SourceControlState->IsModifiedInOtherBranch()) { FString HeadBranch, HeadAction; int32 HeadCL; SourceControlState->GetOtherBranchHeadModification(HeadBranch, HeadAction, HeadCL); FNumberFormattingOptions NoCommas; NoCommas.UseGrouping = false; return FString::Format(TEXT("Modified in {0} CL:{1} ({2})"), { HeadBranch, FText::AsNumber(HeadCL, &NoCommas).ToString(), HeadAction }); } if (SourceControlState->IsCheckedOutInOtherBranch()) { return SourceControlState->GetOtherUserBranchCheckedOuts(); } } } return CheckedOutBy; } /** * Gets the type name and color of the package item * * @param OutName FText into which the type name will be placed, or an empty string if type cannot be obtained * @param OutColor FColor into which the type color will be placed * * @return Whether the details were successfully fetched. */ bool GetTypeNameAndColor(FText& OutName, FColor& OutColor) const; /** * Gets just the type name of the package item * * @return Type name of the package item, or an empty string */ FText GetTypeName() const { FText OutName; FColor OutColor; GetTypeNameAndColor(OutName, OutColor); return OutName; } /** * Gets the tool tip of the checkbox item * * @return the tool tip of the checkbox item */ FString GetToolTip() const { return IconToolTip; } /** * Sets the new checkbox item state * * @param NewState New state */ void SetState(ECheckBoxState NewState) { State = NewState; } private: UPackage* Package; // The package associated with this entry FString AssetDisplayName; // Name of the asset to display FString PackageName; // Name of the package to display FString FileName; // Name of the file UE::Core::FVersePath VersePath; // Verse path to display FString OwnerName; // Name of the owner of the file ECheckBoxState State; // The state of the checkbox bool Disabled; // if the entry is disabled FString IconName; // Name of an icon to show next to the checkbox FString IconToolTip; // ToolTip to display for the icon FSimpleDelegate RefreshButtonCallback; // ToolTip to display for the icon mutable TWeakObjectPtr Object; // Cached object associated with this entry. }; /** * Represents a package dialog comprised of packages and checkboxes and buttons */ class SPackagesDialog : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SPackagesDialog) : _ReadOnly(false) , _AllowSourceControlConnection(false) , _Message() , _Warning() {} /** When true, this dialog only shows a list of packages without the ability to filter */ SLATE_ATTRIBUTE(bool, ReadOnly) /** When true, this dialog displays a 'connect to source control' button */ SLATE_ATTRIBUTE(bool, AllowSourceControlConnection) /** The message of the widget */ SLATE_ARGUMENT(FText, Message) /** The warning message of the widget */ SLATE_ARGUMENT(FText, Warning) /** Called when source control state changes */ SLATE_EVENT(FSimpleDelegate, OnSourceControlStateChanged) SLATE_END_ARGS() /** * Construct this widget * * @param InArgs The declaration data for this widget */ void Construct( const FArguments& InArgs ); /** * Create and return a widget for the given item and column ID * * @param Item The item being queried * @param ColumnID The column ID being queried * * @return The widget which was created */ TSharedRef GenerateWidgetForItemAndColumn( TSharedPtr Item, const FName ColumnID ) const; /** * Removes all checkbox items from the dialog */ void RemoveAll(); /** * Adds a new checkbox item to the dialog * * @param Item The item to be added */ void Add(TSharedPtr Item); /** * Adds a new button to the dialog * * @param Button The button to be added */ void AddButton(TSharedPtr Button); /** * Sets the message of the widget * * @param InMessage The string that the message should be set to */ void SetMessage(const FText& InMessage); /** * Sets the warning message of the widget * * @param InMessage The string that the warning message should be set to */ void SetWarning(const FText& InMessage); /** * Gets the return type of the dialog and it populates the package array results * * @param OutCheckedPackages Will be populated with the checked packages * @param OutUncheckedPackages Will be populated with the unchecked packages * @param OutUndeterminedPackages Will be populated with the undetermined packages * * @return returns the button that was pressed to remove the dialog */ EDialogReturnType GetReturnType(OUT TArray& OutCheckedPackages, OUT TArray& OutUncheckedPackages, OUT TArray& OutUndeterminedPackages); /** * Gets the widget which is to have keyboard focus on activating the dialog * * @return returns the widget */ TSharedPtr< SWidget > GetWidgetToFocusOnActivate() const; /** * Get the visibility of the 'Connect to Source Control' button */ EVisibility GetConnectToSourceControlVisibility() const; /** * Delegate used when the 'Connect to Source Control' button is clicked */ FReply OnConnectToSourceControlClicked() const; /** * Populate the items with their current ignore status * @param InIgnorePackages Container to populate the items from. */ void PopulateIgnoreForSaveItems( const TSet& InIgnorePackages ); /** * Populate current ignore status array with the item status * @param InOutIgnorePackages Container to populate with the current ignore status of the items. */ void PopulateIgnoreForSaveArray( OUT TSet& InOutIgnorePackages ) const; /** * Reset the state of this dialogs buttons */ void Reset(); /** * Whether the dialog allows a source control connection */ bool IsSourceControlConnectionAllowed() const { return bAllowSourceControlConnection; } private: //~ Begin SWidget Interface virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) override; //~ End SWidget Interface /** * Called when the checkbox items have changed state */ void RefreshButtons(); /** * Makes the widget for the checkbox items in the list view */ TSharedRef MakePackageListItemWidget(TSharedPtr Item, const TSharedRef& OwnerTable); /** * Makes the widget for the context menu in the list view */ TSharedPtr MakePackageListContextMenu() const; /** * Handler to check to see if "Diff Against Depot" can be executed */ bool CanExecuteSCCDiffAgainstDepot() const; /** * Handler for when "Diff Against Depot" is selected */ void ExecuteSCCDiffAgainstDepot() const; /** * Get all the selected items in the dialog. * * @param bAllIfNone - if true, returns all items when none are selected * @return the array of selected packages */ TArray< TSharedPtr > GetSelectedItems( bool bAllIfNone ) const; /** Delegate used to supply message text to the widget */ FText GetMessage() const; /** Delegate used to supply warning text to the widget */ FText GetWarning() const; /** Delegate used to determine visibility of the warning */ EVisibility GetWarningVisibility() const; /** * Returns the current column sort mode (ascending or descending) if the ColumnId parameter matches the current * column to be sorted by, otherwise returns EColumnSortMode_None. * * @param ColumnId Column ID to query sort mode for. * * @return The sort mode for the column, or EColumnSortMode_None if it is not known. */ EColumnSortMode::Type GetColumnSortMode( const FName ColumnId ) const; /** * Callback for SHeaderRow::Column::OnSort, called when the column to sort by is changed. * * @param ColumnId The new column to sort by * @param InSortMode The sort mode (ascending or descending) */ void OnColumnSortModeChanged( const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode ); /** * Requests that the source list data be sorted according to the current sort column and mode, * and refreshes the list view. */ void RequestSort(); /** * Sorts the source list data according to the current sort column and mode. */ void SortTree(); /** All checkbox items stored in this widget for the list view */ TArray< TSharedPtr > Items; /** The list view for showing all checkboxes */ TSharedPtr< SListView< TSharedPtr > >ItemListView; /** All buttons stored in this widget */ TArray< TSharedPtr > Buttons; /** A horizontal box that will contain all of the buttons */ TSharedPtr< SHorizontalBox > ButtonsBox; /** Refresh callback that should be called when a checkbox item state change */ FSimpleDelegate RefreshButtonsCallback; /** A horizontal box that will represent the message of the widget */ TSharedPtr< SHorizontalBox > MessageBox; /** When true, this dialog only shows a list of packages without the ability to filter */ bool bReadOnly; /** When true, this dialog displays a 'connect to source control' button */ bool bAllowSourceControlConnection; /** When true, the warning message is displayed in the widget */ bool bShowWarning; /** When true, items will be sorted on the next tick */ bool bSortDirty; /** The message to display */ FText Message; /** The warning to display */ FText Warning; /** Specify which column to sort with */ FName SortByColumn; /** Currently selected sorting mode */ EColumnSortMode::Type SortMode; /** Called when source control state changes */ FSimpleDelegate OnSourceControlStateChanged; }; /** Widget that represents a row in the PackagesDialog's list view. Generates widgets for each column on demand. */ class SPackageItemsListRow : public SMultiColumnTableRow< TSharedPtr< FPackageItem > > { public: SLATE_BEGIN_ARGS( SPackageItemsListRow ) {} /** The Packages Dialog that owns the tree. We'll only keep a weak reference to it. */ SLATE_ARGUMENT( TSharedPtr< SPackagesDialog >, PackagesDialog ) /** The list item for this row */ SLATE_ARGUMENT( TSharedPtr< FPackageItem >, Item ) SLATE_END_ARGS() /** Construct function for this widget */ void Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView ); /** Overridden from SMultiColumnTableRow. Generates a widget for this column of the list row. */ virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override; private: /** Weak reference to the PackagesDialog widget that owns our list */ TWeakPtr< SPackagesDialog > PackagesDialogWeak; /** The item associated with this row of data */ TSharedPtr< FPackageItem > Item; };