private/output/New-WarningsTabFull.ps1
|
function New-WarningsTabFull { <# .SYNOPSIS Creates the Warnings tab with toolbar and text search. #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$Colors ) $warningsTab = [System.Windows.Controls.TabItem]@{ Header = "Warnings" Visibility = 'Collapsed' } Set-TabItemStyle -TabItem $warningsTab $warningsContainer = [System.Windows.Controls.DockPanel]::new() $warningsToolbar = [System.Windows.Controls.DockPanel]@{ Margin = [System.Windows.Thickness]::new(4, 4, 4, 4) Height = 32 } [System.Windows.Controls.DockPanel]::SetDock($warningsToolbar, [System.Windows.Controls.Dock]::Top) $leftToolbarPanel = [System.Windows.Controls.StackPanel]@{ Orientation = 'Horizontal' VerticalAlignment = 'Center' } [System.Windows.Controls.DockPanel]::SetDock($leftToolbarPanel, 'Left') $copyAllButton = [System.Windows.Controls.Button]@{ Content = 'Copy All' Padding = [System.Windows.Thickness]::new(8, 2, 8, 2) Margin = [System.Windows.Thickness]::new(0, 0, 4, 0) VerticalAlignment = 'Center' } Set-ButtonStyle -Button $copyAllButton [void]$leftToolbarPanel.Children.Add($copyAllButton) $saveButton = [System.Windows.Controls.Button]@{ Content = 'Save' Padding = [System.Windows.Thickness]::new(8, 2, 8, 2) Margin = [System.Windows.Thickness]::new(0, 0, 4, 0) VerticalAlignment = 'Center' } Set-ButtonStyle -Button $saveButton [void]$leftToolbarPanel.Children.Add($saveButton) $clearButton = [System.Windows.Controls.Button]@{ Content = 'Clear' Padding = [System.Windows.Thickness]::new(8, 2, 8, 2) Margin = [System.Windows.Thickness]::new(0, 0, 8, 0) VerticalAlignment = 'Center' } Set-ButtonStyle -Button $clearButton [void]$leftToolbarPanel.Children.Add($clearButton) $wrapCheckbox = [System.Windows.Controls.CheckBox]@{ Content = 'Wrap' IsChecked = $true Margin = [System.Windows.Thickness]::new(0, 0, 0, 0) VerticalAlignment = 'Center' VerticalContentAlignment = 'Center' ToolTip = 'Toggle word wrapping' } Set-CheckBoxStyle -CheckBox $wrapCheckbox [void]$leftToolbarPanel.Children.Add($wrapCheckbox) [void]$warningsToolbar.Children.Add($leftToolbarPanel) $findPanel = [System.Windows.Controls.StackPanel]@{ Orientation = 'Horizontal' HorizontalAlignment = 'Right' VerticalAlignment = 'Center' } [System.Windows.Controls.DockPanel]::SetDock($findPanel, 'Right') $findMatchLabel = [System.Windows.Controls.TextBlock]@{ Text = '' FontSize = 11 VerticalAlignment = 'Center' Foreground = ConvertTo-UiBrush $Colors.SecondaryText Margin = [System.Windows.Thickness]::new(0, 0, 10, 0) MinWidth = 50 TextAlignment = 'Right' Visibility = 'Collapsed' } [void]$findPanel.Children.Add($findMatchLabel) $findIcon = [System.Windows.Controls.TextBlock]@{ Text = [PsUi.ModuleContext]::GetIcon('Search') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 14 VerticalAlignment = 'Center' Foreground = ConvertTo-UiBrush $Colors.ControlFg Margin = [System.Windows.Thickness]::new(0, 0, 6, 0) } [void]$findPanel.Children.Add($findIcon) # Create filter box with clear button $findBoxResult = New-FilterBoxWithClear -Width 150 -Height 24 $findBoxContainer = $findBoxResult.Container $warningsFindBox = $findBoxResult.TextBox [void]$findPanel.Children.Add($findBoxContainer) $findPrevBtn = [System.Windows.Controls.Button]@{ Content = [PsUi.ModuleContext]::GetIcon('ChevronUp') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') Width = 24 Height = 24 Padding = [System.Windows.Thickness]::new(0) Margin = [System.Windows.Thickness]::new(4, 0, 0, 0) ToolTip = 'Previous match' VerticalAlignment = 'Center' } Set-ButtonStyle -Button $findPrevBtn [void]$findPanel.Children.Add($findPrevBtn) $findNextBtn = [System.Windows.Controls.Button]@{ Content = [PsUi.ModuleContext]::GetIcon('ChevronDown') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') Width = 24 Height = 24 Padding = [System.Windows.Thickness]::new(0) Margin = [System.Windows.Thickness]::new(2, 0, 0, 0) ToolTip = 'Next match' VerticalAlignment = 'Center' } Set-ButtonStyle -Button $findNextBtn [void]$findPanel.Children.Add($findNextBtn) [void]$warningsToolbar.Children.Add($findPanel) [void]$warningsContainer.Children.Add($warningsToolbar) $warningColor = if ($Colors.WarningText) { $Colors.WarningText } else { '#FFC107' } $highlightColor = if ($Colors.TextHighlight) { $Colors.TextHighlight } else { $Colors.Selection } $highlightBrush = if ($Colors.FindHighlight) { ConvertTo-UiBrush $Colors.FindHighlight } else { [System.Windows.Media.Brushes]::Gold } $currentBrush = if ($Colors.Accent) { ConvertTo-UiBrush $Colors.Accent } else { [System.Windows.Media.Brushes]::Orange } # Warnings RichTextBox - styled like Console tab for multi-match highlighting $warningsTextBox = [System.Windows.Controls.RichTextBox]@{ IsReadOnly = $true FontFamily = [System.Windows.Media.FontFamily]::new('Cascadia Code, Cascadia Mono, Consolas, Courier New') VerticalScrollBarVisibility = 'Auto' HorizontalScrollBarVisibility = 'Disabled' Background = ConvertTo-UiBrush $Colors.ControlBg Foreground = ConvertTo-UiBrush $warningColor BorderBrush = ConvertTo-UiBrush $Colors.Border BorderThickness = [System.Windows.Thickness]::new(0) Padding = [System.Windows.Thickness]::new(8, 4, 8, 4) SelectionBrush = ConvertTo-UiBrush $highlightColor } [void]$warningsContainer.Children.Add($warningsTextBox) # Create FlowDocument with matching style $warningsDocument = [System.Windows.Documents.FlowDocument]@{ FontFamily = [System.Windows.Media.FontFamily]::new('Cascadia Code, Cascadia Mono, Consolas, Courier New') FontSize = 12 Foreground = ConvertTo-UiBrush $warningColor Background = ConvertTo-UiBrush $Colors.ControlBg PagePadding = [System.Windows.Thickness]::new(0) } $warningsParagraph = [System.Windows.Documents.Paragraph]@{ Margin = [System.Windows.Thickness]::new(0) LineHeight = 16 LineStackingStrategy = [System.Windows.LineStackingStrategy]::BlockLineHeight } [void]$warningsDocument.Blocks.Add($warningsParagraph) $warningsTextBox.Document = $warningsDocument $warningsTab.Content = $warningsContainer $wrapCheckbox.Tag = $warningsTextBox $wrapCheckbox.Add_Checked({ $this.Tag.Document.PageWidth = [double]::NaN $this.Tag.HorizontalScrollBarVisibility = 'Disabled' }.GetNewClosure()) $wrapCheckbox.Add_Unchecked({ $this.Tag.Document.PageWidth = 10000 $this.Tag.HorizontalScrollBarVisibility = 'Auto' }.GetNewClosure()) $debounceTimer = [System.Windows.Threading.DispatcherTimer]::new() $debounceTimer.Interval = [TimeSpan]::FromMilliseconds(300) # Merge clear button and watermark references into the Tag $originalTag = $warningsFindBox.Tag $findState = @{ Paragraph = $warningsParagraph TextBox = $warningsTextBox Label = $findMatchLabel HighlightBrush = $highlightBrush CurrentBrush = $currentBrush ResetBrush = [System.Windows.Media.Brushes]::Transparent Matches = [System.Collections.Generic.List[object]]::new() Index = -1 Timer = $debounceTimer FindBox = $warningsFindBox ClearButton = $originalTag.ClearButton Watermark = $originalTag.Watermark } $warningsFindBox.Tag = $findState $findPrevBtn.Tag = $warningsFindBox $findNextBtn.Tag = $warningsFindBox $debounceTimer.Tag = $warningsFindBox $doSearch = { param($findBox) $state = $findBox.Tag $paragraph = $state.Paragraph $term = $findBox.Text foreach ($prevRange in $state.Matches) { try { $prevRange.ApplyPropertyValue([System.Windows.Documents.TextElement]::BackgroundProperty, $state.ResetBrush) } catch { Write-Debug "Suppressed highlight reset error: $_" } } $state.Matches.Clear() $state.Index = -1 if ([string]::IsNullOrEmpty($term)) { $state.Label.Text = '' $state.Label.Visibility = 'Collapsed' return } # Build concatenated text from all Runs with position mapping $textBuilder = [System.Text.StringBuilder]::new() $runMap = [System.Collections.Generic.List[object]]::new() $inlinesCopy = @($paragraph.Inlines) foreach ($inline in $inlinesCopy) { if ($inline -isnot [System.Windows.Documents.Run]) { continue } $runText = $inline.Text if ([string]::IsNullOrEmpty($runText)) { continue } $startPos = $textBuilder.Length [void]$textBuilder.Append($runText) [void]$runMap.Add(@{ Run = $inline; Start = $startPos; Length = $runText.Length }) } $fullText = $textBuilder.ToString() $termLen = $term.Length # Buffer ranges to highlight $rangesToHighlight = [System.Collections.Generic.List[object]]::new() $maxMatches = 500 # Search in full text $offset = 0 :searchLoop while (($ix = $fullText.IndexOf($term, $offset, [StringComparison]::OrdinalIgnoreCase)) -ge 0) { $matchEnd = $ix + $termLen # Find which Run(s) this match spans foreach ($entry in $runMap) { $runStart = $entry.Start $runEnd = $runStart + $entry.Length $run = $entry.Run if ($matchEnd -le $runStart) { continue } if ($ix -ge $runEnd) { continue } $localStart = [Math]::Max(0, $ix - $runStart) $localEnd = [Math]::Min($entry.Length, $matchEnd - $runStart) $ptrStart = $run.ContentStart.GetPositionAtOffset($localStart, [System.Windows.Documents.LogicalDirection]::Forward) $ptrEnd = $run.ContentStart.GetPositionAtOffset($localEnd, [System.Windows.Documents.LogicalDirection]::Forward) if ($ptrStart -and $ptrEnd) { $range = [System.Windows.Documents.TextRange]::new($ptrStart, $ptrEnd) [void]$rangesToHighlight.Add($range) } } if ($rangesToHighlight.Count -ge $maxMatches) { break searchLoop } $offset = $ix + $termLen } # Apply highlights to all matches foreach ($range in $rangesToHighlight) { $range.ApplyPropertyValue([System.Windows.Documents.TextElement]::BackgroundProperty, $state.HighlightBrush) } # Store matches and update UI $state.Matches = $rangesToHighlight if ($rangesToHighlight.Count -gt 0) { $state.Index = 0 # Highlight current match with different color $rangesToHighlight[0].ApplyPropertyValue([System.Windows.Documents.TextElement]::BackgroundProperty, $state.CurrentBrush) $state.Label.Visibility = 'Visible' $state.Label.Text = "1 of $($rangesToHighlight.Count)" # Scroll to first match try { $rect = $rangesToHighlight[0].Start.GetCharacterRect([System.Windows.Documents.LogicalDirection]::Forward) $state.TextBox.ScrollToVerticalOffset($state.TextBox.VerticalOffset + $rect.Top - 100) } catch { Write-Debug "Suppressed scroll to match error: $_" } } else { $state.Label.Visibility = 'Visible' $state.Label.Text = 'No matches' } } # Timer tick handler $debounceTimer.Add_Tick({ $this.Stop() $findBox = $this.Tag & $doSearch $findBox }.GetNewClosure()) # Text changed handler with debounce $warningsFindBox.Add_TextChanged({ $state = $this.Tag # Show/hide clear button and watermark $isEmpty = [string]::IsNullOrEmpty($this.Text) if ($state.ClearButton) { $state.ClearButton.Visibility = if ($isEmpty) { 'Collapsed' } else { 'Visible' } } if ($state.Watermark) { $state.Watermark.Visibility = if ($isEmpty) { 'Visible' } else { 'Collapsed' } } # Restart debounce timer $state.Timer.Stop() $state.Timer.Start() }.GetNewClosure()) # Navigation handlers with proper highlight swapping $navigateMatch = { param($direction, $findBox) $state = $findBox.Tag if ($state.Matches.Count -eq 0) { return } # Reset current match to normal highlight $state.Matches[$state.Index].ApplyPropertyValue([System.Windows.Documents.TextElement]::BackgroundProperty, $state.HighlightBrush) $newIndex = $state.Index + $direction if ($newIndex -lt 0) { $newIndex = $state.Matches.Count - 1 } if ($newIndex -ge $state.Matches.Count) { $newIndex = 0 } $state.Index = $newIndex $state.Label.Text = "$($newIndex + 1) of $($state.Matches.Count)" # Highlight new current match $state.Matches[$newIndex].ApplyPropertyValue([System.Windows.Documents.TextElement]::BackgroundProperty, $state.CurrentBrush) # Scroll to match try { $rect = $state.Matches[$newIndex].Start.GetCharacterRect([System.Windows.Documents.LogicalDirection]::Forward) $state.TextBox.ScrollToVerticalOffset($state.TextBox.VerticalOffset + $rect.Top - 100) } catch { Write-Debug "Suppressed scroll to match navigation error: $_" } } $findPrevBtn.Add_Click({ & $navigateMatch -1 $this.Tag }.GetNewClosure()) $findNextBtn.Add_Click({ & $navigateMatch 1 $this.Tag }.GetNewClosure()) # Copy All handler with "Copied!" feedback $copyAllButton.Tag = @{ TextBox = $warningsTextBox; Document = $warningsDocument; Colors = $Colors } $copyAllButton.Add_Click({ $textRange = [System.Windows.Documents.TextRange]::new($this.Tag.Document.ContentStart, $this.Tag.Document.ContentEnd) $text = $textRange.Text if (![string]::IsNullOrWhiteSpace($text)) { [System.Windows.Clipboard]::SetText($text) $this.Content = 'Copied!' $originalBg = $this.Background $this.Background = ConvertTo-UiBrush $this.Tag.Colors.Accent $timer = [System.Windows.Threading.DispatcherTimer]::new() $timer.Interval = [TimeSpan]::FromMilliseconds(1500) $timer.Tag = @{ Btn = $this; Bg = $originalBg } $timer.Add_Tick({ $this.Tag.Btn.Background = $this.Tag.Bg; $this.Tag.Btn.Content = 'Copy All'; $this.Stop() }) $timer.Start() } }.GetNewClosure()) # Save handler $saveButton.Tag = $warningsDocument $saveButton.Add_Click({ $textRange = [System.Windows.Documents.TextRange]::new($this.Tag.ContentStart, $this.Tag.ContentEnd) $text = $textRange.Text if (![string]::IsNullOrWhiteSpace($text)) { $dialog = [Microsoft.Win32.SaveFileDialog]@{ Filter = 'Text files (*.txt)|*.txt|All files (*.*)|*.*' FileName = 'warnings.txt' } if ($dialog.ShowDialog()) { [System.IO.File]::WriteAllText($dialog.FileName, $text) } } }.GetNewClosure()) # Clear handler $clearButton.Tag = $warningsParagraph $clearButton.Add_Click({ $this.Tag.Inlines.Clear() }.GetNewClosure()) return @{ Tab = $warningsTab TextBox = $warningsTextBox Document = $warningsDocument Paragraph = $warningsParagraph CopyButton = $copyAllButton SaveButton = $saveButton ClearButton = $clearButton WrapCheckbox = $wrapCheckbox FindBox = $warningsFindBox FindState = $findState } } |