diff --git a/README.md b/README.md
index 55487c4..25ba262 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,14 @@
+# for Lib please replace the Lib folder from this repo with the one linked below
+[WhackerLinkLib/Fork](https://github.com/DeathSoulYTDev/WhackerLinkLib)
+
# WhackerLink Console V2
### WhackerLink Dispatch Console
-
+
+
## Setup
- Download the packaged release from the releases or clone and build yourself
- Modify the codeplug file
- Select the codeplug once opening the app
## Features
- Custumizable widgets
-- Auto saved and transferable user settings
\ No newline at end of file
+- Auto saved and transferable user settings
diff --git a/WhackerLinkConsoleV2/Assets/dualpage.png b/WhackerLinkConsoleV2/Assets/dualpage.png
new file mode 100644
index 0000000..7ab2bf5
Binary files /dev/null and b/WhackerLinkConsoleV2/Assets/dualpage.png differ
diff --git a/WhackerLinkConsoleV2/Assets/pager.png b/WhackerLinkConsoleV2/Assets/pager.png
new file mode 100644
index 0000000..b5f4353
Binary files /dev/null and b/WhackerLinkConsoleV2/Assets/pager.png differ
diff --git a/WhackerLinkConsoleV2/ChannelBox.xaml b/WhackerLinkConsoleV2/ChannelBox.xaml
index 705ec69..544c98c 100644
--- a/WhackerLinkConsoleV2/ChannelBox.xaml
+++ b/WhackerLinkConsoleV2/ChannelBox.xaml
@@ -34,7 +34,7 @@
-
+
@@ -100,7 +100,7 @@
-
diff --git a/WhackerLinkConsoleV2/MainWindow.xaml.cs b/WhackerLinkConsoleV2/MainWindow.xaml.cs
index d9098ad..e0ff649 100644
--- a/WhackerLinkConsoleV2/MainWindow.xaml.cs
+++ b/WhackerLinkConsoleV2/MainWindow.xaml.cs
@@ -48,7 +48,10 @@
using WebSocketSharp;
using NWaves.Signals;
using static WhackerLinkConsoleV2.P25Crypto;
-using static WhackerLinkLib.Models.Radio.Codeplug;
+//using static WhackerLinkLib.Models.Radio.Codeplug;
+using System.Threading.Channels;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media.Animation;
namespace WhackerLinkConsoleV2
{
@@ -65,8 +68,13 @@ public partial class MainWindow : Window
private double _offsetY;
private bool _isDragging;
+ public List AllowedZones { get; set; }
private SettingsManager _settingsManager = new SettingsManager();
+ private Dictionary _zoneCanvases = new Dictionary();
private SelectedChannelsManager _selectedChannelsManager;
+
+ private List<(double ToneA, double ToneB)> _selectedToneSets = new List<(double, double)>();
+
private FlashingBackgroundManager _flashingManager;
private WaveFilePlaybackManager _emergencyAlertPlayback;
private WebSocketManager _webSocketManager = new WebSocketManager();
@@ -93,13 +101,26 @@ public partial class MainWindow : Window
List> fneAffs = new List>();
+ [System.Runtime.InteropServices.DllImport("kernel32.dll")]
+ private static extern bool AllocConsole();
+
public MainWindow()
{
#if !DEBUG
ConsoleNative.ShowConsole();
#endif
InitializeComponent();
+ ChannelsTabControl.Loaded += (s, e) => UpdateIndicator(false);
+ ChannelsTabControl.SelectionChanged += (s, e) => UpdateIndicator(true);
+ //ChannelsTabControl.SelectionChanged += ChannelsTabControl_SelectionChanged;
+
_settingsManager.LoadSettings();
+
+ if (_settingsManager.DebugMode)
+ {
+ ShowDebugConsole();
+ }
+
_selectedChannelsManager = new SelectedChannelsManager();
_flashingManager = new FlashingBackgroundManager(null, ChannelsCanvas, null, this);
_emergencyAlertPlayback = new WaveFilePlaybackManager(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "emergency.wav"));
@@ -124,6 +145,80 @@ public MainWindow()
Loaded += MainWindow_Loaded;
}
+
+ private void ChannelsTabControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ UpdateIndicator(false);
+ }
+
+ private void ChannelsTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ UpdateIndicator(true);
+ }
+
+ private void UpdateIndicator(bool animate)
+ {
+ var indicator = ChannelsTabControl.Template.FindName("SelectionIndicator", ChannelsTabControl) as Border;
+ var transform = indicator?.RenderTransform as TranslateTransform;
+ if (indicator == null || transform == null) return;
+
+ if (ChannelsTabControl.SelectedItem is TabItem selectedTab)
+ {
+ // Force layout update to get correct ActualWidth
+ selectedTab.UpdateLayout();
+ ChannelsTabControl.UpdateLayout();
+
+ // Measure tab position relative to TabControl (not TabPanel)
+ var tabPos = selectedTab.TransformToAncestor(ChannelsTabControl).Transform(new Point(0, 0));
+ double targetX = tabPos.X;
+ double targetWidth = selectedTab.ActualWidth;
+
+ if (animate)
+ {
+ // Animate horizontal position
+ var animX = new DoubleAnimation(transform.X, targetX, TimeSpan.FromMilliseconds(250))
+ {
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
+ };
+ transform.BeginAnimation(TranslateTransform.XProperty, animX);
+
+ // Animate width
+ var animW = new DoubleAnimation(indicator.Width, targetWidth, TimeSpan.FromMilliseconds(250))
+ {
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
+ };
+ indicator.BeginAnimation(FrameworkElement.WidthProperty, animW);
+ }
+ else
+ {
+ // Snap immediately (for startup)
+ transform.X = targetX;
+ indicator.Width = targetWidth;
+ }
+ }
+ }
+
+ private class YamlConfig
+ {
+ public List Tones { get; set; }
+ public List Zones { get; set; }
+ }
+
+ public void ShowDebugConsole()
+ {
+ AllocConsole();
+ Console.WriteLine($"Debug Console: Active");
+ if (_settingsManager.DebugMode)
+ {
+ Console.WriteLine($"DebugMode: Enabled");
+ }
+ }
+
+ private void OpenDebugConsole_Click(object sender, RoutedEventArgs e)
+ {
+ ShowDebugConsole();
+ }
+
private void OpenCodeplug_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog
@@ -165,256 +260,355 @@ private void LoadCodeplug(string filePath)
MessageBox.Show($"Error loading codeplug: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
-
private void GenerateChannelWidgets()
{
- ChannelsCanvas.Children.Clear();
- double offsetX = 20;
- double offsetY = 20;
+ // Clear previous UI
+ ChannelsTabControl.Items.Clear();
+
+ double globalOffsetX = 20;
+ double globalOffsetY = 20;
if (Codeplug != null)
{
+ // 1. Add System Status Boxes (global)
foreach (var system in Codeplug.Systems)
{
- var systemStatusBox = new SystemStatusBox(system.Name, system.Address, system.Port);
+ AddSystemBox(GetSystemCanvas(), system, ref globalOffsetX, ref globalOffsetY);
+ }
- if (_settingsManager.SystemStatusPositions.TryGetValue(system.Name, out var position))
+ AddPlaybackChannel(GetSystemCanvas(), ref globalOffsetX, ref globalOffsetY);
+ AddAlertTones(GetSystemCanvas(), ref globalOffsetX, ref globalOffsetY);
+
+ // 2. Add Zone Tabs
+ foreach (var zone in Codeplug.Zones)
+ {
+ var tabItem = new TabItem { Header = zone.Name };
+
+ var scrollViewer = new ScrollViewer
{
- Canvas.SetLeft(systemStatusBox, position.X);
- Canvas.SetTop(systemStatusBox, position.Y);
- }
- else
+ VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
+ HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch
+ };
+
+ var zoneCanvas = new Canvas
{
- Canvas.SetLeft(systemStatusBox, offsetX);
- Canvas.SetTop(systemStatusBox, offsetY);
- }
+ Background = Brushes.Transparent,
+ Width = Double.NaN, // stretch width to parent
+ Height = Double.NaN // let height expand with content
+ };
- systemStatusBox.MouseLeftButtonDown += SystemStatusBox_MouseLeftButtonDown;
- systemStatusBox.MouseMove += SystemStatusBox_MouseMove;
- systemStatusBox.MouseRightButtonDown += SystemStatusBox_MouseRightButtonDown;
+ scrollViewer.Content = zoneCanvas;
+ tabItem.Content = scrollViewer;
+ ChannelsTabControl.Items.Add(tabItem);
- ChannelsCanvas.Children.Add(systemStatusBox);
+ double offsetX = 20;
+ double offsetY = 20;
- offsetX += 225;
- if (offsetX + 220 > ChannelsCanvas.ActualWidth)
+ // Add channels first
+ foreach (var channel in zone.Channels)
{
- offsetX = 20;
- offsetY += 106;
+ AddChannelBox(zoneCanvas, channel, ref offsetX, ref offsetY);
}
- if (File.Exists(system.AliasPath))
- system.RidAlias = AliasTools.LoadAliases(system.AliasPath);
+ // Then add tones once for the zone
+ AddToneSets(zoneCanvas, offsetX, offsetY, zone.Name);
- if (!system.IsDvm)
+ ChannelsCanvas = zoneCanvas;
+
+ scrollViewer.Loaded += (s, e) =>
{
- _webSocketManager.AddWebSocketHandler(system.Name);
+ var sv = s as ScrollViewer;
+ if (sv?.Content is Canvas c)
+ {
+ if (sv.ViewportWidth > c.Width) c.Width = sv.ViewportWidth;
+ if (sv.ViewportHeight > c.Height) c.Height = sv.ViewportHeight;
+ }
+ };
- IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
- handler.OnVoiceChannelResponse += HandleVoiceResponse;
- handler.OnVoiceChannelRelease += HandleVoiceRelease;
- handler.OnEmergencyAlarmResponse += HandleEmergencyAlarmResponse;
- handler.OnAudioData += HandleReceivedAudio;
- handler.OnAffiliationUpdate += HandleAffiliationUpdate;
- handler.OnCallAlert += HandleCallAlert;
-
- handler.OnUnitRegistrationResponse += (response) =>
+ scrollViewer.Loaded += (s, e) =>
+ {
+ var sv = s as ScrollViewer;
+ if (sv.Content is Canvas c)
{
- Dispatcher.Invoke(() =>
- {
- if (response.Status == (int)ResponseType.GRANT)
- {
- systemStatusBox.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
- systemStatusBox.ConnectionState = "Connected";
+ if (sv.ViewportWidth > c.Width) c.Width = sv.ViewportWidth;
+ if (sv.ViewportHeight > c.Height) c.Height = sv.ViewportHeight;
+ }
+ };
- foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
- {
- if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
- continue;
+ // Handle resizing dynamically (width only, height stays flexible)
+ scrollViewer.SizeChanged += (s, e) =>
+ {
+ var sv = s as ScrollViewer;
+ if (sv.Content is Canvas c)
+ {
+ if (sv.ViewportWidth > c.Width) c.Width = sv.ViewportWidth;
+ if (sv.ViewportHeight > c.Height) c.Height = sv.ViewportHeight;
+ }
+ };
+ }
+ }
+ }
- Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+ // ---------------------- Helper Methods ----------------------
- if (!system.IsDvm)
- {
- IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
+ private Canvas CreateSystemTab()
+ {
+ // Check if SYSTEMS tab already exists
+ var existingTab = ChannelsTabControl.Items
+ .OfType()
+ .FirstOrDefault(t => t.Header?.ToString() == "SYSTEMS");
- if (channel.IsSelected && handler.IsConnected)
- {
- Console.WriteLine("sending WLINK master aff");
-
- Task.Run(() =>
- {
- GRP_AFF_REQ release = new GRP_AFF_REQ
- {
- SrcId = system.Rid,
- DstId = cpgChannel.Tgid,
- Site = system.Site
- };
-
- handler.SendMessage(release.GetData());
- });
- }
- }
- }
- }
- else
- {
- systemStatusBox.Background = new SolidColorBrush(Colors.Red);
- systemStatusBox.ConnectionState = "Disconnected";
- }
- });
- };
+ if (existingTab != null && existingTab.Content is ScrollViewer sv && sv.Content is Canvas existingCanvas)
+ return existingCanvas; // reuse existing canvas
- handler.OnClose += () =>
- {
- Dispatcher.Invoke(() =>
- {
- systemStatusBox.Background = new SolidColorBrush(Colors.Red);
- systemStatusBox.ConnectionState = "Disconnected";
- });
- };
+ // Create new SYSTEMS tab
+ var tabItem = new TabItem { Header = "SYSTEMS" };
- handler.OnOpen += () =>
- {
- Console.WriteLine("Peer connected");
- U_REG_REQ release = new U_REG_REQ
- {
- SrcId = system.Rid,
- Site = system.Site
- };
+ var scrollViewer = new ScrollViewer
+ {
+ VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
+ HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch
+ };
- handler.SendMessage(release.GetData());
- };
+ var zoneCanvas = new Canvas
+ {
+ Background = Brushes.Transparent,
+ Width = Double.NaN, // allow canvas to stretch horizontally
+ Height = Double.NaN // let height expand with content
+ };
- handler.OnReconnecting += () =>
- {
- Console.WriteLine("Peer reconnecting");
- };
+ scrollViewer.Content = zoneCanvas;
+ tabItem.Content = scrollViewer;
+ ChannelsTabControl.Items.Add(tabItem);
- Task.Run(() =>
- {
- handler.Connect(system.Address, system.Port, system.AuthKey);
+ return zoneCanvas;
+ }
- handler.OnGroupAffiliationResponse += (response) => { /* TODO */ };
+ private Canvas GetSystemCanvas()
+ {
+ // Just call CreateSystemTab which handles existing or new tab
+ return CreateSystemTab();
+ }
- if (!handler.IsConnected)
- {
- systemStatusBox.Background = new SolidColorBrush(Colors.Red);
- systemStatusBox.ConnectionState = "Disconnected";
- }
- });
- } else
- {
- _fneSystemManager.AddFneSystem(system.Name, system, this);
- PeerSystem peer = _fneSystemManager.GetFneSystem(system.Name);
+ private void AddSystemBox(Canvas zoneCanvas, Codeplug.System system, ref double offsetX, ref double offsetY)
+ {
+ var systemStatusBox = new SystemStatusBox(system.Name, system.Address, system.Port);
- peer.peer.PeerConnected += (sender, response) =>
- {
- Console.WriteLine("FNE Peer connected");
+ // Find the first tab named "SYSTEM"
+ var systemTab = ChannelsTabControl.Items
+ .OfType()
+ .FirstOrDefault(t => t.Header?.ToString() == "SYSTEMS");
- Dispatcher.Invoke(() =>
- {
- systemStatusBox.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
- systemStatusBox.ConnectionState = "Connected";
- });
- };
+ if (systemTab == null)
+ return; // no SYSTEM tab, exit early
+ // Set position
+ if (_settingsManager.SystemStatusPositions.TryGetValue(system.Name, out var pos))
+ {
+ Canvas.SetLeft(systemStatusBox, pos.X);
+ Canvas.SetTop(systemStatusBox, pos.Y);
+ }
+ else
+ {
+ Canvas.SetLeft(systemStatusBox, offsetX);
+ Canvas.SetTop(systemStatusBox, offsetY);
+ }
- peer.peer.PeerDisconnected += (response) =>
- {
- Console.WriteLine("FNE Peer disconnected");
+ // Hook events
+ systemStatusBox.MouseLeftButtonDown += SystemStatusBox_MouseLeftButtonDown;
+ systemStatusBox.MouseMove += SystemStatusBox_MouseMove;
+ systemStatusBox.MouseRightButtonDown += SystemStatusBox_MouseRightButtonDown;
- Dispatcher.Invoke(() =>
- {
- systemStatusBox.Background = new SolidColorBrush(Colors.Red);
- systemStatusBox.ConnectionState = "Disconnected";
- });
- };
+ // Add to the SYSTEM tab's canvas
+ zoneCanvas.Children.Add(systemStatusBox);
- Task.Run(() =>
- {
- peer.Start();
- });
- }
+ // Update offsets
+ offsetX += 225;
+ if (offsetX + 220 > zoneCanvas.ActualWidth)
+ {
+ offsetX = 20;
+ offsetY += 106;
+ }
- if (!_settingsManager.ShowSystemStatus)
- systemStatusBox.Visibility = Visibility.Collapsed;
+ // Load aliases
+ if (File.Exists(system.AliasPath))
+ system.RidAlias = AliasTools.LoadAliases(system.AliasPath);
- }
- }
+ // Setup system
+ if (!system.IsDvm)
+ SetupWebSocketHandler(system, systemStatusBox);
+ else
+ SetupFneSystem(system, systemStatusBox);
+
+ // Apply visibility
+ if (!_settingsManager.ShowSystemStatus)
+ systemStatusBox.Visibility = Visibility.Collapsed;
+ }
+
+ private void SetupWebSocketHandler(Codeplug.System system, SystemStatusBox systemStatusBox)
+ {
+ _webSocketManager.AddWebSocketHandler(system.Name);
- if (_settingsManager.ShowChannels && Codeplug != null)
+ IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
+
+ handler.OnVoiceChannelResponse += HandleVoiceResponse;
+ handler.OnVoiceChannelRelease += HandleVoiceRelease;
+ handler.OnEmergencyAlarmResponse += HandleEmergencyAlarmResponse;
+ handler.OnAudioData += HandleReceivedAudio;
+ handler.OnAffiliationUpdate += HandleAffiliationUpdate;
+ handler.OnCallAlert += HandleCallAlert;
+
+ handler.OnUnitRegistrationResponse += (response) =>
{
- foreach (var zone in Codeplug.Zones)
+ Dispatcher.Invoke(() =>
{
- foreach (var channel in zone.Channels)
+ if (response.Status == (int)ResponseType.GRANT)
{
- var channelBox = new ChannelBox(_selectedChannelsManager, _audioManager, channel.Name, channel.System, channel.Tgid);
+ systemStatusBox.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
+ systemStatusBox.ConnectionState = "Connected";
- //channelBox.crypter.AddKey(channel.GetKeyId(), channel.GetAlgoId(), channel.GetEncryptionKey());
+ // Send WLINK master aff for selected channels
+ foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
+ {
+ if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
+ continue;
- systemStatuses.Add(channel.Name, new SlotStatus());
+ Codeplug.System sys = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
- if (_settingsManager.ChannelPositions.TryGetValue(channel.Name, out var position))
- {
- Canvas.SetLeft(channelBox, position.X);
- Canvas.SetTop(channelBox, position.Y);
- }
- else
- {
- Canvas.SetLeft(channelBox, offsetX);
- Canvas.SetTop(channelBox, offsetY);
+ if (!sys.IsDvm)
+ {
+ IPeer peerHandler = _webSocketManager.GetWebSocketHandler(sys.Name);
+ if (channel.IsSelected && peerHandler.IsConnected)
+ {
+ Task.Run(() =>
+ {
+ GRP_AFF_REQ release = new GRP_AFF_REQ
+ {
+ SrcId = sys.Rid,
+ DstId = cpgChannel.Tgid,
+ Site = sys.Site
+ };
+ peerHandler.SendMessage(release.GetData());
+ });
+ }
+ }
}
+ }
+ else
+ {
+ systemStatusBox.Background = new SolidColorBrush(Colors.Red);
+ systemStatusBox.ConnectionState = "Disconnected";
+ }
+ });
+ };
- channelBox.PTTButtonClicked += ChannelBox_PTTButtonClicked;
- channelBox.PageButtonClicked += ChannelBox_PageButtonClicked;
- channelBox.HoldChannelButtonClicked += ChannelBox_HoldChannelButtonClicked;
+ handler.OnClose += () =>
+ {
+ Dispatcher.Invoke(() =>
+ {
+ systemStatusBox.Background = new SolidColorBrush(Colors.Red);
+ systemStatusBox.ConnectionState = "Disconnected";
+ });
+ };
- channelBox.MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
- channelBox.MouseMove += ChannelBox_MouseMove;
- channelBox.MouseRightButtonDown += ChannelBox_MouseRightButtonDown;
- ChannelsCanvas.Children.Add(channelBox);
+ handler.OnOpen += () =>
+ {
+ Console.WriteLine("Peer connected");
+ U_REG_REQ release = new U_REG_REQ
+ {
+ SrcId = system.Rid,
+ Site = system.Site
+ };
+ handler.SendMessage(release.GetData());
+ };
- offsetX += 225;
+ handler.OnReconnecting += () => Console.WriteLine("Peer reconnecting");
- if (offsetX + 220 > ChannelsCanvas.ActualWidth)
- {
- offsetX = 20;
- offsetY += 106;
- }
- }
+ Task.Run(() =>
+ {
+ handler.Connect(system.Address, system.Port, system.AuthKey);
+ if (!handler.IsConnected)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ systemStatusBox.Background = new SolidColorBrush(Colors.Red);
+ systemStatusBox.ConnectionState = "Disconnected";
+ });
}
- }
+ });
+ }
+
+ private void SetupFneSystem(Codeplug.System system, SystemStatusBox systemStatusBox)
+ {
+ _fneSystemManager.AddFneSystem(system.Name, system, this);
- if (_settingsManager.ShowAlertTones && Codeplug != null)
+ PeerSystem peer = _fneSystemManager.GetFneSystem(system.Name);
+
+ peer.peer.PeerConnected += (sender, response) =>
{
- foreach (var alertPath in _settingsManager.AlertToneFilePaths)
+ Dispatcher.Invoke(() =>
{
- var alertTone = new AlertTone(alertPath)
- {
- IsEditMode = isEditMode
- };
+ systemStatusBox.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
+ systemStatusBox.ConnectionState = "Connected";
+ });
+ };
+
+ peer.peer.PeerDisconnected += (response) =>
+ {
+ Dispatcher.Invoke(() =>
+ {
+ systemStatusBox.Background = new SolidColorBrush(Colors.Red);
+ systemStatusBox.ConnectionState = "Disconnected";
+ });
+ };
- alertTone.OnAlertTone += SendAlertTone;
+ Task.Run(() => peer.Start());
+ }
- if (_settingsManager.AlertTonePositions.TryGetValue(alertPath, out var position))
- {
- Canvas.SetLeft(alertTone, position.X);
- Canvas.SetTop(alertTone, position.Y);
- }
- else
- {
- Canvas.SetLeft(alertTone, 20);
- Canvas.SetTop(alertTone, 20);
- }
+ private void AddChannelBox(Canvas tabCanvas, Codeplug.Channel channel, ref double offsetX, ref double offsetY)
+ {
+ var channelBox = new ChannelBox(_selectedChannelsManager, _audioManager, channel.Name, channel.System, channel.Tgid);
+ systemStatuses.Add(channel.Name, new SlotStatus());
- alertTone.MouseRightButtonUp += AlertTone_MouseRightButtonUp;
+ if (_settingsManager.ChannelPositions.TryGetValue(channel.Name, out var pos))
+ {
+ Canvas.SetLeft(channelBox, pos.X);
+ Canvas.SetTop(channelBox, pos.Y);
+ }
+ else
+ {
+ Canvas.SetLeft(channelBox, offsetX);
+ Canvas.SetTop(channelBox, offsetY);
+ }
- ChannelsCanvas.Children.Add(alertTone);
- }
+ channelBox.PTTButtonClicked += ChannelBox_PTTButtonClicked;
+ channelBox.PageButtonClicked += ChannelBox_PageButtonClicked;
+ channelBox.HoldChannelButtonClicked += ChannelBox_HoldChannelButtonClicked;
+
+ channelBox.MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
+ channelBox.MouseMove += ChannelBox_MouseMove;
+ channelBox.MouseRightButtonDown += ChannelBox_MouseRightButtonDown;
+
+ // Add to current zone canvas
+ tabCanvas.Children.Add(channelBox);
+
+ // --- Layout increment ---
+ offsetX += 225;
+ if (offsetX + 220 > tabCanvas.ActualWidth)
+ {
+ offsetX = 20;
+ offsetY += 106;
}
+ }
+ private void AddPlaybackChannel(Canvas tabCanvas, ref double offsetX, ref double offsetY)
+ {
playbackChannelBox = new ChannelBox(_selectedChannelsManager, _audioManager, PLAYBACKCHNAME, PLAYBACKSYS, PLAYBACKTG);
if (_settingsManager.ChannelPositions.TryGetValue(PLAYBACKCHNAME, out var pos))
@@ -435,19 +629,284 @@ private void GenerateChannelWidgets()
playbackChannelBox.MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
playbackChannelBox.MouseMove += ChannelBox_MouseMove;
playbackChannelBox.MouseRightButtonDown += ChannelBox_MouseRightButtonDown;
- ChannelsCanvas.Children.Add(playbackChannelBox);
- //offsetX += 225;
+ tabCanvas.Children.Add(playbackChannelBox);
- //if (offsetX + 220 > ChannelsCanvas.ActualWidth)
- //{
- // offsetX = 20;
- // offsetY += 106;
- //}
+ offsetX += 225;
+ if (offsetX + 220 > tabCanvas.ActualWidth)
+ {
+ offsetX = 20;
+ offsetY += 106;
+ }
+ }
+
+ private void AddAlertTones(Canvas tabCanvas, ref double offsetX, ref double offsetY)
+ {
+ if (!_settingsManager.ShowAlertTones) return;
+
+ foreach (var alertPath in _settingsManager.AlertToneFilePaths)
+ {
+ var alertTone = new AlertTone(alertPath) { IsEditMode = isEditMode };
+ alertTone.OnAlertTone += SendAlertTone;
+
+ if (_settingsManager.AlertTonePositions.TryGetValue(alertPath, out var pos))
+ {
+ Canvas.SetLeft(alertTone, pos.X);
+ Canvas.SetTop(alertTone, pos.Y);
+ }
+ else
+ {
+ Canvas.SetLeft(alertTone, 20);
+ Canvas.SetTop(alertTone, 20);
+ }
+
+ alertTone.MouseRightButtonUp += AlertTone_MouseRightButtonUp;
+ tabCanvas.Children.Add(alertTone);
+ }
+ }
+
+ private void AddToneSets(Canvas zoneCanvas, double offsetX, double offsetY, string zone)
+ {
+ if (!_settingsManager.ShowQCTones || Codeplug?.Tones == null) return;
+ Console.WriteLine($"Zone Name: {zone}");
+ Console.WriteLine($"ShowQCTones: {_settingsManager.ShowQCTones}");
+ Console.WriteLine($"Loaded tones: {Codeplug.Tones?.Count ?? 0}");
+ Console.WriteLine($"Zone canvas size: {zoneCanvas.ActualWidth}x{zoneCanvas.ActualHeight}");
+ Console.WriteLine($"Adding tones for zone: {zone}");
+
+ foreach (var tone in Codeplug.Tones)
+ {
+ Console.WriteLine($"Tone {tone.Name}, AllowedZones: {string.Join(",", tone.AllowedZones)}");
+
+ // Only add tones allowed in this zone
+ if (tone.AllowedZones == null || !tone.AllowedZones.Any(z => string.Equals(z.Trim(), zone.Trim(), StringComparison.OrdinalIgnoreCase)))
+ {
+ continue;
+ }
+
+ var toneSetControl = new ToneSet(tone.Name, tone.ToneA, tone.ToneB);
+
+ // --- Play logic ---
+ toneSetControl.PlayClicked += async (s, e) =>
+ {
+ if (_selectedToneSets.Count > 0)
+ {
+ var selectedTones = _selectedToneSets.ToList();
+ var initiallyActiveChannels = _selectedChannelsManager
+ .GetSelectedChannels()
+ .Where(c => c.PageState)
+ .ToList();
+
+ for (int i = 0; i < selectedTones.Count; i++)
+ {
+ var selected = selectedTones[i];
+ var key = (selected.ToneA, selected.ToneB);
+
+ foreach (var ch in initiallyActiveChannels)
+ {
+ ch.PageState = true;
+ Dispatcher.Invoke(() =>
+ {
+ ch.PageSelectButton.Background = ch.orangeGradient;
+ });
+ ChannelBox_PageButtonClicked(ch, ch);
+ }
+
+ if (i > 1) {
+ await Task.Delay(2000);
+ }
+
+ await PlayTone(selected.ToneA.ToString(), selected.ToneB.ToString());
+
+ _selectedToneSets.Remove(key);
+
+ foreach (var child in zoneCanvas.Children)
+ {
+ if (child is ToneSet ts && ts.ToneA == key.ToneA && ts.ToneB == key.ToneB)
+ {
+ ts.SetSelected(false);
+ break;
+ }
+ }
+ }
+
+ foreach (var ch in initiallyActiveChannels)
+ {
+ ch.PageState = false;
+ Dispatcher.Invoke(() =>
+ {
+ ch.PageSelectButton.Background = ch.grayGradient;
+ });
+ }
+ }
+ else
+ {
+ var hasActivePage = _selectedChannelsManager.GetSelectedChannels().Any(c => c.PageState);
+ if (hasActivePage)
+ {
+ await PlayTone(tone.ToneA.ToString(), tone.ToneB.ToString());
+
+ foreach (var channel in _selectedChannelsManager.GetSelectedChannels())
+ {
+ channel.PageState = false;
+ channel.PageSelectButton.Background = channel.grayGradient;
+ }
+ }
+ }
+ };
+
+ // --- Select/Toggle logic ---
+ toneSetControl.SelectToggled += (s, e) =>
+ {
+ var key = (tone.ToneA, tone.ToneB);
+ if (_selectedToneSets.Contains(key))
+ {
+ _selectedToneSets.Remove(key);
+ toneSetControl.SetSelected(false);
+ }
+ else
+ {
+ _selectedToneSets.Add(key);
+ toneSetControl.SetSelected(true);
+ }
+ };
+
+ // --- Positioning ---
+ if (_settingsManager.QCToneSetPositions.TryGetValue(tone.Name, out var pos))
+ {
+ Canvas.SetLeft(toneSetControl, pos.X);
+ Canvas.SetTop(toneSetControl, pos.Y);
+ }
+ else
+ {
+ Canvas.SetLeft(toneSetControl, offsetX);
+ Canvas.SetTop(toneSetControl, offsetY);
+ }
+
+ toneSetControl.MouseLeftButtonDown += ToneSet_MouseLeftButtonDown;
+ toneSetControl.MouseMove += ToneSet_MouseMove;
+ toneSetControl.MouseRightButtonDown += ToneSet_MouseRightButtonDown;
+
+ // Add to current zone canvas
+ zoneCanvas.Children.Add(toneSetControl);
+
+ // --- Layout increment ---
+ offsetX += 225;
+ if (offsetX + 220 > zoneCanvas.ActualWidth)
+ {
+ offsetX = 20;
+ offsetY += 106;
+ }
+ }
+ }
+
+ private const int GridSize = 5;
+
+ private void ToneSet_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (!isEditMode || !(sender is UIElement element)) return;
+
+ _draggedElement = element;
+ _startPoint = e.GetPosition(ChannelsCanvas);
+ _offsetX = _startPoint.X - Canvas.GetLeft(_draggedElement);
+ _offsetY = _startPoint.Y - Canvas.GetTop(_draggedElement);
+ _isDragging = true;
+
+ element.CaptureMouse();
+ }
+
+ private void ToneSet_MouseMove(object sender, MouseEventArgs e)
+ {
+ if (!isEditMode || !_isDragging || _draggedElement == null) return;
+
+ // Find the canvas containing this element
+ Canvas zoneCanvas = null;
+ DependencyObject parent = _draggedElement;
+ while (parent != null)
+ {
+ parent = VisualTreeHelper.GetParent(parent);
+ if (parent is Canvas canvas)
+ {
+ zoneCanvas = canvas;
+ break;
+ }
+ }
+ if (zoneCanvas == null) return;
+
+ // Mouse position relative to the canvas
+ Point mousePos = e.GetPosition(zoneCanvas);
+
+ // Center the ToneSet on the cursor
+ double newLeft = mousePos.X - (_draggedElement.RenderSize.Width / 2);
+ double newTop = mousePos.Y - (_draggedElement.RenderSize.Height / 2);
+
+ // Snap to grid
+ newLeft = Math.Round(newLeft / GridSize) * GridSize;
+ newTop = Math.Round(newTop / GridSize) * GridSize;
+
+ // Clamp to canvas bounds
+ newLeft = Math.Max(0, Math.Min(newLeft, zoneCanvas.ActualWidth - _draggedElement.RenderSize.Width));
+ newTop = Math.Max(0, Math.Min(newTop, zoneCanvas.ActualHeight - _draggedElement.RenderSize.Height));
+
+ // Apply new position
+ Canvas.SetLeft(_draggedElement, newLeft);
+ Canvas.SetTop(_draggedElement, newTop);
+
+ // Save the new position
+ if (_draggedElement is ToneSet toneSet)
+ _settingsManager.UpdateQCToneSetPosition(toneSet.ToneName, newLeft, newTop);
AdjustCanvasHeight();
}
+
+ private void ToneSet_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (!isEditMode || !_isDragging || _draggedElement == null) return;
+
+ _isDragging = false;
+ _draggedElement.ReleaseMouseCapture();
+ _draggedElement = null;
+ }
+
+
+ private async void ToneSet_PlayClicked(object sender, EventArgs e)
+ {
+ if (sender is ToneSet toneSet)
+ {
+ if (_selectedToneSets.Count > 0)
+ {
+ foreach (var selected in _selectedToneSets)
+ {
+ await PlayTone(selected.ToneA.ToString(), selected.ToneB.ToString());
+ }
+ }
+ else
+ {
+ await PlayTone(toneSet.ToneA.ToString(), toneSet.ToneB.ToString());
+ }
+ }
+ }
+
+ private void ToneSet_SelectClicked(object sender, EventArgs e)
+ {
+ if (sender is ToneSet toneSet)
+ {
+ var key = (toneSet.ToneA, toneSet.ToneB);
+
+ if (_selectedToneSets.Contains(key))
+ {
+ _selectedToneSets.Remove(key);
+ toneSet.SetSelected(false);
+ }
+ else
+ {
+ _selectedToneSets.Add(key);
+ toneSet.SetSelected(true);
+ }
+ }
+ }
+
private void WaveIn_RecordingStopped(object sender, EventArgs e)
{
/* stub */
@@ -934,6 +1393,7 @@ private void SelectWidgets_Click(object sender, RoutedEventArgs e)
_settingsManager.ShowSystemStatus = widgetSelectionWindow.ShowSystemStatus;
_settingsManager.ShowChannels = widgetSelectionWindow.ShowChannels;
_settingsManager.ShowAlertTones = widgetSelectionWindow.ShowAlertTones;
+ _settingsManager.ShowQCTones = widgetSelectionWindow.ShowQCTones;
GenerateChannelWidgets();
_settingsManager.SaveSettings();
@@ -1288,48 +1748,84 @@ private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
}
}
+ //private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ //{
+
+ // if (!isEditMode || !(sender is UIElement element)) return;
+
+ // _draggedElement = element;
+ // _startPoint = e.GetPosition(ChannelsCanvas);
+ // _offsetX = _startPoint.X - Canvas.GetLeft(_draggedElement);
+ // _offsetY = _startPoint.Y - Canvas.GetTop(_draggedElement);
+ // _isDragging = true;
+
+ // element.CaptureMouse();
+ //}
+
private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!isEditMode || !(sender is UIElement element)) return;
_draggedElement = element;
_startPoint = e.GetPosition(ChannelsCanvas);
- _offsetX = _startPoint.X - Canvas.GetLeft(_draggedElement);
- _offsetY = _startPoint.Y - Canvas.GetTop(_draggedElement);
- _isDragging = true;
+ // Fix: if Left/Top are NaN, treat as 0
+ double left = Canvas.GetLeft(_draggedElement);
+ if (double.IsNaN(left)) left = 0;
+ double top = Canvas.GetTop(_draggedElement);
+ if (double.IsNaN(top)) top = 0;
+
+ _offsetX = _startPoint.X - left;
+ _offsetY = _startPoint.Y - top;
+
+ _isDragging = true;
element.CaptureMouse();
}
- private const int GridSize = 5;
-
private void ChannelBox_MouseMove(object sender, MouseEventArgs e)
{
if (!isEditMode || !_isDragging || _draggedElement == null) return;
- Point currentPosition = e.GetPosition(ChannelsCanvas);
+ // Find the canvas containing this element
+ Canvas zoneCanvas = null;
+ DependencyObject parent = _draggedElement;
+ while (parent != null)
+ {
+ parent = VisualTreeHelper.GetParent(parent);
+ if (parent is Canvas canvas)
+ {
+ zoneCanvas = canvas;
+ break;
+ }
+ }
+ if (zoneCanvas == null) return;
+
+ // Mouse position relative to the canvas
+ Point mousePos = e.GetPosition(zoneCanvas);
+
+ // Center the element on the cursor
+ double newLeft = mousePos.X - (_draggedElement.RenderSize.Width / 2);
+ double newTop = mousePos.Y - (_draggedElement.RenderSize.Height / 2);
- // Calculate the new position with snapping to the grid
- double newLeft = Math.Round((currentPosition.X - _offsetX) / GridSize) * GridSize;
- double newTop = Math.Round((currentPosition.Y - _offsetY) / GridSize) * GridSize;
+ // Snap to grid
+ newLeft = Math.Round(newLeft / GridSize) * GridSize;
+ newTop = Math.Round(newTop / GridSize) * GridSize;
- // Ensure the box stays within canvas bounds
- newLeft = Math.Max(0, Math.Min(newLeft, ChannelsCanvas.ActualWidth - _draggedElement.RenderSize.Width));
- newTop = Math.Max(0, Math.Min(newTop, ChannelsCanvas.ActualHeight - _draggedElement.RenderSize.Height));
+ // Clamp to canvas bounds
+ newLeft = Math.Max(0, Math.Min(newLeft, zoneCanvas.ActualWidth - _draggedElement.RenderSize.Width));
+ newTop = Math.Max(0, Math.Min(newTop, zoneCanvas.ActualHeight - _draggedElement.RenderSize.Height));
- // Apply snapped position
+ // Apply new position
Canvas.SetLeft(_draggedElement, newLeft);
Canvas.SetTop(_draggedElement, newTop);
- // Save the new position if it's a ChannelBox
- if (_draggedElement is ChannelBox channelBox)
- {
- _settingsManager.UpdateChannelPosition(channelBox.ChannelName, newLeft, newTop);
- }
+ if (_draggedElement is ChannelBox cb)
+ _settingsManager.UpdateChannelPosition(cb.ChannelName, newLeft, newTop);
AdjustCanvasHeight();
}
+
private void ChannelBox_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (!isEditMode || !_isDragging || _draggedElement == null) return;
@@ -1455,6 +1951,7 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
if (!string.IsNullOrEmpty(_settingsManager.LastCodeplugPath) && File.Exists(_settingsManager.LastCodeplugPath))
{
LoadCodeplug(_settingsManager.LastCodeplugPath);
+ //LoadToneSets();
}
else
{
@@ -1462,6 +1959,118 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
}
}
+ private async Task PlayTone(string ToneA, string ToneB)
+ {
+ var selectedChannels = _selectedChannelsManager.GetSelectedChannels();
+
+ Console.WriteLine($"Playing Tones A={ToneA}, B={ToneB}");
+
+ // Check if any selected channel has PageState = true
+ if (!selectedChannels.Any(ch => ch.PageState))
+ {
+ // No channels with active page state - do nothing
+ return;
+ }
+
+ foreach (ChannelBox channel in selectedChannels)
+ {
+ if (!channel.PageState)
+ continue; // skip channels without page state
+
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+
+ ToneGenerator generator = new ToneGenerator();
+
+ double toneADuration = 1.0;
+ double toneBDuration = 3.0;
+
+ byte[] toneA = generator.GenerateTone(double.Parse(ToneA), toneADuration);
+ byte[] toneB = generator.GenerateTone(double.Parse(ToneB), toneBDuration);
+
+ byte[] combinedAudio = new byte[toneA.Length + toneB.Length];
+ Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length);
+ Buffer.BlockCopy(toneB, 0, combinedAudio, toneA.Length, toneB.Length);
+
+ int chunkSize = system.IsDvm ? 320 : 1600;
+ int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
+
+ _audioManager.AddTalkgroupStream(cpgChannel.Tgid, combinedAudio);
+
+ await Task.Run(() =>
+ {
+ for (int i = 0; i < totalChunks; i++)
+ {
+ int offset = i * chunkSize;
+ int size = Math.Min(chunkSize, combinedAudio.Length - offset);
+
+ byte[] chunk = new byte[chunkSize];
+ Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size);
+
+ if (!system.IsDvm)
+ {
+ IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
+
+ AudioPacket voicePacket = new AudioPacket
+ {
+ Data = chunk,
+ VoiceChannel = new VoiceChannel
+ {
+ Frequency = channel.VoiceChannel,
+ DstId = cpgChannel.Tgid,
+ SrcId = system.Rid,
+ Site = system.Site
+ },
+ Site = system.Site,
+ LopServerVocode = true
+ };
+
+ handler.SendMessage(voicePacket.GetData());
+ }
+ else
+ {
+ PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
+
+ if (chunk.Length == 320)
+ {
+ P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
+ }
+ }
+ }
+ });
+
+ double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 750;
+ await Task.Delay((int)totalDurationMs);
+
+ if (!system.IsDvm)
+ {
+ IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
+
+ GRP_VCH_RLS release = new GRP_VCH_RLS
+ {
+ SrcId = system.Rid,
+ DstId = cpgChannel.Tgid,
+ Channel = channel.VoiceChannel,
+ Site = system.Site
+ };
+
+ handler.SendMessage(release.GetData());
+ }
+ else
+ {
+ PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
+
+ await Task.Delay(4000);
+ handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
+ }
+
+ Dispatcher.Invoke(() =>
+ {
+ channel.PageSelectButton.Background = channel.grayGradient;
+ });
+ }
+ }
+
private async void OnHoldTimerElapsed(object sender, ElapsedEventArgs e)
{
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
@@ -1600,7 +2209,7 @@ private async void btnGlobalPtt_Click(object sender, RoutedEventArgs e)
{
Dispatcher.Invoke(() =>
{
- btnGlobalPtt.Background = channel.grayGradient;
+ btnGlobalPtt.Background = (Brush)new BrushConverter().ConvertFrom("#888888");
});
GRP_VCH_RLS release = new GRP_VCH_RLS
diff --git a/WhackerLinkConsoleV2/SettingsManager.cs b/WhackerLinkConsoleV2/SettingsManager.cs
index 96690b9..71bd526 100644
--- a/WhackerLinkConsoleV2/SettingsManager.cs
+++ b/WhackerLinkConsoleV2/SettingsManager.cs
@@ -30,6 +30,9 @@ public class SettingsManager
public bool ShowSystemStatus { get; set; } = true;
public bool ShowChannels { get; set; } = true;
public bool ShowAlertTones { get; set; } = true;
+ public bool ShowQCTones { get; set; } = true;
+
+ public bool DebugMode { get; set; } = false;
public string LastCodeplugPath { get; set; } = null;
@@ -37,6 +40,7 @@ public class SettingsManager
public Dictionary SystemStatusPositions { get; set; } = new Dictionary();
public List AlertToneFilePaths { get; set; } = new List();
public Dictionary AlertTonePositions { get; set; } = new Dictionary();
+ public Dictionary QCToneSetPositions { get; set; } = new Dictionary();
public Dictionary ChannelOutputDevices { get; set; } = new Dictionary();
public void LoadSettings()
@@ -50,14 +54,17 @@ public void LoadSettings()
if (loadedSettings != null)
{
+ DebugMode = loadedSettings.DebugMode;
ShowSystemStatus = loadedSettings.ShowSystemStatus;
ShowChannels = loadedSettings.ShowChannels;
ShowAlertTones = loadedSettings.ShowAlertTones;
+ ShowQCTones = loadedSettings.ShowQCTones;
LastCodeplugPath = loadedSettings.LastCodeplugPath;
ChannelPositions = loadedSettings.ChannelPositions ?? new Dictionary();
SystemStatusPositions = loadedSettings.SystemStatusPositions ?? new Dictionary();
AlertToneFilePaths = loadedSettings.AlertToneFilePaths ?? new List();
AlertTonePositions = loadedSettings.AlertTonePositions ?? new Dictionary();
+ QCToneSetPositions = loadedSettings.QCToneSetPositions ?? new Dictionary();
ChannelOutputDevices = loadedSettings.ChannelOutputDevices ?? new Dictionary();
}
}
@@ -67,6 +74,12 @@ public void LoadSettings()
}
}
+
+ public void ToggleDebugMode()
+ {
+ DebugMode = !DebugMode;
+ SaveSettings();
+ }
public void UpdateAlertTonePaths(string newFilePath)
{
if (!AlertToneFilePaths.Contains(newFilePath))
@@ -88,6 +101,12 @@ public void UpdateChannelPosition(string channelName, double x, double y)
SaveSettings();
}
+ public void UpdateQCToneSetPosition(string name, double x, double y)
+ {
+ QCToneSetPositions[name] = new ChannelPosition { X = x, Y = y };
+ SaveSettings();
+ }
+
public void UpdateSystemStatusPosition(string systemName, double x, double y)
{
SystemStatusPositions[systemName] = new ChannelPosition { X = x, Y = y };
diff --git a/WhackerLinkConsoleV2/Themes.xaml b/WhackerLinkConsoleV2/Themes.xaml
new file mode 100644
index 0000000..745f8a2
--- /dev/null
+++ b/WhackerLinkConsoleV2/Themes.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+ #3A86FF
+ #FFFFFF
+ #F2F2F2
+ #E6E6E6
+ #FFFFFF
+ #222222
+ #999999
+
+
+
+
+ #3A86FF
+ #1E1E1E
+ #2C2C2C
+ #333333
+ #2C2C2C
+ #FFFFFF
+ #777777
+
+
+
+
+
diff --git a/WhackerLinkConsoleV2/ToneSet.xaml b/WhackerLinkConsoleV2/ToneSet.xaml
new file mode 100644
index 0000000..8fea962
--- /dev/null
+++ b/WhackerLinkConsoleV2/ToneSet.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WhackerLinkConsoleV2/ToneSet.xaml.cs b/WhackerLinkConsoleV2/ToneSet.xaml.cs
new file mode 100644
index 0000000..ce37511
--- /dev/null
+++ b/WhackerLinkConsoleV2/ToneSet.xaml.cs
@@ -0,0 +1,101 @@
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace WhackerLinkConsoleV2.Controls
+{
+ public partial class ToneSet : UserControl, INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ internal LinearGradientBrush grayGradient;
+ internal LinearGradientBrush orangeGradient;
+
+ public string ToneName { get; set; }
+ public double ToneA { get; set; }
+ public double ToneB { get; set; }
+
+ public bool IsEditMode { get; set; }
+
+ public event EventHandler PlayClicked;
+ public event EventHandler SelectToggled;
+
+ private bool _isSelected = false;
+
+ public ToneSet(string toneName, double toneA, double toneB)
+ {
+ InitializeComponent();
+ UpdateBackground();
+
+ grayGradient = new LinearGradientBrush
+ {
+ StartPoint = new Point(0.5, 0),
+ EndPoint = new Point(0.5, 1)
+ };
+
+ grayGradient.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#888888"), 0.485));
+ grayGradient.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#888888"), 0.517));
+
+ orangeGradient = new LinearGradientBrush
+ {
+ StartPoint = new Point(0.5, 0),
+ EndPoint = new Point(0.5, 1)
+ };
+
+ orangeGradient.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#FFFFAF00"), 0.485));
+ orangeGradient.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#FFEEA400"), 0.517));
+ ToneSetSelectBtn.Background = grayGradient;
+
+ ToneName = toneName;
+ ToneA = toneA;
+ ToneB = toneB;
+
+ MouseLeftButtonDown += ToneSet_MouseLeftButtonDown;
+
+ DataContext = this;
+ }
+
+ private void ToneSet_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (IsEditMode) return;
+ }
+
+
+ private void ToneSetPlayBtn_Click(object sender, RoutedEventArgs e)
+ {
+ PlayClicked?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void ToneSetSelectBtn_Click(object sender, RoutedEventArgs e)
+ {
+ SelectToggled?.Invoke(this, EventArgs.Empty);
+ }
+
+ public void SetSelected(bool selected)
+ {
+ _isSelected = selected;
+ UpdateSelectButton();
+ if (selected)
+ this.Background = System.Windows.Media.Brushes.LightBlue;
+ else
+ this.Background = System.Windows.Media.Brushes.Transparent;
+ }
+
+ private void UpdateSelectButton()
+ {
+ if (ToneSetSelectBtn != null)
+ {
+ //ToneSetSelectBtn.Content = _isSelected ? "Deselect" : "Select";
+ ToneSetSelectBtn.Background = _isSelected ? orangeGradient : grayGradient;
+ }
+ }
+
+ private void UpdateBackground()
+ {
+ Background = _isSelected ? (Brush)new BrushConverter().ConvertFrom("#FF0B004B") : Brushes.DarkGray;
+ }
+ }
+}
diff --git a/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj b/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj
index 5441dd9..77f5a9e 100644
--- a/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj
+++ b/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj
@@ -13,6 +13,8 @@
+
+
@@ -71,6 +73,8 @@
+
+
diff --git a/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj.user b/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj.user
new file mode 100644
index 0000000..0f14913
--- /dev/null
+++ b/WhackerLinkConsoleV2/WhackerLinkConsoleV2.csproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml b/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml
index d2cd7e3..183fa9a 100644
--- a/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml
+++ b/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml
@@ -7,6 +7,7 @@
+
diff --git a/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml.cs b/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml.cs
index d837eb0..9826dda 100644
--- a/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml.cs
+++ b/WhackerLinkConsoleV2/WidgetSelectionWindow.xaml.cs
@@ -27,6 +27,7 @@ public partial class WidgetSelectionWindow : Window
public bool ShowSystemStatus { get; private set; } = true;
public bool ShowChannels { get; private set; } = true;
public bool ShowAlertTones { get; private set; } = true;
+ public bool ShowQCTones { get; private set; } = true;
public WidgetSelectionWindow()
{
@@ -38,6 +39,8 @@ private void ApplyButton_Click(object sender, RoutedEventArgs e)
ShowSystemStatus = SystemStatusCheckBox.IsChecked ?? false;
ShowChannels = ChannelCheckBox.IsChecked ?? false;
ShowAlertTones = AlertToneCheckBox.IsChecked ?? false;
+ ShowQCTones = QCTonesCheckBox.IsChecked ?? false;
+
DialogResult = true;
Close();
}
diff --git a/WhackerLinkConsoleV2/codeplugs/codeplug.yml b/WhackerLinkConsoleV2/codeplugs/codeplug.yml
index ca0f856..820532d 100644
--- a/WhackerLinkConsoleV2/codeplugs/codeplug.yml
+++ b/WhackerLinkConsoleV2/codeplugs/codeplug.yml
@@ -44,6 +44,17 @@ systems:
systemID: 1
siteID: 1
range: 1.5
+tones:
+ - name: "Station 1"
+ toneA: 855.5
+ toneB: 349
+ zones:
+ - "Zone 1"
+ - name: "Station 2"
+ toneA: 1006.9
+ toneB: 500.9
+ zones:
+ - "Zone 1"
zones:
- name: "Zone 1"
@@ -71,4 +82,4 @@ zones:
tgid: "16002"
- name: "Channel C"
system: "System 1"
- tgid: "16002"
\ No newline at end of file
+ tgid: "16002"
diff --git a/WhackerLinkLib b/WhackerLinkLib
index 9178526..9b87dbc 160000
--- a/WhackerLinkLib
+++ b/WhackerLinkLib
@@ -1 +1 @@
-Subproject commit 9178526bf07a73374f2bfe016bc0b48602922d3a
+Subproject commit 9b87dbcee1bad1a69198d7416b9af07436a898a3