Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 27 additions & 33 deletions IpConnectWizardWindow.xaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Window x:Class="ArIED61850Tester.IpConnectWizardWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Add IEC 61850 IED" Width="480" MinWidth="460" MaxHeight="390"
SizeToContent="Height" WindowStartupLocation="CenterOwner"
Title="Add IEC 61850 IED" Width="480" Height="350" MinWidth="480" MaxWidth="480"
SizeToContent="Manual" WindowStartupLocation="CenterOwner"
ResizeMode="NoResize" ShowInTaskbar="False"
Background="{DynamicResource TaskDialogCanvas}"
FontFamily="{DynamicResource AppFontFamily}" Icon="Assets/app-icon.ico">
Expand Down Expand Up @@ -48,10 +48,10 @@
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="HistoryChrome" Background="{TemplateBinding Background}"
CornerRadius="0,8,8,0">
<Path Data="M 2,5 L 7,10 L 12,5" Width="14" Height="14"
Stretch="Uniform" Stroke="{StaticResource TaskDialogMuted}"
StrokeThickness="1.7" StrokeStartLineCap="Round"
StrokeEndLineCap="Round" StrokeLineJoin="Round"
<!-- Google Material Icons: expand_circle_down (24dp). -->
<Path Data="M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M12,16.5 L6,10.5 7.41,9.09 12,13.67 16.59,9.09 18,10.5 Z"
Width="20" Height="20" Stretch="Uniform"
Fill="{StaticResource TaskDialogMuted}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
Expand Down Expand Up @@ -82,9 +82,12 @@
<Border x:Name="Chrome" CornerRadius="6" Padding="2,5"
Background="Transparent">
<StackPanel Orientation="Horizontal">
<Path x:Name="AdvancedChevron" Data="M 2,3 L 6,7 L 10,3" Width="12" Height="12"
Stretch="Uniform" Stroke="{StaticResource TaskDialogMuted}"
StrokeThickness="1.5" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
<!-- Same Material circle-chevron as the IP history control. -->
<Path x:Name="AdvancedChevron"
Data="M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M12,16.5 L6,10.5 7.41,9.09 12,13.67 16.59,9.09 18,10.5 Z"
Width="17" Height="17" Stretch="Uniform"
RenderTransformOrigin="0.5,0.5"
Fill="{StaticResource TaskDialogMuted}"
VerticalAlignment="Center"/>
<TextBlock Text="Advanced settings"
Margin="8,0,0,0" FontSize="11.5"
Expand All @@ -96,7 +99,7 @@
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="AdvancedChevron" Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="180" CenterX="6" CenterY="6"/>
<RotateTransform Angle="180"/>
</Setter.Value>
</Setter>
</Trigger>
Expand Down Expand Up @@ -146,7 +149,7 @@
<TextBlock Text="IP address" FontSize="12" FontWeight="SemiBold"
Foreground="{StaticResource TaskDialogInk}" Margin="0,0,0,8"/>
<Grid>
<Border x:Name="IpEditorHost" Height="43" CornerRadius="9"
<Border x:Name="IpEditorHost" Width="265" Height="43" HorizontalAlignment="Left" CornerRadius="9"
BorderThickness="1" Background="{StaticResource TaskDialogCard}">
<Border.Style>
<Style TargetType="Border">
Expand Down Expand Up @@ -192,7 +195,7 @@
Click="HistoryButton_Click"/>
</Grid>
</Border>
<TextBox x:Name="RawIpBox" Height="43" Padding="12,7"
<TextBox x:Name="RawIpBox" Width="265" Height="43" HorizontalAlignment="Left" Padding="12,7"
VerticalContentAlignment="Center" Visibility="Collapsed"
FontSize="13"
Foreground="{StaticResource TaskDialogInk}"
Expand Down Expand Up @@ -260,31 +263,22 @@
<Expander x:Name="MoreOptionsExpander" Margin="0,12,0,0"
Style="{StaticResource SmartIpOptionsExpander}"
AutomationProperties.Name="Advanced connection settings">
<Grid Margin="2,9,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="126"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Port" FontSize="11"
Foreground="{StaticResource TaskDialogMuted}"
Margin="0,0,0,5"/>
<TextBox x:Name="MmsPortBox" Width="94" Height="34"
HorizontalAlignment="Left" Padding="10,5"
<StackPanel Margin="2,9,0,0">
<TextBlock Text="Port" FontSize="11"
Foreground="{StaticResource TaskDialogMuted}"
Margin="0,0,0,5"/>
<Border Width="96" Height="36" HorizontalAlignment="Left"
Background="{StaticResource TaskDialogCard}"
BorderBrush="{StaticResource TaskDialogBorder}"
BorderThickness="1" CornerRadius="9">
<TextBox x:Name="MmsPortBox" BorderThickness="0"
Background="Transparent" Padding="10,5"
FontSize="12" VerticalContentAlignment="Center"
Foreground="{StaticResource TaskDialogInk}"
Background="{StaticResource TaskDialogCard}"
BorderBrush="{StaticResource TaskDialogBorder}"
ToolTip="Default port: 102"
Text="{Binding MmsPortText, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<CheckBox x:Name="ManualAddressCheck" Grid.Column="1"
Content="Use full address" Margin="2,17,0,0"
VerticalAlignment="Center" FontSize="11.5"
Foreground="{StaticResource TaskDialogMuted}"
ToolTip="Use this for other address formats"
Click="ManualAddressCheck_Click"/>
</Grid>
</Border>
</StackPanel>
</Expander>

