// Copyright Epic Games, Inc. All Rights Reserved. #include "STableTreeView.h" #include "EditorStyleSet.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Layout/WidgetPath.h" #include "SlateOptMacros.h" #include "TraceServices/AnalysisService.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/SToolTip.h" #include "Widgets/Views/STableViewBase.h" // Insights #include "Insights/Common/Stopwatch.h" #include "Insights/InsightsManager.h" #include "Insights/Table/ViewModels/Table.h" #include "Insights/Table/ViewModels/TableColumn.h" #include "Insights/Table/ViewModels/TableTreeNode.h" #include "Insights/Table/ViewModels/TreeNodeGrouping.h" #include "Insights/Table/ViewModels/TreeNodeSorting.h" #include "Insights/Table/ViewModels/UntypedTable.h" #include "Insights/Table/Widgets/STableTreeViewTooltip.h" #include "Insights/Table/Widgets/STableTreeViewRow.h" #include "Insights/TimingProfilerCommon.h" #define LOCTEXT_NAMESPACE "STableTreeView" namespace Insights { const FName STableTreeView::RootNodeName(TEXT("Root")); //////////////////////////////////////////////////////////////////////////////////////////////////// STableTreeView::STableTreeView() : Table(), Session(FInsightsManager::Get()->GetSession()), TreeView(nullptr), TreeViewHeaderRow(nullptr), ExternalScrollbar(nullptr), HoveredColumnId(), HoveredNodePtr(nullptr), HighlightedNodeName(), Root(MakeShared(RootNodeName, Table)), TableTreeNodes(), FilteredGroupNodes(), ExpandedNodes(), bExpansionSaved(false), SearchBox(nullptr), TextFilter(nullptr), Filters(nullptr), AvailableGroupings(), CurrentGroupings(), GroupingBreadcrumbTrail(nullptr), AvailableSorters(), CurrentSorter(nullptr), ColumnBeingSorted(GetDefaultColumnBeingSorted()), ColumnSortMode(GetDefaultColumnSortMode()), StatsStartTime(0.0), StatsEndTime(0.0) { } //////////////////////////////////////////////////////////////////////////////////////////////////// STableTreeView::~STableTreeView() { // Remove ourselves from the Insights manager. if (FInsightsManager::Get().IsValid()) { FInsightsManager::Get()->GetSessionChangedEvent().RemoveAll(this); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::Construct(const FArguments& InArgs, TSharedPtr InTablePtr) { ConstructWidget(InTablePtr); } //////////////////////////////////////////////////////////////////////////////////////////////////// BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void STableTreeView::ConstructWidget(TSharedPtr InTablePtr) { check(InTablePtr.IsValid()); Table = InTablePtr; SAssignNew(ExternalScrollbar, SScrollBar) .AlwaysShowScrollbar(true); ChildSlot [SNew(SVerticalBox) + SVerticalBox::Slot() .VAlign(VAlign_Center) .AutoHeight() [SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(2.0f) [SNew(SVerticalBox) // Search box + SVerticalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoHeight() [SAssignNew(SearchBox, SSearchBox) .HintText(LOCTEXT("SearchBoxHint", "Search")) .OnTextChanged(this, &STableTreeView::SearchBox_OnTextChanged) .IsEnabled(this, &STableTreeView::SearchBox_IsEnabled) .ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search the tree hierarchy by item or group name"))] // Group by + SVerticalBox::Slot() .VAlign(VAlign_Center) .Padding(2.0f) .AutoHeight() [SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [SNew(STextBlock) .Text(LOCTEXT("GroupByText", "Hierarchy:")) .Margin(FMargin(0.0f, 0.0f, 4.0f, 0.0f))] + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [SAssignNew(GroupingBreadcrumbTrail, SBreadcrumbTrail>) .ButtonContentPadding(FMargin(1.0f, 1.0f)) //.DelimiterImage(FEditorStyle::GetBrush("SlateFileDialogs.PathDelimiter")) //.TextStyle(FEditorStyle::Get(), "Tutorials.Browser.PathText") //.ShowLeadingDelimiter(true) //.PersistentBreadcrumbs(true) .InvertTextColorOnHover(true) .OnCrumbClicked(this, &STableTreeView::OnGroupingCrumbClicked) .GetCrumbMenuContent(this, &STableTreeView::GetGroupingCrumbMenuContent)]]]] // Tree view + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f, 6.0f, 0.0f, 0.0f) [SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(0.0f) [SNew(SScrollBox) .Orientation(Orient_Horizontal) + SScrollBox::Slot() [SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(0.0f) [SAssignNew(TreeView, STreeView) .ExternalScrollbar(ExternalScrollbar) .SelectionMode(ESelectionMode::Multi) .TreeItemsSource(&FilteredGroupNodes) .OnGetChildren(this, &STableTreeView::TreeView_OnGetChildren) .OnGenerateRow(this, &STableTreeView::TreeView_OnGenerateRow) .OnSelectionChanged(this, &STableTreeView::TreeView_OnSelectionChanged) .OnMouseButtonDoubleClick(this, &STableTreeView::TreeView_OnMouseButtonDoubleClick) .OnContextMenuOpening(FOnContextMenuOpening::CreateSP(this, &STableTreeView::TreeView_GetMenuContent)) .ItemHeight(12.0f) .HeaderRow( SAssignNew(TreeViewHeaderRow, SHeaderRow) .Visibility(EVisibility::Visible))]]] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f) [SNew(SBox) .WidthOverride(FOptionalSize(13.0f)) [ExternalScrollbar.ToSharedRef()]]]]; // Create the search filters: text based, type based etc. TextFilter = MakeShared(FTableTreeNodeTextFilter::FItemToStringArray::CreateSP(this, &STableTreeView::HandleItemToStringArray)); Filters = MakeShared(); Filters->Add(TextFilter); // BindCommands(); InitializeAndShowHeaderColumns(); CreateGroupings(); CreateSortings(); // Register ourselves with the Insights manager. FInsightsManager::Get()->GetSessionChangedEvent().AddSP(this, &STableTreeView::InsightsManager_OnSessionChanged); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr STableTreeView::TreeView_GetMenuContent() { const TArray SelectedNodes = TreeView->GetSelectedItems(); const int32 NumSelectedNodes = SelectedNodes.Num(); FTableTreeNodePtr SelectedNode = NumSelectedNodes ? SelectedNodes[0] : nullptr; const TSharedPtr HoveredColumnPtr = Table->FindColumn(HoveredColumnId); FText SelectionStr; FText PropertyName; FText PropertyValue; if (NumSelectedNodes == 0) { SelectionStr = LOCTEXT("NothingSelected", "Nothing selected"); } else if (NumSelectedNodes == 1) { if (HoveredColumnPtr != nullptr) { PropertyName = HoveredColumnPtr->GetShortName(); PropertyValue = HoveredColumnPtr->GetValueAsTooltipText(*SelectedNode); } FString ItemName = SelectedNode->GetName().ToString(); const int32 MaxStringLen = 64; if (ItemName.Len() > MaxStringLen) { ItemName = ItemName.Left(MaxStringLen) + TEXT("..."); } SelectionStr = FText::FromString(ItemName); } else { SelectionStr = LOCTEXT("MultipleSelection", "Multiple selection"); } const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); // Selection menu MenuBuilder.BeginSection("Selection", LOCTEXT("ContextMenu_Header_Selection", "Selection")); { struct FLocal { static bool ReturnFalse() { return false; } }; FUIAction DummyUIAction; DummyUIAction.CanExecuteAction = FCanExecuteAction::CreateStatic(&FLocal::ReturnFalse); MenuBuilder.AddMenuEntry( SelectionStr, LOCTEXT("ContextMenu_Selection", "Currently selected items"), FSlateIcon(FEditorStyle::GetStyleSetName(), "@missing.icon"), DummyUIAction, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Misc", LOCTEXT("ContextMenu_Header_Misc", "Miscellaneous")); { /*TODO FUIAction Action_CopySelectedToClipboard ( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_CopySelectedToClipboard_Execute), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_CopySelectedToClipboard_CanExecute) ); MenuBuilder.AddMenuEntry ( LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard", "Copy To Clipboard"), LOCTEXT("ContextMenu_Header_Misc_CopySelectedToClipboard_Desc", "Copies selection to clipboard"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.CopyToClipboard"), Action_CopySelectedToClipboard, NAME_None, EUserInterfaceActionType::Button ); */ MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Misc_Sort", "Sort By"), LOCTEXT("ContextMenu_Header_Misc_Sort_Desc", "Sort by column"), FNewMenuDelegate::CreateSP(this, &STableTreeView::TreeView_BuildSortByMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortBy")); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("Columns", LOCTEXT("ContextMenu_Header_Columns", "Columns")); { MenuBuilder.AddSubMenu( LOCTEXT("ContextMenu_Header_Columns_View", "View Column"), LOCTEXT("ContextMenu_Header_Columns_View_Desc", "Hides or shows columns"), FNewMenuDelegate::CreateSP(this, &STableTreeView::TreeView_BuildViewColumnMenu), false, FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ViewColumn")); FUIAction Action_ShowAllColumns( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_ShowAllColumns_Execute), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_ShowAllColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns", "Show All Columns"), LOCTEXT("ContextMenu_Header_Columns_ShowAllColumns_Desc", "Resets tree view to show all columns"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ShowAllColumns, NAME_None, EUserInterfaceActionType::Button); FUIAction Action_ResetColumns( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_ResetColumns_Execute), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_ResetColumns_CanExecute)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Columns_ResetColumns", "Reset Columns to Default"), LOCTEXT("ContextMenu_Header_Columns_ResetColumns_Desc", "Resets columns to default"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.EventGraph.ResetColumn"), Action_ResetColumns, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_BuildSortByMenu(FMenuBuilder& MenuBuilder) { // TODO: Refactor later @see TSharedPtr SCascadePreviewViewportToolBar::GenerateViewMenu() const MenuBuilder.BeginSection("ColumnName", LOCTEXT("ContextMenu_Header_Misc_ColumnName", "Column Name")); // TODO: for (Sorting : AvailableSortings) for (const TSharedRef& ColumnRef: Table->GetColumns()) { const FTableColumn& Column = *ColumnRef; if (Column.IsVisible() && Column.CanBeSorted()) { FUIAction Action_SortByColumn( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortByColumn_Execute, Column.GetId()), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortByColumn_CanExecute, Column.GetId()), FIsActionChecked::CreateSP(this, &STableTreeView::ContextMenu_SortByColumn_IsChecked, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_SortByColumn, NAME_None, EUserInterfaceActionType::RadioButton); } } MenuBuilder.EndSection(); //----------------------------------------------------------------------------- MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); { FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortMode_Execute, EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &STableTreeView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortMode_Execute, EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &STableTreeView::ContextMenu_SortMode_CanExecute, EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &STableTreeView::ContextMenu_SortMode_IsChecked, EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_BuildViewColumnMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("ViewColumn", LOCTEXT("ContextMenu_Header_Columns_View", "View Column")); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const FTableColumn& Column = *ColumnRef; FUIAction Action_ToggleColumn( FExecuteAction::CreateSP(this, &STableTreeView::ToggleColumnVisibility, Column.GetId()), FCanExecuteAction::CreateSP(this, &STableTreeView::CanToggleColumnVisibility, Column.GetId()), FIsActionChecked::CreateSP(this, &STableTreeView::IsColumnVisible, Column.GetId())); MenuBuilder.AddMenuEntry( Column.GetTitleName(), Column.GetDescription(), FSlateIcon(), Action_ToggleColumn, NAME_None, EUserInterfaceActionType::ToggleButton); } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::InitializeAndShowHeaderColumns() { for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->ShouldBeVisible()) { ShowColumn(ColumnRef->GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::RebuildColumns() { TreeViewHeaderRow->ClearColumns(); InitializeAndShowHeaderColumns(); PreChangeGroupings(); CreateGroupings(); PostChangeGroupings(); CreateSortings(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FText STableTreeView::GetColumnHeaderText(const FName ColumnId) const { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.GetShortName(); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STableTreeView::TreeViewHeaderRow_GenerateColumnMenu(const FTableColumn& Column) { bool bIsMenuVisible = false; const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); { if (Column.CanBeHidden()) { MenuBuilder.BeginSection("Column", LOCTEXT("TreeViewHeaderRow_Header_Column", "Column")); FUIAction Action_HideColumn( FExecuteAction::CreateSP(this, &STableTreeView::HideColumn, Column.GetId()), FCanExecuteAction::CreateSP(this, &STableTreeView::CanHideColumn, Column.GetId())); MenuBuilder.AddMenuEntry( LOCTEXT("TreeViewHeaderRow_HideColumn", "Hide"), LOCTEXT("TreeViewHeaderRow_HideColumn_Desc", "Hides the selected column"), FSlateIcon(), Action_HideColumn, NAME_None, EUserInterfaceActionType::Button); bIsMenuVisible = true; MenuBuilder.EndSection(); } if (Column.CanBeSorted()) { MenuBuilder.BeginSection("SortMode", LOCTEXT("ContextMenu_Header_Misc_Sort_SortMode", "Sort Mode")); FUIAction Action_SortAscending( FExecuteAction::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Ascending), FCanExecuteAction::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Ascending), FIsActionChecked::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Ascending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending", "Sort Ascending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortAscending_Desc", "Sorts ascending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortAscending"), Action_SortAscending, NAME_None, EUserInterfaceActionType::RadioButton); FUIAction Action_SortDescending( FExecuteAction::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_Execute, Column.GetId(), EColumnSortMode::Descending), FCanExecuteAction::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_CanExecute, Column.GetId(), EColumnSortMode::Descending), FIsActionChecked::CreateSP(this, &STableTreeView::HeaderMenu_SortMode_IsChecked, Column.GetId(), EColumnSortMode::Descending)); MenuBuilder.AddMenuEntry( LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending", "Sort Descending"), LOCTEXT("ContextMenu_Header_Misc_Sort_SortDescending_Desc", "Sorts descending"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Profiler.Misc.SortDescending"), Action_SortDescending, NAME_None, EUserInterfaceActionType::RadioButton); bIsMenuVisible = true; MenuBuilder.EndSection(); } // if (Column.CanBeFiltered()) //{ // MenuBuilder.BeginSection("FilterMode", LOCTEXT("ContextMenu_Header_Misc_Filter_FilterMode", "Filter Mode")); // bIsMenuVisible = true; // MenuBuilder.EndSection(); // } } return bIsMenuVisible ? MenuBuilder.MakeWidget() : (TSharedRef)SNullWidget::NullWidget; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::InsightsManager_OnSessionChanged() { TSharedPtr NewSession = FInsightsManager::Get()->GetSession(); if (NewSession != Session) { Session = NewSession; Reset(); } else { UpdateTree(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::UpdateTree() { CreateGroups(); SortTreeNodes(); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ApplyFiltering() { // Apply filter to all groups and its children. ApplyFilteringForNode(Root); FilteredGroupNodes.Reset(); const TArray& RootChildren = Root->GetFilteredChildren(); const int32 NumRootChildren = RootChildren.Num(); for (int32 Cx = 0; Cx < NumRootChildren; ++Cx) { // Add a child. const FTableTreeNodePtr& ChildNodePtr = StaticCastSharedPtr(RootChildren[Cx]); if (ChildNodePtr->IsGroup()) { FilteredGroupNodes.Add(ChildNodePtr); } } // Only expand nodes if we have a text filter. const bool bNonEmptyTextFilter = !TextFilter->GetRawFilterText().IsEmpty(); if (bNonEmptyTextFilter) { if (!bExpansionSaved) { ExpandedNodes.Empty(); TreeView->GetExpandedItems(ExpandedNodes); bExpansionSaved = true; } for (int32 Fx = 0; Fx < FilteredGroupNodes.Num(); Fx++) { const FTableTreeNodePtr& GroupPtr = FilteredGroupNodes[Fx]; TreeView->SetItemExpansion(GroupPtr, GroupPtr->IsExpanded()); } } else { if (bExpansionSaved) { // Restore previously expanded nodes when the text filter is disabled. TreeView->ClearExpandedItems(); for (auto It = ExpandedNodes.CreateConstIterator(); It; ++It) { TreeView->SetItemExpansion(*It, true); } bExpansionSaved = false; } } // Request tree refresh TreeView->RequestTreeRefresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ApplyFilteringForNode(FTableTreeNodePtr NodePtr) { const bool bIsNodeVisible = Filters->PassesAllFilters(NodePtr); if (NodePtr->IsGroup()) { NodePtr->ClearFilteredChildren(); const TArray& GroupChildren = NodePtr->GetChildren(); const int32 NumChildren = GroupChildren.Num(); int32 NumVisibleChildren = 0; for (int32 Cx = 0; Cx < NumChildren; ++Cx) { // Add a child. const FTableTreeNodePtr& ChildNodePtr = StaticCastSharedPtr(GroupChildren[Cx]); if (ApplyFilteringForNode(ChildNodePtr)) { NodePtr->AddFilteredChild(ChildNodePtr); NumVisibleChildren++; } } const bool bIsGroupNodeVisible = bIsNodeVisible || NumVisibleChildren > 0; if (bIsGroupNodeVisible) { // Add a group. NodePtr->SetExpansion(true); } else { NodePtr->SetExpansion(false); } return bIsGroupNodeVisible; } else { return bIsNodeVisible; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::HandleItemToStringArray(const FTableTreeNodePtr& FTableTreeNodePtr, TArray& OutSearchStrings) const { OutSearchStrings.Add(FTableTreeNodePtr->GetName().GetPlainNameString()); } //////////////////////////////////////////////////////////////////////////////////////////////////// // TreeView //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_Refresh() { if (TreeView.IsValid()) { TreeView->RequestTreeRefresh(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_OnSelectionChanged(FTableTreeNodePtr SelectedItem, ESelectInfo::Type SelectInfo) { } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_OnGetChildren(FTableTreeNodePtr InParent, TArray& OutChildren) { const TArray& FilteredChildren = InParent->GetFilteredChildren(); for (const FBaseTreeNodePtr& NodePtr: FilteredChildren) { OutChildren.Add(StaticCastSharedPtr(NodePtr)); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TreeView_OnMouseButtonDoubleClick(FTableTreeNodePtr NodePtr) { if (NodePtr->IsGroup()) { const bool bIsGroupExpanded = TreeView->IsItemExpanded(NodePtr); TreeView->SetItemExpansion(NodePtr, !bIsGroupExpanded); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Tree View's Table Row //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STableTreeView::TreeView_OnGenerateRow(FTableTreeNodePtr NodePtr, const TSharedRef& OwnerTable) { TSharedRef TableRow = SNew(STableTreeViewRow, OwnerTable) .OnShouldBeEnabled(this, &STableTreeView::TableRow_ShouldBeEnabled) .OnIsColumnVisible(this, &STableTreeView::IsColumnVisible) .OnSetHoveredCell(this, &STableTreeView::TableRow_SetHoveredCell) .OnGetColumnOutlineHAlignmentDelegate(this, &STableTreeView::TableRow_GetColumnOutlineHAlignment) .HighlightText(this, &STableTreeView::TableRow_GetHighlightText) .HighlightedNodeName(this, &STableTreeView::TableRow_GetHighlightedNodeName) .TablePtr(Table) .TableTreeNodePtr(NodePtr); return TableRow; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::TableRow_ShouldBeEnabled(FTableTreeNodePtr NodePtr) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::TableRow_SetHoveredCell(TSharedPtr InTablePtr, TSharedPtr InColumnPtr, FTableTreeNodePtr InNodePtr) { HoveredColumnId = InColumnPtr ? InColumnPtr->GetId() : FName(); const bool bIsAnyMenusVisible = FSlateApplication::Get().AnyMenusVisible(); if (!HasMouseCapture() && !bIsAnyMenusVisible) { HoveredNodePtr = InNodePtr; } } //////////////////////////////////////////////////////////////////////////////////////////////////// EHorizontalAlignment STableTreeView::TableRow_GetColumnOutlineHAlignment(const FName ColumnId) const { const TIndirectArray& Columns = TreeViewHeaderRow->GetColumns(); const int32 LastColumnIdx = Columns.Num() - 1; // First column if (Columns[0].ColumnId == ColumnId) { return HAlign_Left; } // Last column else if (Columns[LastColumnIdx].ColumnId == ColumnId) { return HAlign_Right; } // Middle columns { return HAlign_Center; } } //////////////////////////////////////////////////////////////////////////////////////////////////// FText STableTreeView::TableRow_GetHighlightText() const { return SearchBox->GetText(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FName STableTreeView::TableRow_GetHighlightedNodeName() const { return HighlightedNodeName; } //////////////////////////////////////////////////////////////////////////////////////////////////// // SearchBox //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::SearchBox_OnTextChanged(const FText& InFilterText) { TextFilter->SetRawFilterText(InFilterText); SearchBox->SetError(TextFilter->GetFilterErrorText()); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::SearchBox_IsEnabled() const { return TableTreeNodes.Num() > 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Grouping //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::CreateGroups() { GroupNodesRec(TableTreeNodes, *Root, 0); ResetAggregatedValuesRec(*Root); for (const TSharedRef& ColumnRef: Table->GetColumns()) { FTableColumn& Column = *ColumnRef; if (Column.GetAggregation() == ETableColumnAggregation::Sum) { switch (Column.GetDataType()) { case ETableCellDataType::Int64: UpdateInt64SumAggregationRec(Column, *Root); break; case ETableCellDataType::Float: UpdateFloatSumAggregationRec(Column, *Root); break; case ETableCellDataType::Double: UpdateDoubleSumAggregationRec(Column, *Root); break; } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupNodesRec(const TArray& Nodes, FTableTreeNode& ParentGroup, int32 GroupingDepth) { ensure(CurrentGroupings.Num() > 0); FTreeNodeGrouping& Grouping = *CurrentGroupings[GroupingDepth]; TMap GroupMap; ParentGroup.ClearChildren(); for (FTableTreeNodePtr NodePtr: Nodes) { ensure(!NodePtr->IsGroup()); FTableTreeNodePtr GroupPtr = nullptr; FTreeNodeGroupInfo GroupInfo = Grouping.GetGroupForNode(NodePtr); FTableTreeNodePtr* GroupPtrPtr = GroupMap.Find(GroupInfo.Name); if (!GroupPtrPtr) { GroupPtr = MakeShared(GroupInfo.Name, Table); GroupMap.Add(GroupInfo.Name, GroupPtr); ParentGroup.AddChildAndSetGroupPtr(GroupPtr); GroupPtr->SetExpansion(GroupInfo.IsExpanded); TreeView->SetItemExpansion(GroupPtr, GroupInfo.IsExpanded); } else { GroupPtr = *GroupPtrPtr; } GroupPtr->AddChildAndSetGroupPtr(NodePtr); } if (GroupingDepth < CurrentGroupings.Num() - 1) { TArray ChildNodes; for (FBaseTreeNodePtr GroupPtr: ParentGroup.GetChildren()) { ensure(GroupPtr->IsGroup()); FTableTreeNode& Group = *StaticCastSharedPtr(GroupPtr); // Make a copy of the child nodes. ChildNodes.Reset(); for (FBaseTreeNodePtr ChildPtr: Group.GetChildren()) { ChildNodes.Add(StaticCastSharedPtr(ChildPtr)); } GroupNodesRec(ChildNodes, Group, GroupingDepth + 1); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ResetAggregatedValuesRec(FTableTreeNode& GroupNode) { ensure(GroupNode.IsGroup()); GroupNode.ResetAggregatedValues(); for (FBaseTreeNodePtr ChildPtr: GroupNode.GetChildren()) { if (ChildPtr->IsGroup()) { ResetAggregatedValuesRec(*StaticCastSharedPtr(ChildPtr)); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::UpdateInt64SumAggregationRec(FTableColumn& Column, FTableTreeNode& GroupNode) { int64 AggregatedValue = 0; for (FBaseTreeNodePtr NodePtr: GroupNode.GetChildren()) { FTableTreeNode& TableNode = *StaticCastSharedPtr(NodePtr); if (TableNode.IsGroup()) { UpdateInt64SumAggregationRec(Column, TableNode); } const TOptional OptionalValue = Column.GetValue(TableNode); if (OptionalValue.IsSet()) { ensure(OptionalValue.GetValue().DataType == ETableCellDataType::Int64); AggregatedValue += OptionalValue.GetValue().Int64; } } GroupNode.AddAggregatedValue(Column.GetId(), FTableCellValue(AggregatedValue)); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::UpdateFloatSumAggregationRec(FTableColumn& Column, FTableTreeNode& GroupNode) { float AggregatedValue = 0.0f; for (FBaseTreeNodePtr NodePtr: GroupNode.GetChildren()) { FTableTreeNode& TableNode = *StaticCastSharedPtr(NodePtr); if (TableNode.IsGroup()) { UpdateFloatSumAggregationRec(Column, TableNode); } const TOptional OptionalValue = Column.GetValue(TableNode); if (OptionalValue.IsSet()) { ensure(OptionalValue.GetValue().DataType == ETableCellDataType::Float); AggregatedValue += OptionalValue.GetValue().Float; } } GroupNode.AddAggregatedValue(Column.GetId(), FTableCellValue(AggregatedValue)); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::UpdateDoubleSumAggregationRec(FTableColumn& Column, FTableTreeNode& GroupNode) { double AggregatedValue = 0.0; for (FBaseTreeNodePtr NodePtr: GroupNode.GetChildren()) { FTableTreeNode& TableNode = *StaticCastSharedPtr(NodePtr); if (TableNode.IsGroup()) { UpdateDoubleSumAggregationRec(Column, TableNode); } const TOptional OptionalValue = Column.GetValue(TableNode); if (OptionalValue.IsSet()) { ensure(OptionalValue.GetValue().DataType == ETableCellDataType::Double); AggregatedValue += OptionalValue.GetValue().Double; } } GroupNode.AddAggregatedValue(Column.GetId(), FTableCellValue(AggregatedValue)); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::CreateGroupings() { AvailableGroupings.Reset(); CurrentGroupings.Reset(); AvailableGroupings.Add(MakeShared()); // AvailableGroupings.Add(MakeShared()); // AvailableGroupings.Add(MakeShared()); for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (!ColumnRef->IsHierarchy()) { AvailableGroupings.Add(MakeShared(ColumnRef)); } } CurrentGroupings.Add(AvailableGroupings[0]); RebuildGroupingCrumbs(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::PreChangeGroupings() { for (TSharedPtr GroupingPtr: CurrentGroupings) { const FName& ColumnId = GroupingPtr->GetColumnId(); if (ColumnId != NAME_None) { // Show columns used in previous groupings. ShowColumn(ColumnId); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::PostChangeGroupings() { constexpr float HierarchyMinWidth = 60.0f; constexpr float HierarchyIndentation = 10.0f; constexpr float DefaultHierarchyColumnWidth = 90.0f; float HierarchyColumnWidth = DefaultHierarchyColumnWidth; // FString GroupingStr; int32 GroupingDepth = 0; for (TSharedPtr GroupingPtr: CurrentGroupings) { const FName& ColumnId = GroupingPtr->GetColumnId(); if (ColumnId != NAME_None) { // Compute width for Hierarchy column based on column used in grouping and its indentation. const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); for (int32 ColumnIndex = 0; ColumnIndex < NumColumns; ++ColumnIndex) { const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; if (CurrentColumn.ColumnId == ColumnId) { const float Width = HierarchyMinWidth + GroupingDepth * HierarchyIndentation + CurrentColumn.GetWidth(); if (Width > HierarchyColumnWidth) { HierarchyColumnWidth = Width; } break; } } // Hide columns used in groupings. HideColumn(ColumnId); } // Compute name of the Hierarchy column. // if (!GroupingStr.IsEmpty()) //{ // GroupingStr.Append(TEXT(" / ")); //} // GroupingStr.Append(GroupingPtr->GetShortName().ToString()); ++GroupingDepth; } ////////////////////////////////////////////////// if (TreeViewHeaderRow->GetColumns().Num() > 0) { // Set width for the Hierarchy column. SHeaderRow::FColumn& HierarchyColumn = const_cast(TreeViewHeaderRow->GetColumns()[0]); HierarchyColumn.SetWidth(HierarchyColumnWidth); // Set name for the Hierarchy column. // FTableColumn& HierarchyTableColumn = *Table->FindColumnChecked(HierarchyColumn.ColumnId); // if (!GroupingStr.IsEmpty()) //{ // const FText HierarchyColumnName = FText::Format(LOCTEXT("HierarchyShortNameFmt", "Hierarchy ({0})"), FText::FromString(GroupingStr)); // HierarchyTableColumn.SetShortName(HierarchyColumnName); //} // else //{ // const FText HierarchyColumnName(LOCTEXT("HierarchyShortName", "Hierarchy")); // HierarchyTableColumn.SetShortName(HierarchyColumnName); //} } ////////////////////////////////////////////////// TreeViewHeaderRow->RefreshColumns(); CreateGroups(); SortTreeNodes(); ApplyFiltering(); RebuildGroupingCrumbs(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::RebuildGroupingCrumbs() { GroupingBreadcrumbTrail->ClearCrumbs(); for (const TSharedPtr& Grouping: CurrentGroupings) { GroupingBreadcrumbTrail->PushCrumb(Grouping->GetShortName(), Grouping); } } //////////////////////////////////////////////////////////////////////////////////////////////////// int32 STableTreeView::GetGroupingDepth(const TSharedPtr& Grouping) const { for (int32 GroupingDepth = CurrentGroupings.Num() - 1; GroupingDepth >= 0; --GroupingDepth) { if (Grouping == CurrentGroupings[GroupingDepth]) { return GroupingDepth; } } return -1; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::OnGroupingCrumbClicked(const TSharedPtr& CrumbGrouping) { const int32 CrumbGroupingDepth = GetGroupingDepth(CrumbGrouping); if (CrumbGroupingDepth >= 0 && CrumbGroupingDepth < CurrentGroupings.Num() - 1) { PreChangeGroupings(); CurrentGroupings.RemoveAt(CrumbGroupingDepth + 1, CurrentGroupings.Num() - CrumbGroupingDepth - 1); PostChangeGroupings(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::BuildGroupingSubMenu_Change(FMenuBuilder& MenuBuilder, const TSharedPtr CrumbGrouping) { MenuBuilder.BeginSection("ChangeGrouping"); { for (const TSharedPtr& Grouping: AvailableGroupings) { FUIAction Action_Change( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Change_Execute, CrumbGrouping, Grouping), FCanExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Change_CanExecute, CrumbGrouping, Grouping)); MenuBuilder.AddMenuEntry( Grouping->GetTitleName(), Grouping->GetDescription(), FSlateIcon(), Action_Change, NAME_None, EUserInterfaceActionType::Button); } } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::BuildGroupingSubMenu_Add(FMenuBuilder& MenuBuilder, const TSharedPtr CrumbGrouping) { MenuBuilder.BeginSection("AddGrouping"); { for (const TSharedPtr& Grouping: AvailableGroupings) { FUIAction Action_Add( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Add_Execute, Grouping, CrumbGrouping), FCanExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Add_CanExecute, Grouping, CrumbGrouping)); MenuBuilder.AddMenuEntry( Grouping->GetTitleName(), Grouping->GetDescription(), FSlateIcon(), Action_Add, NAME_None, EUserInterfaceActionType::Button); } } MenuBuilder.EndSection(); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedRef STableTreeView::GetGroupingCrumbMenuContent(const TSharedPtr& CrumbGrouping) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL); const int32 CrumbGroupingDepth = GetGroupingDepth(CrumbGrouping); MenuBuilder.BeginSection("InsertOrAdd"); { const FText AddGroupingText = (CrumbGroupingDepth == CurrentGroupings.Num() - 1) ? // after last one LOCTEXT("GroupingMenu_Add", "Add Grouping...") : LOCTEXT("GroupingMenu_Insert", "Insert Grouping..."); MenuBuilder.AddSubMenu( AddGroupingText, LOCTEXT("GroupingMenu_AddOrInsert_Desc", "Add or insert new grouping."), FNewMenuDelegate::CreateSP(this, &STableTreeView::BuildGroupingSubMenu_Add, CrumbGrouping), false, FSlateIcon()); } MenuBuilder.EndSection(); if (CrumbGroupingDepth >= 0) { MenuBuilder.BeginSection("CrumbGrouping", CrumbGrouping->GetTitleName()); { MenuBuilder.AddSubMenu( LOCTEXT("GroupingMenu_Change", "Change To..."), LOCTEXT("GroupingMenu_Change_Desc", "Change selected grouping."), FNewMenuDelegate::CreateSP(this, &STableTreeView::BuildGroupingSubMenu_Change, CrumbGrouping), false, FSlateIcon()); if (CrumbGroupingDepth > 0) { FUIAction Action_MoveLeft( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_MoveLeft_Execute, CrumbGrouping), FCanExecuteAction()); MenuBuilder.AddMenuEntry( LOCTEXT("GroupingMenu_MoveLeft", "Move Left"), LOCTEXT("GroupingMenu_MoveLeft_Desc", "Move selected grouping to the left."), FSlateIcon(), Action_MoveLeft, NAME_None, EUserInterfaceActionType::Button); } if (CrumbGroupingDepth < CurrentGroupings.Num() - 1) { FUIAction Action_MoveRight( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_MoveRight_Execute, CrumbGrouping), FCanExecuteAction()); MenuBuilder.AddMenuEntry( LOCTEXT("GroupingMenu_MoveRight", "Move Right"), LOCTEXT("GroupingMenu_MoveRight_Desc", "Move selected grouping to the right."), FSlateIcon(), Action_MoveRight, NAME_None, EUserInterfaceActionType::Button); } if (CurrentGroupings.Num() > 1) { FUIAction Action_Remove( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Remove_Execute, CrumbGrouping), FCanExecuteAction()); MenuBuilder.AddMenuEntry( LOCTEXT("GroupingMenu_Remove", "Remove"), LOCTEXT("GroupingMenu_Remove_Desc", "Remove selected grouping."), FSlateIcon(), Action_Remove, NAME_None, EUserInterfaceActionType::Button); } } MenuBuilder.EndSection(); } if (CurrentGroupings.Num() > 1 || CurrentGroupings[0] != AvailableGroupings[0]) { MenuBuilder.BeginSection("ResetGroupings"); { FUIAction Action_Reset( FExecuteAction::CreateSP(this, &STableTreeView::GroupingCrumbMenu_Reset_Execute), FCanExecuteAction()); MenuBuilder.AddMenuEntry( LOCTEXT("GroupingMenu_Reset", "Reset"), LOCTEXT("GroupingMenu_Reset_Desc", "Reset groupings to default."), FSlateIcon(), Action_Reset, NAME_None, EUserInterfaceActionType::Button); } MenuBuilder.EndSection(); } return MenuBuilder.MakeWidget(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_Reset_Execute() { PreChangeGroupings(); CurrentGroupings.Reset(); CurrentGroupings.Add(AvailableGroupings[0]); PostChangeGroupings(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_Remove_Execute(const TSharedPtr Grouping) { const int32 GroupingDepth = GetGroupingDepth(Grouping); if (GroupingDepth >= 0) { PreChangeGroupings(); CurrentGroupings.RemoveAt(GroupingDepth); PostChangeGroupings(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_MoveLeft_Execute(const TSharedPtr Grouping) { const int32 GroupingDepth = GetGroupingDepth(Grouping); if (GroupingDepth > 0) { PreChangeGroupings(); CurrentGroupings[GroupingDepth] = CurrentGroupings[GroupingDepth - 1]; CurrentGroupings[GroupingDepth - 1] = Grouping; PostChangeGroupings(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_MoveRight_Execute(const TSharedPtr Grouping) { const int32 GroupingDepth = GetGroupingDepth(Grouping); if (GroupingDepth < CurrentGroupings.Num() - 1) { PreChangeGroupings(); CurrentGroupings[GroupingDepth] = CurrentGroupings[GroupingDepth + 1]; CurrentGroupings[GroupingDepth + 1] = Grouping; PostChangeGroupings(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_Change_Execute(const TSharedPtr OldGrouping, const TSharedPtr NewGrouping) { const int32 OldGroupingDepth = GetGroupingDepth(OldGrouping); if (OldGroupingDepth >= 0) { PreChangeGroupings(); const int32 NewGroupingDepth = GetGroupingDepth(NewGrouping); if (NewGroupingDepth >= 0 && NewGroupingDepth != OldGroupingDepth) // NewGrouping already exists { CurrentGroupings.RemoveAt(NewGroupingDepth); if (NewGroupingDepth < OldGroupingDepth) { CurrentGroupings[OldGroupingDepth - 1] = NewGrouping; } else { CurrentGroupings[OldGroupingDepth] = NewGrouping; } } else { CurrentGroupings[OldGroupingDepth] = NewGrouping; } PostChangeGroupings(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::GroupingCrumbMenu_Change_CanExecute(const TSharedPtr OldGrouping, const TSharedPtr NewGrouping) const { return NewGrouping != OldGrouping; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::GroupingCrumbMenu_Add_Execute(const TSharedPtr Grouping, const TSharedPtr AfterGrouping) { PreChangeGroupings(); if (AfterGrouping.IsValid()) { const int32 AfterGroupingDepth = GetGroupingDepth(AfterGrouping); ensure(AfterGroupingDepth >= 0); const int32 GroupingDepth = GetGroupingDepth(Grouping); if (GroupingDepth >= 0) // Grouping already exists { CurrentGroupings.RemoveAt(GroupingDepth); if (GroupingDepth <= AfterGroupingDepth) { CurrentGroupings.Insert(Grouping, AfterGroupingDepth); } else { CurrentGroupings.Insert(Grouping, AfterGroupingDepth + 1); } } else { CurrentGroupings.Insert(Grouping, AfterGroupingDepth + 1); } } else { CurrentGroupings.Remove(Grouping); CurrentGroupings.Add(Grouping); } PostChangeGroupings(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::GroupingCrumbMenu_Add_CanExecute(const TSharedPtr Grouping, const TSharedPtr AfterGrouping) const { if (AfterGrouping.IsValid()) { const int32 AfterGroupingDepth = GetGroupingDepth(AfterGrouping); ensure(AfterGroupingDepth >= 0); const int32 GroupingDepth = GetGroupingDepth(Grouping); return GroupingDepth < AfterGroupingDepth || GroupingDepth > AfterGroupingDepth + 1; } else { return Grouping != CurrentGroupings.Last(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Sorting //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// const EColumnSortMode::Type STableTreeView::GetDefaultColumnSortMode() { return EColumnSortMode::Descending; } //////////////////////////////////////////////////////////////////////////////////////////////////// const FName STableTreeView::GetDefaultColumnBeingSorted() { return NAME_None; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::CreateSortings() { AvailableSorters.Reset(); CurrentSorter = nullptr; for (const TSharedRef& ColumnRef: Table->GetColumns()) { if (ColumnRef->CanBeSorted()) { TSharedPtr SorterPtr = ColumnRef->GetValueSorter(); if (ensure(SorterPtr.IsValid())) { AvailableSorters.Add(SorterPtr); } } } UpdateCurrentSortingByColumn(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::UpdateCurrentSortingByColumn() { TSharedPtr ColumnPtr = Table->FindColumn(ColumnBeingSorted); CurrentSorter = ColumnPtr.IsValid() ? ColumnPtr->GetValueSorter() : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::SortTreeNodes() { if (CurrentSorter.IsValid()) { SortTreeNodesRec(*Root, *CurrentSorter); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::SortTreeNodesRec(FTableTreeNode& GroupNode, const ITableCellValueSorter& Sorter) { if (ColumnSortMode == EColumnSortMode::Type::Descending) { GroupNode.SortChildrenDescending(Sorter); } else // if (ColumnSortMode == EColumnSortMode::Type::Ascending) { GroupNode.SortChildrenAscending(Sorter); } for (FBaseTreeNodePtr ChildPtr: GroupNode.GetChildren()) { if (ChildPtr->IsGroup()) { SortTreeNodesRec(*StaticCastSharedPtr(ChildPtr), Sorter); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// EColumnSortMode::Type STableTreeView::GetSortModeForColumn(const FName ColumnId) const { if (ColumnBeingSorted != ColumnId) { return EColumnSortMode::None; } return ColumnSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::SetSortModeForColumn(const FName& ColumnId, const EColumnSortMode::Type SortMode) { ColumnBeingSorted = ColumnId; ColumnSortMode = SortMode; UpdateCurrentSortingByColumn(); SortTreeNodes(); ApplyFiltering(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::OnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type SortMode) { SetSortModeForColumn(ColumnId, SortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (HeaderMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::HeaderMenu_SortMode_IsChecked(const FName ColumnId, const EColumnSortMode::Type InSortMode) { return ColumnBeingSorted == ColumnId && ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::HeaderMenu_SortMode_CanExecute(const FName ColumnId, const EColumnSortMode::Type InSortMode) const { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeSorted(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::HeaderMenu_SortMode_Execute(const FName ColumnId, const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnId, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortMode action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_SortMode_IsChecked(const EColumnSortMode::Type InSortMode) { return ColumnSortMode == InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_SortMode_CanExecute(const EColumnSortMode::Type InSortMode) const { return true; // ColumnSortMode != InSortMode; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ContextMenu_SortMode_Execute(const EColumnSortMode::Type InSortMode) { SetSortModeForColumn(ColumnBeingSorted, InSortMode); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // SortByColumn action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_SortByColumn_IsChecked(const FName ColumnId) { return ColumnId == ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_SortByColumn_CanExecute(const FName ColumnId) const { return true; // ColumnId != ColumnBeingSorted; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ContextMenu_SortByColumn_Execute(const FName ColumnId) { SetSortModeForColumn(ColumnId, EColumnSortMode::Descending); TreeView_Refresh(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ShowColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::CanShowColumn(const FName ColumnId) const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ShowColumn(const FName ColumnId) { FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Show(); SHeaderRow::FColumn::FArguments ColumnArgs; ColumnArgs .ColumnId(Column.GetId()) .DefaultLabel(Column.GetShortName()) .HAlignHeader(HAlign_Fill) .VAlignHeader(VAlign_Fill) .HeaderContentPadding(FMargin(2.0f)) .HAlignCell(HAlign_Fill) .VAlignCell(VAlign_Fill) .SortMode(this, &STableTreeView::GetSortModeForColumn, Column.GetId()) .OnSort(this, &STableTreeView::OnSortModeChanged) .ManualWidth(Column.GetInitialWidth()) .FixedWidth(Column.IsFixedWidth() ? Column.GetInitialWidth() : TOptional()) .HeaderContent() [SNew(SBox) .ToolTip(STableTreeViewTooltip::GetColumnTooltip(Column)) .HAlign(Column.GetHorizontalAlignment()) .VAlign(VAlign_Center) [SNew(STextBlock) .Text(this, &STableTreeView::GetColumnHeaderText, Column.GetId())]] .MenuContent() [TreeViewHeaderRow_GenerateColumnMenu(Column)]; int32 ColumnIndex = 0; const int32 NewColumnPosition = Table->GetColumnPositionIndex(ColumnId); const int32 NumColumns = TreeViewHeaderRow->GetColumns().Num(); for (; ColumnIndex < NumColumns; ColumnIndex++) { const SHeaderRow::FColumn& CurrentColumn = TreeViewHeaderRow->GetColumns()[ColumnIndex]; const int32 CurrentColumnPosition = Table->GetColumnPositionIndex(CurrentColumn.ColumnId); if (NewColumnPosition < CurrentColumnPosition) { break; } } TreeViewHeaderRow->InsertColumn(ColumnArgs, ColumnIndex); } //////////////////////////////////////////////////////////////////////////////////////////////////// // HideColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::CanHideColumn(const FName ColumnId) const { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::HideColumn(const FName ColumnId) { FTableColumn& Column = *Table->FindColumnChecked(ColumnId); Column.Hide(); TreeViewHeaderRow->RemoveColumn(ColumnId); } //////////////////////////////////////////////////////////////////////////////////////////////////// // ToggleColumn action //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::IsColumnVisible(const FName ColumnId) { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return Column.IsVisible(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::CanToggleColumnVisibility(const FName ColumnId) const { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); return !Column.IsVisible() || Column.CanBeHidden(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ToggleColumnVisibility(const FName ColumnId) { const FTableColumn& Column = *Table->FindColumnChecked(ColumnId); if (Column.IsVisible()) { HideColumn(ColumnId); } else { ShowColumn(ColumnId); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // "Show All Columns" action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_ShowAllColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ContextMenu_ShowAllColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const FTableColumn& Column = *ColumnRef; if (!Column.IsVisible()) { ShowColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // ResetColumns action (ContextMenu) //////////////////////////////////////////////////////////////////////////////////////////////////// bool STableTreeView::ContextMenu_ResetColumns_CanExecute() const { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::ContextMenu_ResetColumns_Execute() { ColumnBeingSorted = GetDefaultColumnBeingSorted(); ColumnSortMode = GetDefaultColumnSortMode(); UpdateCurrentSortingByColumn(); for (const TSharedRef& ColumnRef: Table->GetColumns()) { const FTableColumn& Column = *ColumnRef; if (Column.ShouldBeVisible() && !Column.IsVisible()) { ShowColumn(Column.GetId()); } else if (!Column.ShouldBeVisible() && Column.IsVisible()) { HideColumn(Column.GetId()); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::Reset() { StatsStartTime = 0.0; StatsEndTime = 0.0; RebuildTree(true); } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::RebuildTree(bool bResync) { unimplemented(); } //////////////////////////////////////////////////////////////////////////////////////////////////// FTableTreeNodePtr STableTreeView::GetNodeByTableRowIndex(int32 RowIndex) const { return (RowIndex >= 0 && RowIndex < TableTreeNodes.Num()) ? TableTreeNodes[RowIndex] : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void STableTreeView::SelectNodeByTableRowIndex(int32 RowIndex) { if (RowIndex >= 0 && RowIndex < TableTreeNodes.Num()) { FTableTreeNodePtr NodePtr = TableTreeNodes[RowIndex]; if (ensure(NodePtr)) { TreeView->SetSelection(NodePtr); TreeView->RequestScrollIntoView(NodePtr); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace Insights #undef LOCTEXT_NAMESPACE