diff --git a/IoListTestingWindow.ClockSyncUx.cs b/IoListTestingWindow.ClockSyncUx.cs index 2ca060789..7b265d1a2 100644 --- a/IoListTestingWindow.ClockSyncUx.cs +++ b/IoListTestingWindow.ClockSyncUx.cs @@ -40,9 +40,9 @@ private void ClockSyncUx_Loaded(object sender, RoutedEventArgs e) Margin = new Thickness(0, 0, 8, 0), Padding = new Thickness(2, 0, 2, 0), FontSize = 11.2, - FontWeight = FontWeights.SemiBold, + FontWeight = FontWeights.Medium, Foreground = TryFindResource("Ink") as Brush ?? Brushes.DimGray, - Text = "Global SNTP", + Text = "SNTP · —", ToolTip = "Global SNTP is controlled from the ARSAS header. It is not owned by FAT and continues running when the FAT window closes while the global server toggle remains enabled." }; @@ -54,7 +54,7 @@ private void ClockSyncUx_Loaded(object sender, RoutedEventArgs e) FontSize = 10.4, FontWeight = FontWeights.Medium, Foreground = TryFindResource("MutedInk") as Brush ?? Brushes.SlateGray, - Text = "SNTP: waiting" + Text = "Clock · waiting" }; _clockSyncGlobalStatusText = globalStatus; @@ -104,25 +104,22 @@ private void RefreshClockSyncEvidence(SntpClockServiceSnapshot snapshot) SntpClockTransportMode.UdpSocket => "UDP", _ => "—" }; - var localAddress = snapshot.Binding?.LocalAddress.ToString(); - _clockSyncGlobalStatusText.Text = !enabled - ? "Global SNTP · Off" - : snapshot.State == SntpClockServiceState.Serving - ? $"Global SNTP · {localAddress ?? "Active"}" - : "Global SNTP · Enabled"; + _clockSyncGlobalStatusText.Text = enabled + ? "SNTP · ON" + : "SNTP · OFF"; _clockSyncEvidenceText.Text = !enabled - ? "SNTP: off" + ? "Clock · idle" : snapshot.State switch { SntpClockServiceState.Serving => - $"{transport} · B {snapshot.BroadcastCount} · Req {snapshot.ClientRequestCount} · Reply {snapshot.ReplyCount} · sync not proven", - SntpClockServiceState.Starting => "SNTP: starting...", - SntpClockServiceState.Stopped => "SNTP: waiting for connected IED", - SntpClockServiceState.PortUnavailable => "SNTP: unavailable", - SntpClockServiceState.Faulted => "SNTP: fault", - _ => $"SNTP: {snapshot.State}" + $"{transport} · B {snapshot.BroadcastCount} · Req {snapshot.ClientRequestCount} · Rep {snapshot.ReplyCount}", + SntpClockServiceState.Starting => "Clock · starting...", + SntpClockServiceState.Stopped => "Clock · waiting", + SntpClockServiceState.PortUnavailable => "Clock · unavailable", + SntpClockServiceState.Faulted => "Clock · fault", + _ => $"Clock · {snapshot.State}" }; var toolTip = BuildClockSyncEvidenceToolTip(snapshot, transport, enabled); @@ -164,4 +161,4 @@ private void ClockSyncUx_Closed(object? sender, EventArgs e) _clockSyncSnapshotOwner.ClockSyncSnapshotChanged -= ClockSyncSnapshotChanged; _clockSyncSnapshotOwner = null; } -} +} \ No newline at end of file diff --git a/IoListTestingWindow.P0BenchUx.cs b/IoListTestingWindow.P0BenchUx.cs index 04d69babc..788f8420c 100644 --- a/IoListTestingWindow.P0BenchUx.cs +++ b/IoListTestingWindow.P0BenchUx.cs @@ -58,6 +58,7 @@ private void ApplyP0BenchUx() { ConfigureP0StableFatColumns(); ConfigureP0AdaptiveHeaderActions(); + ConfigureP2CompactHeader(); } private void ConfigureP0StableFatColumns() @@ -206,4 +207,4 @@ public object Convert(object value, Type targetType, object parameter, CultureIn public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing; -} +} \ No newline at end of file diff --git a/IoListTestingWindow.P2CompactHeader.cs b/IoListTestingWindow.P2CompactHeader.cs new file mode 100644 index 000000000..9d899819c --- /dev/null +++ b/IoListTestingWindow.P2CompactHeader.cs @@ -0,0 +1,67 @@ +using System.Windows; +using System.Windows.Controls; + +namespace ArIED61850Tester; + +/// +/// P2 bench-facing header compaction. +/// +/// P0 already separates operational actions from evidence/status actions. P2 only tightens +/// that existing hierarchy so the FAT header remains readable on normal engineering laptop +/// widths. Full evidence detail stays in the existing tooltips; protocol, evidence and grid +/// behavior are intentionally untouched. +/// +public partial class IoListTestingWindow +{ + private void ConfigureP2CompactHeader() + { + if (_p0PrimaryHeaderActions == null || _p0SecondaryHeaderActions == null) + return; + + _p0PrimaryHeaderActions.Margin = new Thickness(0); + _p0SecondaryHeaderActions.Margin = new Thickness(0, 3, 0, 0); + + foreach (var button in _p0PrimaryHeaderActions.Children.OfType()) + ApplyP2CompactButtonMetrics(button, secondary: false); + + foreach (var button in _p0SecondaryHeaderActions.Children.OfType()) + ApplyP2CompactButtonMetrics(button, secondary: true); + + WorkspacePreviewToggle.Content = "Preview"; + if (_cleanSessionButton != null) + _cleanSessionButton.Content = "Clean FAT"; + + ApplyP2CompactStatusMetrics(_clockSyncGlobalStatusText, 118, FontWeights.Medium); + ApplyP2CompactStatusMetrics(_clockSyncEvidenceText, 188, FontWeights.Normal); + } + + private static void ApplyP2CompactButtonMetrics(Button button, bool secondary) + { + button.Padding = secondary + ? new Thickness(8, 5, 8, 5) + : new Thickness(9, 6, 9, 6); + button.Margin = new Thickness(0, 0, 5, 0); + button.MinWidth = 0; + button.MinHeight = secondary ? 27 : 29; + if (secondary) + { + button.FontSize = 10.4; + button.FontWeight = FontWeights.Medium; + } + } + + private static void ApplyP2CompactStatusMetrics( + TextBlock? text, + double maxWidth, + FontWeight fontWeight) + { + if (text == null) + return; + + text.MaxWidth = maxWidth; + text.TextWrapping = TextWrapping.NoWrap; + text.TextTrimming = TextTrimming.CharacterEllipsis; + text.FontWeight = fontWeight; + text.FontSize = 10.2; + } +} \ No newline at end of file diff --git a/IoListTestingWindow.SupplementalEvidence.cs b/IoListTestingWindow.SupplementalEvidence.cs index 0874287b5..49ed7d89c 100644 --- a/IoListTestingWindow.SupplementalEvidence.cs +++ b/IoListTestingWindow.SupplementalEvidence.cs @@ -36,13 +36,13 @@ private void InstallSupplementalEvidenceControls() if (_comtradeEvidenceButton != null || WorkspacePreviewToggle.Parent is not Panel actionPanel) return; - _timeSyncEvidenceButton = CreateEvidenceButton("Time Sync · —", RefreshTimeSyncEvidence_Click); + _timeSyncEvidenceButton = CreateEvidenceButton("Sync · —", RefreshTimeSyncEvidence_Click); _timeSyncEvidenceButton.ToolTip = "Capture IEC 61850 time-synchronization evidence for the selected IED"; _comtradeEvidenceButton = CreateEvidenceButton("COMTRADE · —", OpenComtradeEvidence_Click); _comtradeEvidenceButton.ToolTip = "Browse relay fault records. A remote COMTRADE listing is sufficient File Service FAT evidence; download is optional."; - _cleanSessionButton = CreateEvidenceButton("New Clean FAT", NewCleanFatSession_Click); + _cleanSessionButton = CreateEvidenceButton("Clean FAT", NewCleanFatSession_Click); _cleanSessionButton.ToolTip = "Archive all current FAT evidence and reset the project to a zero-evidence retest session"; var insertionIndex = actionPanel.Children.IndexOf(WorkspacePreviewToggle) + 1; @@ -57,9 +57,9 @@ private Button CreateEvidenceButton(string content, RoutedEventHandler handler) var button = new Button { Content = content, - Padding = new Thickness(10, 8, 10, 8), + Padding = new Thickness(8, 6, 8, 6), Margin = new Thickness(0, 0, 6, 0), - MinWidth = 92 + MinWidth = 0 }; if (TryFindResource("SoftButton") is Style style) button.Style = style; @@ -75,7 +75,7 @@ private void RefreshSupplementalEvidenceControls() var ied = SelectedIed; if (ied == null) { - _timeSyncEvidenceButton.Content = "Time Sync · —"; + _timeSyncEvidenceButton.Content = "Sync · —"; _timeSyncEvidenceButton.ToolTip = "Select an IED first"; _comtradeEvidenceButton.Content = "COMTRADE · —"; _comtradeEvidenceButton.ToolTip = "Select an IED first"; @@ -87,8 +87,8 @@ private void RefreshSupplementalEvidenceControls() ied.IedName, IoFatSupplementalEvidenceService.TimeSyncKind); _timeSyncEvidenceButton.Content = timeSync == null - ? "Time Sync · —" - : $"Time Sync · {timeSync.Verdict}"; + ? "Sync · —" + : $"Sync · {timeSync.Verdict}"; _timeSyncEvidenceButton.ToolTip = timeSync == null ? "No time-sync evidence captured yet. It will be captured automatically after FAT connection." : $"{timeSync.DisplayText}\n{timeSync.Reason}\nCaptured {timeSync.RecordedAtUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss}"; @@ -288,4 +288,4 @@ private static bool HasLiveValue(string? value) !text.Equals("Unknown", StringComparison.OrdinalIgnoreCase) && !text.Contains("not probed", StringComparison.OrdinalIgnoreCase); } -} +} \ No newline at end of file diff --git a/tests/ARSAS.Tests/IoFatP2CompactHeaderRegressionTests.cs b/tests/ARSAS.Tests/IoFatP2CompactHeaderRegressionTests.cs new file mode 100644 index 000000000..dd25879c0 --- /dev/null +++ b/tests/ARSAS.Tests/IoFatP2CompactHeaderRegressionTests.cs @@ -0,0 +1,82 @@ +namespace ARSAS.Tests; + +public sealed class IoFatP2CompactHeaderRegressionTests +{ + [Fact] + public void P2_ReusesP0PrimaryAndSecondaryHeaderHierarchy() + { + var p0 = File.ReadAllText(FindRepoFile("IoListTestingWindow.P0BenchUx.cs")); + + Assert.Contains("ConfigureP0AdaptiveHeaderActions();", p0, StringComparison.Ordinal); + Assert.Contains("ConfigureP2CompactHeader();", p0, StringComparison.Ordinal); + Assert.Contains("_p0PrimaryHeaderActions", p0, StringComparison.Ordinal); + Assert.Contains("_p0SecondaryHeaderActions", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, WorkspacePreviewToggle)", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, _timeSyncEvidenceButton)", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, _comtradeEvidenceButton)", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, _cleanSessionButton)", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, _clockSyncGlobalStatusText)", p0, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(element, _clockSyncEvidenceText)", p0, StringComparison.Ordinal); + } + + [Fact] + public void P2_CompactsSecondaryControlsWithoutHidingEvidenceDetail() + { + var header = File.ReadAllText(FindRepoFile("IoListTestingWindow.P2CompactHeader.cs")); + var clock = File.ReadAllText(FindRepoFile("IoListTestingWindow.ClockSyncUx.cs")); + var supplemental = File.ReadAllText(FindRepoFile("IoListTestingWindow.SupplementalEvidence.cs")); + + Assert.Contains("WorkspacePreviewToggle.Content = \"Preview\";", header, StringComparison.Ordinal); + Assert.Contains("_cleanSessionButton.Content = \"Clean FAT\";", header, StringComparison.Ordinal); + Assert.Contains("TextTrimming.CharacterEllipsis", header, StringComparison.Ordinal); + Assert.Contains("button.MinWidth = 0;", header, StringComparison.Ordinal); + + Assert.Contains("Text = \"SNTP · —\"", clock, StringComparison.Ordinal); + Assert.Contains("\"SNTP · ON\"", clock, StringComparison.Ordinal); + Assert.Contains("\"SNTP · OFF\"", clock, StringComparison.Ordinal); + Assert.Contains("Rep {snapshot.ReplyCount}", clock, StringComparison.Ordinal); + Assert.Contains("BuildClockSyncEvidenceToolTip", clock, StringComparison.Ordinal); + Assert.Contains("Binding: {binding}", clock, StringComparison.Ordinal); + Assert.Contains("Client request seen: {snapshot.ClientRequestCount}", clock, StringComparison.Ordinal); + Assert.Contains("Mode 4 reply sent: {snapshot.ReplyCount}", clock, StringComparison.Ordinal); + + Assert.Contains("CreateEvidenceButton(\"Sync · —\"", supplemental, StringComparison.Ordinal); + Assert.Contains("CreateEvidenceButton(\"Clean FAT\"", supplemental, StringComparison.Ordinal); + Assert.Contains("$\"Sync · {timeSync.Verdict}\"", supplemental, StringComparison.Ordinal); + Assert.Contains("timeSync.DisplayText", supplemental, StringComparison.Ordinal); + Assert.Contains("timeSync.Reason", supplemental, StringComparison.Ordinal); + Assert.Contains("ied.LatestComtradeRemotePath", supplemental, StringComparison.Ordinal); + } + + [Fact] + public void P2_IsPresentationOnlyAndDoesNotTouchFatProtocolEvidenceOrGridSchema() + { + var header = File.ReadAllText(FindRepoFile("IoListTestingWindow.P2CompactHeader.cs")); + + Assert.DoesNotContain("SetVirtualizationMode", header, StringComparison.Ordinal); + Assert.DoesNotContain("SetIsVirtualizing", header, StringComparison.Ordinal); + Assert.DoesNotContain("ReadObject", header, StringComparison.Ordinal); + Assert.DoesNotContain("ReadAsync", header, StringComparison.Ordinal); + Assert.DoesNotContain("CaptureCurrentEvidence", header, StringComparison.Ordinal); + Assert.DoesNotContain("ScheduleSave", header, StringComparison.Ordinal); + Assert.DoesNotContain("ARIEC61850", header, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("grid.Columns", header, StringComparison.Ordinal); + Assert.DoesNotContain("ON RELAY TIME", header, StringComparison.Ordinal); + Assert.DoesNotContain("OFF RELAY TIME", header, StringComparison.Ordinal); + } + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException( + $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +} \ No newline at end of file AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル
AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル