Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1bdd40d
feat(fat): add native FAT persistent state model
masarray Sep 9, 2026
aa09d19
feat(fat): add tolerant per-IED JSON state store
masarray Sep 9, 2026
b4a7cbb
refactor(fat): bind native FAT rows to Explorer signal authority
masarray Sep 9, 2026
69e278b
refactor(fat): reconcile against canonical Explorer signal scope
masarray Sep 9, 2026
2d5a962
feat(fat): add native continuous FAT workspace tab
masarray Sep 9, 2026
aecc453
Harden native FAT state identity and legacy migration
masarray Sep 9, 2026
19505db
Add immutable native FAT report snapshot models
masarray Sep 9, 2026
2c84084
Build deterministic native FAT report snapshots
masarray Sep 9, 2026
e365da2
Cover native FAT continuity and report snapshot regressions
masarray Sep 9, 2026
88c7cbd
Drive native FAT report preview from immutable evidence snapshots
masarray Sep 9, 2026
3bfc3ea
Preserve unreadable native FAT evidence files
masarray Sep 9, 2026
628d83d
Verify damaged native FAT files remain recoverable
masarray Sep 9, 2026
babd994
Flush native FAT evidence before application shutdown
masarray Sep 9, 2026
b2b412a
Add native FAT PDF evidence export
masarray Sep 9, 2026
42dafa5
Wire native FAT PDF export into engineering workspace
masarray Sep 9, 2026
8fd1c8e
Cover native FAT PDF evidence export
masarray Sep 9, 2026
8b7af6a
Harden FAT persistence across IED switches and shutdown
masarray Sep 9, 2026
eec5507
Keep native FAT scope synchronized with Explorer signal selection
masarray Sep 9, 2026
2c3eac3
Add auditable native FAT history inspector
masarray Sep 9, 2026
7dfec8b
Cover Explorer selection continuity in native FAT regression tests
masarray Sep 9, 2026
ea736d0
Pivot FAT tab to production workspace host
masarray Sep 9, 2026
4272309
Embed production FAT workspace in Engineering tab
masarray Sep 9, 2026
624b4fe
Fix production FAT dock state assignment
masarray Sep 9, 2026
537cad3
Merge current main into production FAT pivot
masarray Sep 9, 2026
11b6c8b
FAT: project Engineering static DataSet into production workspace
masarray Sep 9, 2026
cdc9052
FAT: adopt Engineering-owned SCL runtime workspaces
masarray Sep 9, 2026
c12c4c3
FAT: auto bootstrap from Engineering static DataSet
masarray Sep 9, 2026
d8fdb62
FAT: align seventh tab with Engineering nav language
masarray Sep 9, 2026
088066b
FAT: resolve all seven nav buttons by visual identity
masarray Sep 9, 2026
fc6b351
test: guard Engineering-owned FAT tab bootstrap and nav parity
masarray Sep 9, 2026
6df6eae
Fix FAT static scope duplicate manual overlays
masarray Sep 9, 2026
1edad87
Keep automatic FAT bootstrap on static DataSet authority
masarray Sep 9, 2026
27935b1
Keep Engineering visible during FAT bootstrap
masarray Sep 9, 2026
4dfc834
Add FAT P0 field regression coverage
masarray Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions IoListTestingWindow.EmbeddedEngineeringHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using ArIED61850Tester.Models;

namespace ArIED61850Tester;

/// <summary>
/// Hosts the proven IoListTestingWindow production workspace inside MainWindow's FAT tab.
/// The production Window remains loaded-but-hidden so its existing session controller,
/// auto-capture lifecycle, persistence and report-preview code stay the single FAT authority.
/// Only its central workspace + FAT status footer are re-parented into Engineering.
/// </summary>
public partial class IoListTestingWindow
{
private bool _engineeringEmbeddedMountQueued;
private bool _engineeringEmbeddedMounted;
private FrameworkElement? _engineeringEmbeddedSurface;

[ModuleInitializer]
internal static void RegisterEmbeddedEngineeringFatHost()
{
EventManager.RegisterClassHandler(
typeof(IoListTestingWindow),
FrameworkElement.LoadedEvent,
new RoutedEventHandler(EmbeddedEngineeringFatHost_Loaded),
handledEventsToo: true);
}

private static void EmbeddedEngineeringFatHost_Loaded(object sender, RoutedEventArgs e)
{
if (sender is not IoListTestingWindow window ||
!ReferenceEquals(e.OriginalSource, window) ||
window._engineeringEmbeddedMounted ||
window._engineeringEmbeddedMountQueued ||
window.Owner is not MainWindow owner ||
!owner.ProductionFatTabReady)
{
return;
}

window._engineeringEmbeddedMountQueued = true;

// MainWindow's legacy launcher still calls Show() on this Window. Make that bootstrap
// surface invisible immediately; the actual production visual is moved into Engineering
// on the next Loaded-priority dispatcher turn, before ContextIdle command-panel work.
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0d;

window.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => window.TryMountIntoEngineering(owner)));
}

