-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMainWindow.FirstRunLauncherRepair.cs
More file actions
183 lines (160 loc) · 7.28 KB
/
Copy pathMainWindow.FirstRunLauncherRepair.cs
File metadata and controls
183 lines (160 loc) · 7.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace ArIED61850Tester;
/// <summary>
/// Keeps the original first-run engineering/FAT launcher contract intact when a
/// decorative workstation overlay is installed before the Loaded-priority launcher.
/// The launcher is operational UI, so visual adapters must never make it disappear.
/// </summary>
public partial class MainWindow
{
private static readonly bool FirstRunLauncherRepairRegistered = RegisterFirstRunLauncherRepair();
private static bool RegisterFirstRunLauncherRepair()
{
EventManager.RegisterClassHandler(
typeof(MainWindow),
FrameworkElement.LoadedEvent,
new RoutedEventHandler(FirstRunLauncherRepair_Loaded));
return true;
}
private static void FirstRunLauncherRepair_Loaded(object sender, RoutedEventArgs e)
{
if (sender is not MainWindow window)
return;
// MainWindow.IoTesting queued InstallFirstRunTestingChoices at Loaded priority
// during OnInitialized. Remove the decorative Border synchronously while handling
// Loaded, before that queued callback can execute SingleOrDefault over hero Borders.
// This prevents the startup exception instead of merely repairing the launcher
// after the exception was swallowed by the dispatcher error handler.
window.RemoveFirstRunHeroTintBeforeLauncherDiscovery();
// Run the compatibility audit after the normal launcher attempt. At this point it
// only restores actions/Z-order or retries if some unrelated condition prevented
// the normal launcher from materializing.
window.Dispatcher.BeginInvoke(
new Action(window.RestoreFirstRunLauncherContract),
DispatcherPriority.ContextIdle);
}
private void RemoveFirstRunHeroTintBeforeLauncherDiscovery()
{
var heroGrid = FindFirstRunHeroGrid();
var tint = heroGrid?.Children
.OfType<Border>()
.FirstOrDefault(border => Equals(border.Tag, "P2IndustrialHeroTint"));
if (heroGrid != null && tint != null)
heroGrid.Children.Remove(tint);
}
private void RestoreFirstRunLauncherContract()
{
if (_ioListTestingLauncherCard == null)
{
// Defensive retry: remove a tint that may have been re-applied between Loaded
// and ContextIdle, then invoke the legacy launcher against one operational card.
RemoveFirstRunHeroTintBeforeLauncherDiscovery();
InstallFirstRunTestingChoices();
if (_ioListTestingLauncherCard == null)
return;
// Re-apply the visual adapter only after the operational cards exist. It can
// now tint the hero and assign Z-order without changing launcher discovery.
P2IndustrialWorkstationUx.Apply(this);
}
RestoreFirstRunLauncherActions();
}
private Grid? FindFirstRunHeroGrid()
{
if (MainTabs.Items.Count == 0 ||
MainTabs.Items[0] is not TabItem explorerTab ||
explorerTab.Content is not Grid explorerGrid)
{
return null;
}
var workspace = explorerGrid.Children
.OfType<Grid>()
.FirstOrDefault(child => Grid.GetColumn(child) == 2);
var emptyState = workspace?.Children
.OfType<Border>()
.FirstOrDefault(border =>
System.Windows.Data.BindingOperations.GetBinding(border, UIElement.VisibilityProperty)?.Path?.Path == nameof(EmptyExplorerVisibility));
return emptyState?.Child as Grid;
}
private void RestoreFirstRunLauncherActions()
{
if (_ioListTestingLauncherCard?.Parent is not WrapPanel chooser)
return;
Panel.SetZIndex(chooser, 2);
var generalCard = chooser.Children
.OfType<Border>()
.FirstOrDefault(card => !ReferenceEquals(card, _ioListTestingLauncherCard));
if (generalCard?.Child is StackPanel generalContent)
{
var title = generalContent.Children
.OfType<TextBlock>()
.FirstOrDefault(text => text.Text.Contains("Add an IED", StringComparison.OrdinalIgnoreCase));
var description = generalContent.Children
.OfType<TextBlock>()
.FirstOrDefault(text => text.Text.Contains("Connect a relay", StringComparison.OrdinalIgnoreCase));
var actions = generalContent.Children.OfType<WrapPanel>().FirstOrDefault();
if (title != null)
title.Text = "Add IED by SCL or IP address";
if (description != null)
{
description.Text =
"Import IEC 61850 endpoints from SCD, CID, ICD, IID or SSD, or enter an IP address directly, then verify the live model before testing.";
}
if (actions != null)
{
actions.Children.Clear();
actions.Children.Add(CreateLauncherButton(
"Open SCL",
"LucideFileInput",
"SoftButton",
OpenScl_Click,
null,
new Thickness(0, 0, 8, 0)));
actions.Children.Add(CreateLauncherButton(
"Add IED by IP",
"LucidePlus",
"PrimaryButton",
AddRelay_Click,
Brushes.White,
new Thickness(0, 0, 8, 0)));
actions.Children.Add(CreateLauncherButton(
"Open Project",
"LucideFolderOpen",
"SoftButton",
OpenProject_Click,
null,
new Thickness(0)));
}
}
// Keep the FAT card's existing import handler and icon, but restore the explicit
// Excel affordance that operators used to recognize immediately.
var importButton = DescendantButtons(_ioListTestingLauncherCard)
.FirstOrDefault(button =>
GetLauncherButtonText(button).Equals("Open IO List Workbook", StringComparison.OrdinalIgnoreCase));
if (importButton != null && importButton.Content is StackPanel importContent)
{
var label = importContent.Children.OfType<TextBlock>().FirstOrDefault();
if (label != null)
label.Text = "Import Excel IO List";
importButton.ToolTip = "Import the ARSAS IO List FAT Excel workbook (.xlsx)";
}
}
private static IEnumerable<Button> DescendantButtons(DependencyObject root)
{
var childCount = VisualTreeHelper.GetChildrenCount(root);
for (var index = 0; index < childCount; index++)
{
var child = VisualTreeHelper.GetChild(root, index);
if (child is Button button)
yield return button;
foreach (var descendant in DescendantButtons(child))
yield return descendant;
}
}
private static string GetLauncherButtonText(Button button)
=> button.Content is StackPanel panel
? panel.Children.OfType<TextBlock>().FirstOrDefault()?.Text ?? string.Empty
: button.Content?.ToString() ?? string.Empty;
}