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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Core functionality is under an open source license to help increase the adoption

### Project Documentation
- **[Developer Guidelines](docs/CLAUDE.md)** - Development guidelines and build commands
- **[UI Style Guide](src/UI/Windows/Styles/StyleGuide.md)** - Comprehensive design system and styling guidelines

### Architecture Plans
- **[Connection Plugin Architecture](docs/CONNECTION_PLUGIN_ARCHITECTURE.md)** - Plan for implementing pluggable connection types (Serial, Bluetooth, Network)
Expand Down
14 changes: 12 additions & 2 deletions docs/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@

## Code Style Guidelines
- Use C# 8.0+ features with async/await patterns for asynchronous operations
- Follow MVVM design pattern for view models with ObservableObject and RelayCommand
- Follow the MVVM design pattern for view models with ObservableObject and RelayCommand
- Use dependency injection for services
- Include XML documentation for public interfaces and methods
- Use PascalCase for class, method, and public property names
- Use _camelCase for private fields with underscore prefix
- Use _camelCase for private fields with an underscore prefix
- Implement defensive programming with null checks for constructor parameters
- Use standard exception handling with try/catch blocks around external operations
- Prefer async/await over direct Task management
- Organize files into clear namespaces (Core, Models, Services, ViewModels, etc.)
- Use meaningful variable names that reflect their purpose
- Keep methods focused and small with a single responsibility

## UI Style Guidelines
- **Always use standard styles** - Apply predefined styles from the design system instead of inline properties
- **Use design tokens for spacing** - Reference `{StaticResource Margin.Card}` instead of hardcoding values
- **Apply semantic colors** - Use `{StaticResource Brush.Error}` instead of hardcoded colors like "Red"
- **Follow the style hierarchy** - Check ComponentStyles.xaml and LayoutTemplates.xaml before creating custom styles
- **Update existing code** - When modifying files, replace inline styling with standard styles
- **Create reusable patterns** - If you find yourself repeating XAML structures, consider adding a new style or template

For detailed UI styling guidelines and examples, see: `src/UI/Windows/Styles/StyleGuide.md`
32 changes: 32 additions & 0 deletions src/Core/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@
<value>Baud Rate</value>
<comment>Label for baud rate selection</comment>
</data>
<data name="Connect_ConnectionSettings" xml:space="preserve">
<value>Connection Settings</value>
<comment>Header for connection settings section</comment>
</data>
<data name="Connect_SecuritySettings" xml:space="preserve">
<value>Security Settings</value>
<comment>Header for security settings section</comment>
</data>
<data name="Connect_Address" xml:space="preserve">
<value>Address</value>
<comment>Label for device address input</comment>
Expand Down Expand Up @@ -257,6 +265,30 @@
<value>Device Action</value>
<comment>Header for device action section</comment>
</data>
<data name="Manage_LedColor" xml:space="preserve">
<value>LED Color</value>
<comment>Label for LED color selection</comment>
</data>
<data name="Manage_SelectedFile" xml:space="preserve">
<value>Selected File</value>
<comment>Label for file selection in file transfer</comment>
</data>
<data name="Manage_Browse" xml:space="preserve">
<value>Browse</value>
<comment>Button text for file browser</comment>
</data>
<data name="Manage_Progress" xml:space="preserve">
<value>Progress</value>
<comment>Label for file transfer progress</comment>
</data>
<data name="Manage_BytesOf" xml:space="preserve">
<value> Bytes/</value>
<comment>Text between transferred and total bytes</comment>
</data>
<data name="Manage_Bytes" xml:space="preserve">
<value> Bytes</value>
<comment>Text for bytes unit</comment>
</data>