<TextBlock x:Name="WizardStatusText" Margin="2,9,0,0"
Expand Down
48 changes: 8 additions & 40 deletions IpConnectWizardWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public string MmsPortText
private readonly bool _rememberDiscoveryEndpoint;
private readonly ICollectionView _historyView;
private bool _updatingOctets;
private bool _rawAddressMode;
public int MmsPort { get; private set; } = 102;

// UI projection ONLY; UserPreferenceStore remains the sole persistence authority.
Expand Down Expand Up @@ -80,7 +81,7 @@ public IpConnectWizardWindow(string initialIp = "", int initialPort = 102,

Loaded += (_, _) =>
{
if (ManualAddressCheck.IsChecked == true) RawIpBox.Focus();
if (_rawAddressMode) RawIpBox.Focus();
else Octet1Box.Focus();
};
}
Expand Down Expand Up @@ -129,7 +130,7 @@ private void ApplyAddress(string address)
RelayIpAddress = address;
if (TrySplitIpv4(address, out var parts))
{
ManualAddressCheck.IsChecked = false;
_rawAddressMode = false;
_updatingOctets = true;
try
{
Expand All @@ -141,13 +142,13 @@ private void ApplyAddress(string address)
}
else if (!string.IsNullOrWhiteSpace(address))
{
ManualAddressCheck.IsChecked = true;
_rawAddressMode = true;
RawIpBox.Text = address;
SetAddressMode(true);
}
else
{
ManualAddressCheck.IsChecked = false;
_rawAddressMode = false;
_updatingOctets = true;
try { foreach (var box in Octets) box.Clear(); }
finally { _updatingOctets = false; }
Expand All @@ -162,45 +163,12 @@ private void SetAddressMode(bool manual)
OctetEditor.Visibility = manual ? Visibility.Collapsed : Visibility.Visible;
HistoryButton.Visibility = manual ? Visibility.Collapsed : Visibility.Visible;
RawIpBox.Visibility = manual ? Visibility.Visible : Visibility.Collapsed;
if (manual)
MoreOptionsExpander.IsExpanded = true;
_rawAddressMode = manual;
AddressHintText.Text = manual
? "Enter the address exactly as provided."
: "Example: 192.168.1.10";
}

private void ManualAddressCheck_Click(object sender, RoutedEventArgs e)
{
var manual = ManualAddressCheck.IsChecked == true;
if (manual && TryNormalizeIpv4Octets(Octets.Select(b => b.Text).ToArray(), out var ip))
{
RelayIpAddress = ip;
RawIpBox.Text = ip;
}
else if (!manual && TrySplitIpv4(RawIpBox.Text, out var parts))
{
_updatingOctets = true;
try
{
var boxes = Octets;
for (int i = 0; i < 4; i++) boxes[i].Text = parts[i];
}
finally { _updatingOctets = false; }
}
else if (!manual && !string.IsNullOrWhiteSpace(RawIpBox.Text))
{
// Never silently truncate a valid IPv6 literal into four octets.
ManualAddressCheck.IsChecked = true;
WizardStatusText.Text = "Keep manual entry enabled for this address format.";
return;
}

SetAddressMode(manual);
if (manual) RawIpBox.Focus();
else Octet1Box.Focus();
WizardStatusText.Text = string.Empty;
}

private int OctetIndex(TextBox box) => Array.IndexOf(Octets, box);

private void FocusOctet(int index, bool atEnd = false)
Expand Down Expand Up @@ -370,15 +338,15 @@ private void CommitHistorySelection()
if (selection.MmsPort != 102)
MoreOptionsExpander.IsExpanded = true;
HistoryPopup.IsOpen = false;
if (ManualAddressCheck.IsChecked == true) RawIpBox.Focus();
if (_rawAddressMode) RawIpBox.Focus();
else Octet1Box.Focus();
// Recalling an endpoint is UI-only. Only Connect_Click submits.
}