private void TryMountIntoEngineering(MainWindow owner)
{
_engineeringEmbeddedMountQueued = false;
if (_engineeringEmbeddedMounted || !IsLoaded || !ReferenceEquals(Owner, owner) || !owner.ProductionFatTabReady)
return;

try
{
EnsureProductionFatPresentationForEmbeddedHost();
DisableLegacyEmbeddedCommandPanel();
var surface = DetachProductionFatCentralWorkspace();
if (surface == null)
return;

_engineeringEmbeddedSurface = surface;
_engineeringEmbeddedMounted = owner.MountProductionFatWorkspace(this, surface);
if (!_engineeringEmbeddedMounted)
return;

// The central FAT view now belongs to MainWindow. Keep this Window loaded and
// hidden because existing controller/session/event code is intentionally reused.
Hide();
}
catch (Exception ex)
{
Opacity = 1d;
ShowInTaskbar = true;
ShowActivated = true;
MessageBox.Show(
owner,
$"ARSAS could not embed the production FAT workspace. The standalone FAT window will remain available.\n\n{ex.Message}",
"FAT workspace host",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}

private void EnsureProductionFatPresentationForEmbeddedHost()
{
// P1 normally installs this during Loaded. Calling it explicitly is safe/idempotent
// and guarantees the first embedded frame is the same V2 FAT grid as the old Window.
InstallFatV2WorkspaceUx();

// PrintPreview historically installs from OnContentRendered. The bootstrap Window is
// intentionally transparent, so install the exact same production preview explicitly
// before the central workspace is detached. This preserves full-center mode switching.
if (!_printPreviewInstalled)
{
InstallPerIedPrintPreview();
PropertyChanged += PrintPreviewWindow_PropertyChanged;
Session.PropertyChanged += PrintPreviewSession_PropertyChanged;
Closed += PrintPreviewWindow_Closed;
_printPreviewInstalled = true;
}

// The embedded center already declares WorkspacePreviewToggle in XAML. Make that
// visible button the production toggle authority instead of the hidden Window header.
if (WorkspacePreviewToggle != null)
_printPreviewToggle = WorkspacePreviewToggle;
}

private void DisableLegacyEmbeddedCommandPanel()
{
// Engineering already owns one shared Command Dock. Prevent the old FAT window's
// duplicate command panel from being created/refreshed while embedded. A non-null
// sentinel makes the queued legacy installer return immediately; null row/summary
// references make its queued refresh a no-op. No command runtime semantics change.
DetachFatCommandDevice();
if (_fatCommandPanelShell?.Parent is Grid existingHost)
{
var row = Grid.GetRow(_fatCommandPanelShell);
existingHost.Children.Remove(_fatCommandPanelShell);
if (row >= 0 && row < existingHost.RowDefinitions.Count)
existingHost.RowDefinitions[row].Height = new GridLength(0);
if (row - 1 >= 0 && row - 1 < existingHost.RowDefinitions.Count)
existingHost.RowDefinitions[row - 1].Height = new GridLength(0);
}

_fatCommandPanelShell ??= new Border { Visibility = Visibility.Collapsed };
_fatCommandRows = null;
_fatCommandSummary = null;
_fatCommandEmptyState = null;
}

private FrameworkElement? DetachProductionFatCentralWorkspace()
{
if (Content is not Grid root)
return null;

var middle = root.Children
.OfType<Grid>()
.FirstOrDefault(child => Grid.GetRow(child) == 2);
var workspaceBorder = middle?.Children
.OfType<Border>()
.FirstOrDefault(child => Grid.GetColumn(child) == 2);
if (middle == null || workspaceBorder == null)
return null;

var footer = root.Children
.OfType<Border>()
.FirstOrDefault(child => Grid.GetRow(child) == 4);

middle.Children.Remove(workspaceBorder);
if (footer != null)
root.Children.Remove(footer);

var host = new Grid
{
DataContext = this,
Margin = new Thickness(0)
};
host.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
if (footer != null)
{
host.RowDefinitions.Add(new RowDefinition { Height = new GridLength(8) });
host.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
}

workspaceBorder.Margin = new Thickness(0);
Grid.SetRow(workspaceBorder, 0);
Grid.SetColumn(workspaceBorder, 0);
host.Children.Add(workspaceBorder);

if (footer != null)
{
footer.Margin = new Thickness(0);
Grid.SetRow(footer, 2);
Grid.SetColumn(footer, 0);
host.Children.Add(footer);
}

return host;
}

internal void SelectEngineeringDeviceForEmbeddedFat(Iec61850MonitorDevice? device)
{
if (!_engineeringEmbeddedMounted || device == null)
return;

var match = Project.Ieds.FirstOrDefault(ied =>
(!string.IsNullOrWhiteSpace(ied.LiveDeviceId) &&
ied.LiveDeviceId.Equals(device.DeviceId, StringComparison.OrdinalIgnoreCase)) ||
(!string.IsNullOrWhiteSpace(ied.IpAddress) &&
ied.IpAddress.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase)) ||
ied.IedName.Equals(device.SclIedName, StringComparison.OrdinalIgnoreCase) ||
ied.IedName.Equals(device.Name, StringComparison.OrdinalIgnoreCase));
if (match == null || ReferenceEquals(SelectedIed, match))
return;

// Do not retarget an active production FAT transaction/session. When idle, the
// persistent Engineering IED Explorer is the navigation/selection authority.
if (!CanSelectIed)
return;

SelectedIed = match;
}

internal void NotifyEmbeddedHostActivated()
{
if (!_engineeringEmbeddedMounted)
return;

RefreshFatV2WorkspaceUx(refreshRows: true);
if (_printPreviewActive)
RefreshPrintPreview();
}

private void EmbeddedEngineeringFatHost_Closed(object? sender, EventArgs e)
{
if (Owner is MainWindow owner)
owner.UnmountProductionFatWorkspace(this);
Closed -= EmbeddedEngineeringFatHost_Closed;
_engineeringEmbeddedMounted = false;
_engineeringEmbeddedSurface = null;
}

// Field initializer cannot attach an instance event. Hook cleanup once the embedded
// surface has actually been mounted; this helper is called from the production owner.
internal void RegisterEmbeddedHostCloseCleanup()
{
Closed -= EmbeddedEngineeringFatHost_Closed;
Closed += EmbeddedEngineeringFatHost_Closed;
}
}
Loading
Loading