<!-- Monitor Page -->
<data name="Monitor_DeviceNotConnected" xml:space="preserve">
Expand Down
7 changes: 3 additions & 4 deletions src/Core/ViewModels/Pages/ConnectViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,12 @@ public ConnectViewModel(IDialogService dialogService, IDeviceManagementService d

private void OnDeviceManagementServiceOnTraceEntryReceived(object? sender, TraceEntry traceEntry)
{
if (_deviceManagementService.IsUsingSecureChannel) return;

// Update activity indicators based on raw trace entry direction (works for encrypted packets too)
UpdateActivityIndicators(traceEntry.Direction);

PacketTraceEntry? packetTraceEntry = BuildPacketTraceEntry(traceEntry);
if (packetTraceEntry == null) return;

UpdateActivityIndicators(packetTraceEntry.Direction);

_lastPacketEntry = packetTraceEntry;
}

Expand Down
23 changes: 11 additions & 12 deletions src/Core/ViewModels/Pages/ManageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,17 @@ private void DeviceManagementServiceOnKeypadReadReceived(object? sender, string

private void OnDeviceManagementServiceOnTraceEntryReceived(object? sender, TraceEntry traceEntry)
{
if (_deviceManagementService.IsUsingSecureChannel) return;
// Update activity indicators based on raw trace entry direction (works for encrypted packets too)
switch (traceEntry.Direction)
{
// Flash the appropriate LED based on a direction
case TraceDirection.Output:
LastTxActiveTime = DateTime.Now;
break;
case TraceDirection.Input or TraceDirection.Trace:
LastRxActiveTime = DateTime.Now;
break;
}

var build = new PacketTraceEntryBuilder();
PacketTraceEntry packetTraceEntry;
Expand All @@ -208,17 +218,6 @@ private void OnDeviceManagementServiceOnTraceEntryReceived(object? sender, Trace
{
return;
}

switch (packetTraceEntry.Direction)
{
// Flash the appropriate LED based on a direction
case TraceDirection.Output:
LastTxActiveTime = DateTime.Now;
break;
case TraceDirection.Input or TraceDirection.Trace:
LastRxActiveTime = DateTime.Now;
break;
}

_lastPacketEntry = packetTraceEntry;
}
Expand Down
25 changes: 12 additions & 13 deletions src/Core/ViewModels/Pages/MonitorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,18 @@ private void InitializePollingMetrics()
private void OnDeviceManagementServiceOnTraceEntryReceived(object? _, TraceEntry traceEntry)
{
UsingSecureChannel = _deviceManagementService.IsUsingSecureChannel;

if (UsingSecureChannel) return;

// Update activity indicators based on raw trace entry direction (works for encrypted packets too)
switch (traceEntry.Direction)
{
// Flash appropriate LED based on direction
case Output:
LastTxActiveTime = DateTime.Now;
break;
case Input or Trace:
LastRxActiveTime = DateTime.Now;
break;
}

var build = new PacketTraceEntryBuilder();
PacketTraceEntry packetTraceEntry;
Expand All @@ -67,17 +77,6 @@ private void OnDeviceManagementServiceOnTraceEntryReceived(object? _, TraceEntry
return;
}

switch (packetTraceEntry.Direction)
{
// Flash appropriate LED based on direction
case Output:
LastTxActiveTime = DateTime.Now;
break;
case Input or Trace:
LastRxActiveTime = DateTime.Now;
break;
}

bool notDisplaying = packetTraceEntry.Packet.CommandType == CommandType.Poll ||
_lastPacketEntry?.Packet.CommandType == CommandType.Poll &&
packetTraceEntry.Packet.ReplyType == ReplyType.Ack;
Expand Down
26 changes: 15 additions & 11 deletions src/UI/Windows/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,34 @@
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary />
<ui:ControlsDictionary />
<!-- OSDP Bench Design System -->
<ResourceDictionary Source="Styles/DesignTokens.xaml"/>
<ResourceDictionary Source="Styles/ComponentStyles.xaml"/>
<ResourceDictionary Source="Styles/LayoutTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="PageTitleStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="36"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Setter Property="Margin" Value="10 0 0 0"/>
</Style>

<!-- Legacy styles - kept for backwards compatibility -->
<!-- Use Style="{StaticResource Page.Title}" for new implementations -->
<Style x:Key="PageTitleStyle" TargetType="TextBlock" BasedOn="{StaticResource Page.Title}"/>

<!-- Enhanced DataGrid styles -->
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Padding" Value="{StaticResource Padding.Control}"/>
<Setter Property="FontWeight" Value="Bold"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
</Style>

<!-- Global TextBlock style -->
<Style TargetType="TextBlock" BasedOn="{StaticResource Text.Body}"/>

</ResourceDictionary>
</Application.Resources>
</Application>
71 changes: 71 additions & 0 deletions src/UI/Windows/Helpers/CopyTextBoxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace OSDPBench.Windows.Helpers;

public static class CopyTextBoxHelper
{
public static readonly DependencyProperty CopyCommandProperty =
DependencyProperty.RegisterAttached(
"CopyCommand",
typeof(ICommand),
typeof(CopyTextBoxHelper),
new PropertyMetadata(null));

public static ICommand GetCopyCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CopyCommandProperty);
}

public static void SetCopyCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CopyCommandProperty, value);
}

public static readonly ICommand DefaultCopyCommand = new RelayCommand(
parameter =>
{
if (parameter is TextBox textBox && !string.IsNullOrEmpty(textBox.Text))
{
try
{
Clipboard.SetText(textBox.Text);
}
catch (System.Runtime.InteropServices.COMException)
{
// Clipboard is locked by another application, ignore silently
// This is a common Windows issue and shouldn't crash the app
}
}
});
}

public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;

public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}

public void Execute(object parameter)
{
_execute(parameter);
}
}
Loading