private void Connect_Click(object sender, RoutedEventArgs e)
{
string submittedAddress;
if (ManualAddressCheck.IsChecked == true)
if (_rawAddressMode)
{
submittedAddress = RawIpBox.Text.Trim();
if (!IPAddress.TryParse(submittedAddress, out _))
Expand Down
17 changes: 13 additions & 4 deletions tests/ARSAS.Tests/IedTaskDialogVisualRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@ public void BothDialogs_UseIsolatedHighContrastSurfaceAndBoundedNaturalHeight()
var document = XDocument.Load(Find(file));
var root = Assert.IsType<XElement>(document.Root);
Assert.Equal("Window", root.Name.LocalName);
Assert.Equal("Height", (string?)root.Attribute("SizeToContent"));
Assert.Equal(file == "IpConnectWizardWindow.xaml" ? "Manual" : "Height",
(string?)root.Attribute("SizeToContent"));
Assert.Equal("TaskDialogCanvas", ResourceRef((string?)root.Attribute("Background"), "DynamicResource"));
Assert.True(double.TryParse((string?)root.Attribute("MaxHeight"),
NumberStyles.Float, CultureInfo.InvariantCulture, out var max) && max <= 700);
Assert.Null(root.Attribute("Height"));
if (file == "IpConnectWizardWindow.xaml")
{
Assert.Equal("350", (string?)root.Attribute("Height"));
Assert.Equal("CenterOwner", (string?)root.Attribute("WindowStartupLocation"));
}
else
{
Assert.True(double.TryParse((string?)root.Attribute("MaxHeight"),
NumberStyles.Float, CultureInfo.InvariantCulture, out var max) && max <= 700);
Assert.Null(root.Attribute("Height"));
}
Assert.Contains(root.Descendants(Presentation + "ResourceDictionary"),
element => element.Attribute("Source")?.Value == "Styles/IedTaskDialogStyles.xaml");
Assert.Contains(root.Descendants(Presentation + "ScrollViewer"),
Expand Down
36 changes: 22 additions & 14 deletions tests/ARSAS.Tests/PremiumSmartIpWizardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ public void DefaultDialog_IsCompactUsesAppTypography_AndKeepsExpertControlsColla
var root = Assert.IsType<XElement>(doc.Root);

Assert.InRange(int.Parse(root.Attribute("Width")!.Value), 460, 530);
Assert.True(int.Parse(root.Attribute("MaxHeight")!.Value) <= 480);
Assert.Equal("350", root.Attribute("Height")?.Value);
Assert.Contains("AppFontFamily", root.Attribute("FontFamily")!.Value);
Assert.Equal("Height", root.Attribute("SizeToContent")?.Value);
Assert.Equal("Manual", root.Attribute("SizeToContent")?.Value);

var octetStyle = doc.Descendants(w + "Style")
.Single(style => style.Attribute(x + "Key")?.Value == "SmartIpOctet");
Expand All @@ -104,8 +104,12 @@ public void DefaultDialog_IsCompactUsesAppTypography_AndKeepsExpertControlsColla
Assert.NotEqual("True", options.Attribute("IsExpanded")?.Value);
Assert.Contains(options.Descendants(w + "TextBox"),
box => box.Attribute(x + "Name")?.Value == "MmsPortBox");
Assert.Contains(options.Descendants(w + "CheckBox"),
Assert.DoesNotContain(doc.Descendants(w + "CheckBox"),
box => box.Attribute(x + "Name")?.Value == "ManualAddressCheck");
Assert.Equal("265", doc.Descendants(w + "Border")
.Single(e => e.Attribute(x + "Name")?.Value == "IpEditorHost")
.Attribute("Width")?.Value);
Assert.Contains("expand_circle_down", File.ReadAllText(Find("IpConnectWizardWindow.xaml")));

var xaml = File.ReadAllText(Find("IpConnectWizardWindow.xaml"));
Assert.DoesNotContain("Smart discovery", xaml, StringComparison.Ordinal);
Expand Down Expand Up @@ -152,19 +156,23 @@ public void WpfEditor_ConstructsOnSta_AndHasReachableKeyboardFocusAndIPv6Mode()

var options = Assert.IsType<Expander>(wizard.FindName("MoreOptionsExpander"));
Assert.False(options.IsExpanded);
var advanced = Assert.IsType<CheckBox>(wizard.FindName("ManualAddressCheck"));
var raw = Assert.IsType<TextBox>(wizard.FindName("RawIpBox"));
var host = Assert.IsType<Border>(wizard.FindName("IpEditorHost"));
var mode = typeof(IpConnectWizardWindow).GetMethod(
"ManualAddressCheck_Click", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(mode);
advanced.IsChecked = true;
mode.Invoke(wizard, [advanced, new RoutedEventArgs()]);
Assert.True(options.IsExpanded);
Assert.Equal(Visibility.Visible, raw.Visibility);
Assert.Equal(Visibility.Collapsed, host.Visibility);
raw.Text = "2001:db8::2";
Assert.True(System.Net.IPAddress.TryParse(raw.Text, out _));
Assert.Equal(Visibility.Collapsed, raw.Visibility);
Assert.Equal(Visibility.Visible, host.Visibility);
Assert.Null(wizard.FindName("ManualAddressCheck"));

// IPv6 has no visible toggle; it activates via the caller's
// initial address or a history recall, without losing data.
var ipv6Wizard = new IpConnectWizardWindow("2001:db8::2");
try
{
Assert.Equal(Visibility.Visible,
Assert.IsType<TextBox>(ipv6Wizard.FindName("RawIpBox")).Visibility);
Assert.Equal(Visibility.Collapsed,
Assert.IsType<Border>(ipv6Wizard.FindName("IpEditorHost")).Visibility);
}
finally { ipv6Wizard.Close(); }

Assert.NotNull(wizard.FindName("HistorySearchBox"));
Assert.NotNull(wizard.FindName("HistoryListBox"));
Expand Down
4 changes: 4 additions & 0 deletions tests/ARSAS.Tests/StartupVisualStabilityRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public void IpDialog_UsesSingleInputSurfaceAndLowNoiseAdvancedDisclosure()
var host = doc.Descendants(presentation + "Border")
.Single(node => node.Attribute(xaml + "Name")?.Value == "IpEditorHost");
Assert.Equal("43", (string?)host.Attribute("Height"));
Assert.Equal("265", (string?)host.Attribute("Width"));
Assert.Equal("Manual", (string?)doc.Root.Attribute("SizeToContent"));
Assert.Equal("350", (string?)doc.Root.Attribute("Height"));
Assert.DoesNotContain("ManualAddressCheck", source, StringComparison.Ordinal);

var editor = doc.Descendants(presentation + "StackPanel")
.Single(node => node.Attribute(xaml + "Name")?.Value == "OctetEditor");
Expand Down
Loading