diff --git a/dvmconsole/Assets/bg_main_hd_dark.png b/dvmconsole/Assets/bg_main_hd_dark.png
new file mode 100644
index 0000000..0eee0f9
Binary files /dev/null and b/dvmconsole/Assets/bg_main_hd_dark.png differ
diff --git a/dvmconsole/Assets/bg_main_hd.png b/dvmconsole/Assets/bg_main_hd_light.png
similarity index 100%
rename from dvmconsole/Assets/bg_main_hd.png
rename to dvmconsole/Assets/bg_main_hd_light.png
diff --git a/dvmconsole/AudioSettingsWindow.xaml.cs b/dvmconsole/AudioSettingsWindow.xaml.cs
index 011628d..9cc89bc 100644
--- a/dvmconsole/AudioSettingsWindow.xaml.cs
+++ b/dvmconsole/AudioSettingsWindow.xaml.cs
@@ -132,6 +132,8 @@ namespace dvmconsole
return outputDevices;
}
+ /** WPF Events */
+
///
///
///
diff --git a/dvmconsole/CallHistoryWindow.xaml.cs b/dvmconsole/CallHistoryWindow.xaml.cs
index 98c4dae..ec9ee6a 100644
--- a/dvmconsole/CallHistoryWindow.xaml.cs
+++ b/dvmconsole/CallHistoryWindow.xaml.cs
@@ -18,34 +18,7 @@ using System.Windows.Media;
namespace dvmconsole
{
///
- ///
- ///
- public class CallHistoryViewModel
- {
- /*
- ** Properties
- */
-
- ///
- ///
- ///
- public ObservableCollection CallHistory { get; set; }
-
- /*
- ** Methods
- */
-
- ///
- /// Initializes a new instance of the class.
- ///
- public CallHistoryViewModel()
- {
- CallHistory = new ObservableCollection();
- }
- } // public class CallHistoryViewModel
-
- ///
- ///
+ /// Data structure representing a call entry.
///
public class CallEntry : DependencyObject
{
@@ -57,20 +30,20 @@ namespace dvmconsole
*/
///
- ///
+ /// Textual name of channel call was received on.
///
public string Channel { get; set; }
///
- ///
+ /// Source ID.
///
public int SrcId { get; set; }
///
- ///
+ /// Destination ID.
///
public int DstId { get; set; }
///
- ///
+ /// Background color for call entry.
///
public Brush BackgroundColor
{
@@ -79,6 +52,33 @@ namespace dvmconsole
}
} // public class CallEntry : DependencyObject
+ ///
+ /// Data view model representing the call history.
+ ///
+ public class CallHistoryViewModel
+ {
+ /*
+ ** Properties
+ */
+
+ ///
+ /// Collection of call history entries.
+ ///
+ public ObservableCollection CallHistory { get; set; }
+
+ /*
+ ** Methods
+ */
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CallHistoryViewModel()
+ {
+ CallHistory = new ObservableCollection();
+ }
+ } // public class CallHistoryViewModel
+
///
/// Interaction logic for CallHistoryWindow.xaml.
///
@@ -89,7 +89,7 @@ namespace dvmconsole
*/
///
- ///
+ /// Gets or sets the view model for the window.
///
public CallHistoryViewModel ViewModel { get; set; }
diff --git a/dvmconsole/Controls/ChannelBox.xaml.cs b/dvmconsole/Controls/ChannelBox.xaml.cs
index 813ef97..c49f6d3 100644
--- a/dvmconsole/Controls/ChannelBox.xaml.cs
+++ b/dvmconsole/Controls/ChannelBox.xaml.cs
@@ -13,7 +13,6 @@
*/
using System.ComponentModel;
-using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
@@ -78,15 +77,15 @@ namespace dvmconsole.Controls
*/
///
- ///
+ /// Textual name of channel.
///
public string ChannelName { get; set; }
///
- ///
+ /// Textual name of system channel belongs to.
///
public string SystemName { get; set; }
///
- ///
+ /// Destination ID.
///
public string DstId { get; set; }
@@ -104,33 +103,33 @@ namespace dvmconsole.Controls
*/
///
- ///
+ /// Event action that handles the PTT button being clicked.
///
public event EventHandler PTTButtonClicked;
///
- ///
+ /// Event action that handles the page button being clicked.
///
public event EventHandler PageButtonClicked;
///
- ///
+ /// Event action that handles the hold channel button being clicked.
///
public event EventHandler HoldChannelButtonClicked;
///
- ///
+ /// Event action that occurs when a property changes on this control.
///
public event PropertyChangedEventHandler PropertyChanged;
///
- ///
+ /// Flag indicating whether or not this channel is receiving.
///
public bool IsReceiving { get; set; } = false;
///
- ///
+ /// Flag indicating whether or not this channel is receiving encrypted.
///
public bool IsReceivingEncrypted { get; set; } = false;
///
- ///
+ /// Last Source ID received.
///
public string LastSrcId
{
@@ -146,7 +145,7 @@ namespace dvmconsole.Controls
}
///
- ///
+ /// Flag indicating the current PTT state of this channel.
///
public bool PttState
{
@@ -159,7 +158,7 @@ namespace dvmconsole.Controls
}
///
- ///
+ /// Flag indicating the current page state of this channel.
///
public bool PageState
{
@@ -172,7 +171,7 @@ namespace dvmconsole.Controls
}
///
- ///
+ /// Flag indicating the hold state of this channel.
///
public bool HoldState
{
@@ -185,7 +184,7 @@ namespace dvmconsole.Controls
}
///
- ///
+ /// Flag indicating the emergency state of this channel.
///
public bool Emergency
{
@@ -210,12 +209,12 @@ namespace dvmconsole.Controls
public string VoiceChannel { get; set; }
///
- ///
+ /// Flag indicating whether or not edit mode is enabled.
///
public bool IsEditMode { get; set; }
///
- ///
+ /// Flag indicating whether or not this channel is selected.
///
public bool IsSelected
{
@@ -228,7 +227,7 @@ namespace dvmconsole.Controls
}
///
- ///
+ /// Current volume for this channel.
///
public double Volume
{
@@ -247,7 +246,7 @@ namespace dvmconsole.Controls
///
///
///
- public uint txStreamId { get; internal set; }
+ public uint TxStreamId { get; internal set; }
/*
** Methods
@@ -367,31 +366,12 @@ namespace dvmconsole.Controls
}
}
- ///
- ///
- ///
- ///
- ///
- private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
- {
- if (IsEditMode)
- return;
-
- IsSelected = !IsSelected;
- ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
-
- if (IsSelected)
- selectedChannelsManager.AddSelectedChannel(this);
- else
- selectedChannelsManager.RemoveSelectedChannel(this);
- }
-
///
///
///
private void UpdatePTTColor()
{
- if (IsEditMode)
+ if (IsEditMode)
return;
if (PttState)
@@ -405,7 +385,7 @@ namespace dvmconsole.Controls
///
private void UpdatePageColor()
{
- if (IsEditMode)
+ if (IsEditMode)
return;
if (PageState)
@@ -416,7 +396,7 @@ namespace dvmconsole.Controls
private void UpdateHoldColor()
{
- if (IsEditMode)
+ if (IsEditMode)
return;
if (HoldState)
@@ -439,6 +419,36 @@ namespace dvmconsole.Controls
ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
}
+ ///
+ ///
+ ///
+ ///
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ /** WPF Events */
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (IsEditMode)
+ return;
+
+ IsSelected = !IsSelected;
+ ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
+
+ if (IsSelected)
+ selectedChannelsManager.AddSelectedChannel(this);
+ else
+ selectedChannelsManager.RemoveSelectedChannel(this);
+ }
+
///
///
///
@@ -481,15 +491,6 @@ namespace dvmconsole.Controls
Volume = e.NewValue;
}
- ///
- ///
- ///
- ///
- protected virtual void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
-
///
///
///
diff --git a/dvmconsole/Controls/SystemStatusBox.xaml.cs b/dvmconsole/Controls/SystemStatusBox.xaml.cs
index e767a66..e11875e 100644
--- a/dvmconsole/Controls/SystemStatusBox.xaml.cs
+++ b/dvmconsole/Controls/SystemStatusBox.xaml.cs
@@ -30,16 +30,16 @@ namespace dvmconsole.Controls
*/
///
- ///
+ /// Textual name of the system.
///
public string SystemName { get; set; }
///
- ///
+ /// Address and port of the system.
///
public string AddressPort { get; set; }
///
- ///
+ /// Connection state.
///
public string ConnectionState
{
@@ -68,7 +68,7 @@ namespace dvmconsole.Controls
*/
///
- ///
+ /// Event action that occurs when a property changes on this control.
///
public event PropertyChangedEventHandler PropertyChanged;
diff --git a/dvmconsole/DVMConsole.csproj b/dvmconsole/DVMConsole.csproj
index 6111456..45c7c32 100644
--- a/dvmconsole/DVMConsole.csproj
+++ b/dvmconsole/DVMConsole.csproj
@@ -29,7 +29,8 @@
-
+
+
@@ -62,7 +63,10 @@
Never
-
+
+ Never
+
+
Never
diff --git a/dvmconsole/DigitalPageWindow.xaml.cs b/dvmconsole/DigitalPageWindow.xaml.cs
index 014b207..527f055 100644
--- a/dvmconsole/DigitalPageWindow.xaml.cs
+++ b/dvmconsole/DigitalPageWindow.xaml.cs
@@ -20,9 +20,15 @@ namespace dvmconsole
///
public partial class DigitalPageWindow : Window
{
- public List systems = new List();
+ private List systems = new List();
+ ///
+ /// Destination ID.
+ ///
public string DstId = string.Empty;
+ ///
+ /// System.
+ ///
public Codeplug.System RadioSystem = null;
///
@@ -39,6 +45,8 @@ namespace dvmconsole
SystemCombo.SelectedIndex = 0;
}
+ /** WPF Events */
+
///
///
///
diff --git a/dvmconsole/KeyStatusWindow.xaml.cs b/dvmconsole/KeyStatusWindow.xaml.cs
index 8dc46f2..951991c 100644
--- a/dvmconsole/KeyStatusWindow.xaml.cs
+++ b/dvmconsole/KeyStatusWindow.xaml.cs
@@ -20,7 +20,7 @@ using dvmconsole.Controls;
namespace dvmconsole
{
///
- ///
+ /// Data structure representing a key status item.
///
public class KeyStatusItem
{
@@ -29,25 +29,25 @@ namespace dvmconsole
*/
///
- ///
+ /// Textual name of channel key is for.
///
public string ChannelName { get; set; }
///
- ///
+ /// Algorithm ID.
///
public string AlgId { get; set; }
///
- ///
+ /// Key ID.
///
public string KeyId { get; set; }
///
- ///
+ /// Key status.
///
public string KeyStatus { get; set; }
} // public class KeyStatusItem
///
- ///
+ /// Interaction logic for KeyStatusWindow.xaml.
///
public partial class KeyStatusWindow : Window
{
@@ -59,7 +59,7 @@ namespace dvmconsole
*/
///
- ///
+ /// Collection of key status entries.
///
public ObservableCollection KeyStatusItems { get; private set; } = new ObservableCollection();
diff --git a/dvmconsole/MainWindow.xaml b/dvmconsole/MainWindow.xaml
index 89a4a79..115aff5 100644
--- a/dvmconsole/MainWindow.xaml
+++ b/dvmconsole/MainWindow.xaml
@@ -51,13 +51,16 @@
+
diff --git a/dvmconsole/MainWindow.xaml.cs b/dvmconsole/MainWindow.xaml.cs
index d23513a..f6c8881 100644
--- a/dvmconsole/MainWindow.xaml.cs
+++ b/dvmconsole/MainWindow.xaml.cs
@@ -19,7 +19,7 @@ using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
-using System.Windows.Media;
+using System.Windows.Media.Imaging;
using Microsoft.Win32;
@@ -40,7 +40,7 @@ using fnecore.P25.KMM;
namespace dvmconsole
{
///
- ///
+ /// Data structure representing the position of a widget.
///
public class ChannelPosition
{
@@ -49,11 +49,11 @@ namespace dvmconsole
*/
///
- ///
+ /// X
///
public double X { get; set; }
///
- ///
+ /// Y
///
public double Y { get; set; }
} // public class ChannelPosition
@@ -66,6 +66,8 @@ namespace dvmconsole
public const double MIN_WIDTH = 875;
public const double MIN_HEIGHT = 700;
+ private const string URI_RESOURCE_PATH = "pack://application:,,,/dvmconsole;component";
+
private bool isEditMode = false;
private bool globalPttState = false;
@@ -108,7 +110,7 @@ namespace dvmconsole
*/
///
- ///
+ /// Codeplug
///
public Codeplug Codeplug { get; set; }
@@ -153,16 +155,22 @@ namespace dvmconsole
}
///
- /// Helper to enable form controls when settings and codeplug are loaded.
+ /// Helper to enable menu controls for Commands submenu.
///
- private void EnableControls()
+ private void EnableCommandControls()
{
menuPageSubscriber.IsEnabled = true;
menuRadioCheckSubscriber.IsEnabled = true;
menuInhibitSubscriber.IsEnabled = true;
menuUninhibitSubscriber.IsEnabled = true;
menuQuickCall2.IsEnabled = true;
+ }
+ ///
+ /// Helper to enable form controls when settings and codeplug are loaded.
+ ///
+ private void EnableControls()
+ {
btnGlobalPtt.IsEnabled = true;
btnAlert1.IsEnabled = true;
btnAlert2.IsEnabled = true;
@@ -174,15 +182,23 @@ namespace dvmconsole
}
///
- /// Helper to disable form controls when settings load fails.
+ /// Helper to disable menu controls for Commands submenu.
///
- private void DisableControls()
+ private void DisableCommandControls()
{
menuPageSubscriber.IsEnabled = false;
menuRadioCheckSubscriber.IsEnabled = false;
menuInhibitSubscriber.IsEnabled = false;
menuUninhibitSubscriber.IsEnabled = false;
menuQuickCall2.IsEnabled = false;
+ }
+
+ ///
+ /// Helper to disable form controls when settings load fails.
+ ///
+ private void DisableControls()
+ {
+ DisableCommandControls();
btnGlobalPtt.IsEnabled = false;
btnAlert1.IsEnabled = false;
@@ -195,46 +211,7 @@ namespace dvmconsole
}
///
- ///
- ///
- ///
- ///
- private void OpenCodeplug_Click(object sender, RoutedEventArgs e)
- {
- OpenFileDialog openFileDialog = new OpenFileDialog
- {
- Filter = "Codeplug Files (*.yml)|*.yml|All Files (*.*)|*.*",
- Title = "Open Codeplug"
- };
-
- if (openFileDialog.ShowDialog() == true)
- {
- LoadCodeplug(openFileDialog.FileName);
-
- settingsManager.LastCodeplugPath = openFileDialog.FileName;
- noSaveSettingsOnClose = false;
- settingsManager.SaveSettings();
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- private void ResetSettings_Click(object sender, RoutedEventArgs e)
- {
- var confirmResult = MessageBox.Show("Are you sure to wish to reset console settings?", "Reset Settings", MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (confirmResult == MessageBoxResult.Yes)
- {
- MessageBox.Show("Settings will be reset after console restart.", "Reset Settings", MessageBoxButton.OK, MessageBoxImage.Information);
- noSaveSettingsOnClose = true;
- settingsManager.Reset();
- }
- }
-
- ///
- ///
+ /// Helper to load the codeplug.
///
///
private void LoadCodeplug(string filePath)
@@ -260,7 +237,7 @@ namespace dvmconsole
}
///
- ///
+ /// Helper to initialize and generate channel widgets on the canvas.
///
private void GenerateChannelWidgets()
{
@@ -435,61 +412,6 @@ namespace dvmconsole
Cursor = Cursors.Arrow;
}
- ///
- ///
- ///
- ///
- ///
- private void WaveIn_RecordingStopped(object sender, EventArgs e)
- {
- /* stub */
- }
-
- ///
- ///
- ///
- ///
- ///
- private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
- {
- bool isAnyTgOn = false;
-
- foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
- {
- if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
- continue;
-
- Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
-
-
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
-
- if (channel.IsSelected && channel.PttState)
- {
- isAnyTgOn = true;
-
- int samples = 320;
-
- Task.Run(() =>
- {
- channel.chunkedPcm = AudioConverter.SplitToChunks(e.Buffer);
-
- foreach (byte[] chunk in channel.chunkedPcm)
- {
- if (chunk.Length == samples)
- P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
- else
- Trace.WriteLine("bad sample length: " + chunk.Length);
- }
- });
- }
- }
-
- if (playbackChannelBox != null && isAnyTgOn && playbackChannelBox.IsSelected)
- audioManager.AddTalkgroupStream(PLAYBACKTG, e.Buffer);
- }
-
///
///
///
@@ -518,303 +440,94 @@ namespace dvmconsole
///
///
///
- ///
///
- private void AudioSettings_Click(object sender, RoutedEventArgs e)
+ private void SendAlertTone(AlertTone e)
{
- List channels = Codeplug?.Zones.SelectMany(z => z.Channels).ToList() ?? new List();
-
- AudioSettingsWindow audioSettingsWindow = new AudioSettingsWindow(settingsManager, audioManager, channels);
- audioSettingsWindow.ShowDialog();
+ Task.Run(() => SendAlertTone(e.AlertFilePath));
}
///
///
///
- ///
- ///
- private void PageRID_Click(object sender, RoutedEventArgs e)
+ ///
+ ///
+ private void SendAlertTone(string filePath, bool forHold = false)
{
- DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
- pageWindow.Owner = this;
- pageWindow.Title = "Page Subscriber";
-
- if (pageWindow.ShowDialog() == true)
+ if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
- PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
- IOSP_CALL_ALRT callAlert = new IOSP_CALL_ALRT(uint.Parse(pageWindow.DstId), uint.Parse(pageWindow.RadioSystem.Rid));
-
- RemoteCallData callData = new RemoteCallData
+ try
{
- SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
- DstId = uint.Parse(pageWindow.DstId),
- LCO = P25Defines.TSBK_IOSP_CALL_ALRT
- };
+ foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
+ {
+ if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
+ continue;
- byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
- callAlert.Encode(ref tsbk);
+ if (channel.PageState || (forHold && channel.HoldState))
+ {
+ byte[] pcmData;
- handler.SendP25TSBK(callData, tsbk);
- }
- }
+ Task.Run(async () => {
+ using (var waveReader = new WaveFileReader(filePath))
+ {
+ if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
+ waveReader.WaveFormat.SampleRate != 8000 ||
+ waveReader.WaveFormat.BitsPerSample != 16 ||
+ waveReader.WaveFormat.Channels != 1)
+ {
+ MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
- ///
- ///
- ///
- ///
- ///
- private void RadioCheckRID_Click(object sender, RoutedEventArgs e)
- {
- DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
- pageWindow.Owner = this;
- pageWindow.Title = "Radio Check Subscriber";
+ using (MemoryStream ms = new MemoryStream())
+ {
+ waveReader.CopyTo(ms);
+ pcmData = ms.ToArray();
+ }
+ }
- if (pageWindow.ShowDialog() == true)
- {
- PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
- IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.CHECK, uint.Parse(pageWindow.RadioSystem.Rid), uint.Parse(pageWindow.DstId));
+ int chunkSize = 1600;
+ int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
- RemoteCallData callData = new RemoteCallData
- {
- SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
- DstId = uint.Parse(pageWindow.DstId),
- LCO = P25Defines.TSBK_IOSP_EXT_FNCT
- };
+ if (pcmData.Length % chunkSize != 0)
+ {
+ byte[] paddedData = new byte[totalChunks * chunkSize];
+ Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length);
+ pcmData = paddedData;
+ }
- byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+ Task.Run(() =>
+ {
+ audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
+ });
- extFunc.Encode(ref tsbk);
+ DateTime startTime = DateTime.UtcNow;
- handler.SendP25TSBK(callData, tsbk);
- }
- }
+ for (int i = 0; i < totalChunks; i++)
+ {
+ int offset = i * chunkSize;
+ byte[] chunk = new byte[chunkSize];
+ Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
- ///
- ///
- ///
- ///
- ///
- private void InhibitRID_Click(object sender, RoutedEventArgs e)
- {
- DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
- pageWindow.Owner = this;
- pageWindow.Title = "Inhibit Subscriber";
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
- if (pageWindow.ShowDialog() == true)
- {
- PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
- IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.INHIBIT, P25Defines.WUID_FNE, uint.Parse(pageWindow.DstId));
+ channel.chunkedPcm = AudioConverter.SplitToChunks(chunk);
- RemoteCallData callData = new RemoteCallData
- {
- SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
- DstId = uint.Parse(pageWindow.DstId),
- LCO = P25Defines.TSBK_IOSP_EXT_FNCT
- };
+ foreach (byte[] smallchunk in channel.chunkedPcm)
+ {
+ if (smallchunk.Length == 320)
+ P25EncodeAudioFrame(smallchunk, handler, channel, cpgChannel, system);
+ }
- byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+ DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
+ TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
- extFunc.Encode(ref tsbk);
-
- handler.SendP25TSBK(callData, tsbk);
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- private void UninhibitRID_Click(object sender, RoutedEventArgs e)
- {
- DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
- pageWindow.Owner = this;
- pageWindow.Title = "Uninhibit Subscriber";
-
- if (pageWindow.ShowDialog() == true)
- {
- PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
- IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.UNINHIBIT, P25Defines.WUID_FNE, uint.Parse(pageWindow.DstId));
-
- RemoteCallData callData = new RemoteCallData
- {
- SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
- DstId = uint.Parse(pageWindow.DstId),
- LCO = P25Defines.TSBK_IOSP_EXT_FNCT
- };
-
- byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
-
- extFunc.Encode(ref tsbk);
-
- handler.SendP25TSBK(callData, tsbk);
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- private async void ManualPage_Click(object sender, RoutedEventArgs e)
- {
- QuickCallPage pageWindow = new QuickCallPage();
- pageWindow.Owner = this;
-
- if (pageWindow.ShowDialog() == true)
- {
- foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
- {
- Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
-
- if (channel.PageState)
- {
- ToneGenerator generator = new ToneGenerator();
-
- double toneADuration = 1.0;
- double toneBDuration = 3.0;
-
- byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), toneADuration);
- byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.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 = 320;
-
- int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
-
- Task.Run(() =>
- {
- //_waveProvider.ClearBuffer();
- 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 (chunk.Length == 320)
- P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
- }
- });
-
- double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 750;
- await Task.Delay((int)totalDurationMs + 4000);
-
- handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
-
- Dispatcher.Invoke(() =>
- {
- //channel.PageState = false; // TODO: Investigate
- channel.PageSelectButton.Background = ChannelBox.GRAY_GRADIENT;
- });
- }
- }
- }
- }
-
- ///
- ///
- ///
- ///
- private void SendAlertTone(AlertTone e)
- {
- Task.Run(() => SendAlertTone(e.AlertFilePath));
- }
-
- ///
- ///
- ///
- ///
- ///
- private void SendAlertTone(string filePath, bool forHold = false)
- {
- if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
- {
- try
- {
- foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
- {
- if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
- continue;
-
- Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
-
- if (channel.PageState || (forHold && channel.HoldState))
- {
- byte[] pcmData;
-
- Task.Run(async () => {
- using (var waveReader = new WaveFileReader(filePath))
- {
- if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
- waveReader.WaveFormat.SampleRate != 8000 ||
- waveReader.WaveFormat.BitsPerSample != 16 ||
- waveReader.WaveFormat.Channels != 1)
- {
- MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
- return;
- }
-
- using (MemoryStream ms = new MemoryStream())
- {
- waveReader.CopyTo(ms);
- pcmData = ms.ToArray();
- }
- }
-
- int chunkSize = 1600;
- int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
-
- if (pcmData.Length % chunkSize != 0)
- {
- byte[] paddedData = new byte[totalChunks * chunkSize];
- Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length);
- pcmData = paddedData;
- }
-
- Task.Run(() =>
- {
- audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
- });
-
- DateTime startTime = DateTime.UtcNow;
-
- for (int i = 0; i < totalChunks; i++)
- {
- int offset = i * chunkSize;
- byte[] chunk = new byte[chunkSize];
- Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
-
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
-
- channel.chunkedPcm = AudioConverter.SplitToChunks(chunk);
-
- foreach (byte[] smallchunk in channel.chunkedPcm)
- {
- if (smallchunk.Length == 320)
- P25EncodeAudioFrame(smallchunk, handler, channel, cpgChannel, system);
- }
-
- DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
- TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
-
- if (waitTime.TotalMilliseconds > 0)
- await Task.Delay(waitTime);
- }
+ if (waitTime.TotalMilliseconds > 0)
+ await Task.Delay(waitTime);
+ }
double totalDurationMs = ((double)pcmData.Length / 16000) + 250;
await Task.Delay((int)totalDurationMs + 3000);
@@ -838,30 +551,7 @@ namespace dvmconsole
}
}
else
- {
MessageBox.Show("Alert file not set or file not found.", "Alert", MessageBoxButton.OK, MessageBoxImage.Warning);
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- private void SelectWidgets_Click(object sender, RoutedEventArgs e)
- {
- WidgetSelectionWindow widgetSelectionWindow = new WidgetSelectionWindow();
- widgetSelectionWindow.Owner = this;
- if (widgetSelectionWindow.ShowDialog() == true)
- {
- settingsManager.ShowSystemStatus = widgetSelectionWindow.ShowSystemStatus;
- settingsManager.ShowChannels = widgetSelectionWindow.ShowChannels;
- settingsManager.ShowAlertTones = widgetSelectionWindow.ShowAlertTones;
-
- GenerateChannelWidgets();
- if (!noSaveSettingsOnClose)
- settingsManager.SaveSettings();
- }
}
///
@@ -899,32 +589,32 @@ namespace dvmconsole
///
///
///
- ///
- ///
- private void ChannelBox_HoldChannelButtonClicked(object sender, ChannelBox e)
+ private void UpdateEditModeForWidgets()
{
- if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
- return;
+ foreach (var child in channelsCanvas.Children)
+ {
+ if (child is AlertTone alertTone)
+ alertTone.IsEditMode = isEditMode;
+
+ if (child is ChannelBox channelBox)
+ channelBox.IsEditMode = isEditMode;
+ }
}
///
///
///
- ///
- ///
- private void ChannelBox_PageButtonClicked(object sender, ChannelBox e)
+ private void UpdateBackground()
{
- if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
- return;
-
- Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
-
- if (e.PageState)
- handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), true);
+ BitmapImage bg = new BitmapImage();
+ bg.BeginInit();
+ if (settingsManager.DarkMode)
+ bg.UriSource = new Uri($"{URI_RESOURCE_PATH}/Assets/bg_main_hd_dark.png");
else
- handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
+ bg.UriSource = new Uri($"{URI_RESOURCE_PATH}/Assets/bg_main_hd_light.png");
+ bg.EndInit();
+
+ channelsCanvasBg.ImageSource = bg;
}
///
@@ -932,67 +622,144 @@ namespace dvmconsole
///
///
///
- private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
+ private async void OnHoldTimerElapsed(object sender, ElapsedEventArgs e)
{
- if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
- return;
-
- Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+ foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
+ {
+ if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
+ continue;
- if (!e.IsSelected)
- return;
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
- FneUtils.Memset(e.mi, 0x00, P25Defines.P25_MI_LENGTH);
+ if (channel.HoldState && !channel.IsReceiving && !channel.PttState && !channel.PageState)
+ {
+ handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), true);
+ await Task.Delay(1000);
- uint srcId = uint.Parse(system.Rid);
- uint dstId = uint.Parse(cpgChannel.Tgid);
+ SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/hold.wav"), true);
+ }
+ }
+ }
- if (e.PttState)
+ ///
+ ///
+ ///
+ ///
+ protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
+ {
+ if (!noSaveSettingsOnClose)
{
- e.txStreamId = handler.NewStreamId();
- handler.SendP25TDU(srcId, dstId, true);
+ if (WindowState == WindowState.Maximized)
+ settingsManager.Maximized = true;
+
+ settingsManager.SaveSettings();
}
- else
- handler.SendP25TDU(srcId, dstId, false);
+
+ base.OnClosing(e);
+ Application.Current.Shutdown();
}
+ /** NAudio Events */
+
///
///
///
///
///
- private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ private void WaveIn_RecordingStopped(object sender, EventArgs e)
{
- if (!isEditMode || !(sender is UIElement element)) return;
+ /* stub */
+ }
- draggedElement = element;
- startPoint = e.GetPosition(channelsCanvas);
- offsetX = startPoint.X - Canvas.GetLeft(draggedElement);
- offsetY = startPoint.Y - Canvas.GetTop(draggedElement);
- isDragging = true;
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
+ {
+ bool isAnyTgOn = false;
- Cursor = Cursors.ScrollAll;
+ foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
+ {
+ if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
+ continue;
- element.CaptureMouse();
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+
+
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+
+ if (channel.IsSelected && channel.PttState)
+ {
+ isAnyTgOn = true;
+
+ int samples = 320;
+
+ Task.Run(() =>
+ {
+ channel.chunkedPcm = AudioConverter.SplitToChunks(e.Buffer);
+
+ foreach (byte[] chunk in channel.chunkedPcm)
+ {
+ if (chunk.Length == samples)
+ P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
+ else
+ Trace.WriteLine("bad sample length: " + chunk.Length);
+ }
+ });
+ }
+ }
+
+ if (playbackChannelBox != null && isAnyTgOn && playbackChannelBox.IsSelected)
+ audioManager.AddTalkgroupStream(PLAYBACKTG, e.Buffer);
}
+ /** WPF Window Events */
+
///
///
///
///
///
- private void ChannelBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ ///
+ private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
- if (!isEditMode || !isDragging || draggedElement == null)
+ const double widthOffset = 16;
+ const double heightOffset = 115;
+
+ if (!windowLoaded)
return;
- Cursor = Cursors.Arrow;
+ if (ActualWidth > channelsCanvas.ActualWidth)
+ {
+ channelsCanvas.Width = ActualWidth;
+ canvasScrollViewer.Width = ActualWidth;
+ }
+ else
+ canvasScrollViewer.Width = Width - widthOffset;
- isDragging = false;
- draggedElement.ReleaseMouseCapture();
- draggedElement = null;
+ if (ActualHeight > channelsCanvas.ActualHeight)
+ {
+ channelsCanvas.Height = ActualHeight;
+ canvasScrollViewer.Height = ActualHeight;
+ }
+ else
+ canvasScrollViewer.Height = Height - heightOffset;
+
+ if (WindowState == WindowState.Maximized)
+ ResizeCanvasToWindow_Click(sender, e);
+ else
+ settingsManager.Maximized = false;
+
+ settingsManager.CanvasWidth = channelsCanvas.ActualWidth;
+ settingsManager.CanvasHeight = channelsCanvas.ActualHeight;
+
+ settingsManager.WindowWidth = ActualWidth;
+ settingsManager.WindowHeight = ActualHeight;
}
///
@@ -1000,28 +767,43 @@ namespace dvmconsole
///
///
///
- private void ChannelBox_MouseMove(object sender, MouseEventArgs e)
+ private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
- if (!isEditMode || !isDragging || draggedElement == null)
- return;
+ const double widthOffset = 16;
+ const double heightOffset = 115;
- Point currentPosition = e.GetPosition(channelsCanvas);
+ if (!string.IsNullOrEmpty(settingsManager.LastCodeplugPath) && File.Exists(settingsManager.LastCodeplugPath))
+ LoadCodeplug(settingsManager.LastCodeplugPath);
+ else
+ GenerateChannelWidgets();
- // 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;
+ menuDarkMode.IsChecked = settingsManager.DarkMode;
+ UpdateBackground();
- // 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));
+ if (settingsManager.Maximized)
+ {
+ windowLoaded = true;
+ WindowState = WindowState.Maximized;
+ ResizeCanvasToWindow_Click(sender, e);
+ }
+ else
+ {
+ Width = settingsManager.WindowWidth;
+ channelsCanvas.Width = settingsManager.CanvasWidth;
+ if (settingsManager.CanvasWidth > settingsManager.WindowWidth)
+ canvasScrollViewer.Width = Width - widthOffset;
+ else
+ canvasScrollViewer.Width = Width;
- // Apply snapped position
- Canvas.SetLeft(draggedElement, newLeft);
- Canvas.SetTop(draggedElement, newTop);
+ Height = settingsManager.WindowHeight;
+ channelsCanvas.Height = settingsManager.CanvasHeight;
+ if (settingsManager.CanvasHeight > settingsManager.WindowHeight)
+ canvasScrollViewer.Height = Height - heightOffset;
+ else
+ canvasScrollViewer.Height = Height;
- // Save the new position if it's a ChannelBox
- if (draggedElement is ChannelBox channelBox)
- settingsManager.UpdateChannelPosition(channelBox.ChannelName, newLeft, newTop);
+ windowLoaded = true;
+ }
}
///
@@ -1029,26 +811,45 @@ namespace dvmconsole
///
///
///
- private void SystemStatusBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) => ChannelBox_MouseLeftButtonDown(sender, e);
+ private void OpenCodeplug_Click(object sender, RoutedEventArgs e)
+ {
+ OpenFileDialog openFileDialog = new OpenFileDialog
+ {
+ Filter = "Codeplug Files (*.yml)|*.yml|All Files (*.*)|*.*",
+ Title = "Open Codeplug"
+ };
+
+ if (openFileDialog.ShowDialog() == true)
+ {
+ LoadCodeplug(openFileDialog.FileName);
+
+ settingsManager.LastCodeplugPath = openFileDialog.FileName;
+ noSaveSettingsOnClose = false;
+ settingsManager.SaveSettings();
+ }
+ }
///
///
///
///
///
- private void SystemStatusBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ private void Exit_Click(object sender, RoutedEventArgs e)
{
- if (!isEditMode)
- return;
+ Application.Current.Shutdown();
+ }
- if (sender is SystemStatusBox systemStatusBox)
- {
- double x = Canvas.GetLeft(systemStatusBox);
- double y = Canvas.GetTop(systemStatusBox);
- settingsManager.SystemStatusPositions[systemStatusBox.SystemName] = new ChannelPosition { X = x, Y = y };
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void AudioSettings_Click(object sender, RoutedEventArgs e)
+ {
+ List channels = Codeplug?.Zones.SelectMany(z => z.Channels).ToList() ?? new List();
- ChannelBox_MouseLeftButtonUp(sender, e);
- }
+ AudioSettingsWindow audioSettingsWindow = new AudioSettingsWindow(settingsManager, audioManager, channels);
+ audioSettingsWindow.ShowDialog();
}
///
@@ -1056,7 +857,16 @@ namespace dvmconsole
///
///
///
- private void SystemStatusBox_MouseMove(object sender, MouseEventArgs e) => ChannelBox_MouseMove(sender, e);
+ private void ResetSettings_Click(object sender, RoutedEventArgs e)
+ {
+ var confirmResult = MessageBox.Show("Are you sure to wish to reset console settings?", "Reset Settings", MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (confirmResult == MessageBoxResult.Yes)
+ {
+ MessageBox.Show("Settings will be reset after console restart.", "Reset Settings", MessageBoxButton.OK, MessageBoxImage.Information);
+ noSaveSettingsOnClose = true;
+ settingsManager.Reset();
+ }
+ }
///
///
@@ -1074,16 +884,15 @@ namespace dvmconsole
///
///
///
- private void UpdateEditModeForWidgets()
+ ///
+ ///
+ private void ToggleDarkMode_Click(object sender, RoutedEventArgs e)
{
- foreach (var child in channelsCanvas.Children)
- {
- if (child is AlertTone alertTone)
- alertTone.IsEditMode = isEditMode;
+ if (!windowLoaded)
+ return;
- if (child is ChannelBox channelBox)
- channelBox.IsEditMode = isEditMode;
- }
+ settingsManager.DarkMode = menuDarkMode.IsChecked;
+ UpdateBackground();
}
///
@@ -1118,6 +927,27 @@ namespace dvmconsole
settingsManager.WindowHeight = ActualHeight;
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void SelectWidgets_Click(object sender, RoutedEventArgs e)
+ {
+ WidgetSelectionWindow widgetSelectionWindow = new WidgetSelectionWindow();
+ widgetSelectionWindow.Owner = this;
+ if (widgetSelectionWindow.ShowDialog() == true)
+ {
+ settingsManager.ShowSystemStatus = widgetSelectionWindow.ShowSystemStatus;
+ settingsManager.ShowChannels = widgetSelectionWindow.ShowChannels;
+ settingsManager.ShowAlertTones = widgetSelectionWindow.ShowAlertTones;
+
+ GenerateChannelWidgets();
+ if (!noSaveSettingsOnClose)
+ settingsManager.SaveSettings();
+ }
+ }
+
///
///
///
@@ -1161,15 +991,29 @@ namespace dvmconsole
///
///
///
- private void AlertTone_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
+ private void PageRID_Click(object sender, RoutedEventArgs e)
{
- if (!isEditMode) return;
+ DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
+ pageWindow.Owner = this;
+ pageWindow.Title = "Page Subscriber";
- if (sender is AlertTone alertTone)
+ if (pageWindow.ShowDialog() == true)
{
- double x = Canvas.GetLeft(alertTone);
- double y = Canvas.GetTop(alertTone);
- settingsManager.UpdateAlertTonePosition(alertTone.AlertFilePath, x, y);
+ PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
+ IOSP_CALL_ALRT callAlert = new IOSP_CALL_ALRT(uint.Parse(pageWindow.DstId), uint.Parse(pageWindow.RadioSystem.Rid));
+
+ RemoteCallData callData = new RemoteCallData
+ {
+ SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
+ DstId = uint.Parse(pageWindow.DstId),
+ LCO = P25Defines.TSBK_IOSP_CALL_ALRT
+ };
+
+ byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+
+ callAlert.Encode(ref tsbk);
+
+ handler.SendP25TSBK(callData, tsbk);
}
}
@@ -1178,41 +1022,228 @@ namespace dvmconsole
///
///
///
- ///
- private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
+ private void RadioCheckRID_Click(object sender, RoutedEventArgs e)
{
- const double widthOffset = 16;
- const double heightOffset = 115;
+ DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
+ pageWindow.Owner = this;
+ pageWindow.Title = "Radio Check Subscriber";
- if (!windowLoaded)
- return;
+ if (pageWindow.ShowDialog() == true)
+ {
+ PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
+ IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.CHECK, uint.Parse(pageWindow.RadioSystem.Rid), uint.Parse(pageWindow.DstId));
- if (ActualWidth > channelsCanvas.ActualWidth)
+ RemoteCallData callData = new RemoteCallData
+ {
+ SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
+ DstId = uint.Parse(pageWindow.DstId),
+ LCO = P25Defines.TSBK_IOSP_EXT_FNCT
+ };
+
+ byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+
+ extFunc.Encode(ref tsbk);
+
+ handler.SendP25TSBK(callData, tsbk);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void InhibitRID_Click(object sender, RoutedEventArgs e)
+ {
+ DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
+ pageWindow.Owner = this;
+ pageWindow.Title = "Inhibit Subscriber";
+
+ if (pageWindow.ShowDialog() == true)
{
- channelsCanvas.Width = ActualWidth;
- canvasScrollViewer.Width = ActualWidth;
+ PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
+ IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.INHIBIT, P25Defines.WUID_FNE, uint.Parse(pageWindow.DstId));
+
+ RemoteCallData callData = new RemoteCallData
+ {
+ SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
+ DstId = uint.Parse(pageWindow.DstId),
+ LCO = P25Defines.TSBK_IOSP_EXT_FNCT
+ };
+
+ byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+
+ extFunc.Encode(ref tsbk);
+
+ handler.SendP25TSBK(callData, tsbk);
}
- else
- canvasScrollViewer.Width = Width - widthOffset;
+ }
- if (ActualHeight > channelsCanvas.ActualHeight)
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void UninhibitRID_Click(object sender, RoutedEventArgs e)
+ {
+ DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
+ pageWindow.Owner = this;
+ pageWindow.Title = "Uninhibit Subscriber";
+
+ if (pageWindow.ShowDialog() == true)
{
- channelsCanvas.Height = ActualHeight;
- canvasScrollViewer.Height = ActualHeight;
+ PeerSystem handler = fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
+ IOSP_EXT_FNCT extFunc = new IOSP_EXT_FNCT((ushort)ExtendedFunction.UNINHIBIT, P25Defines.WUID_FNE, uint.Parse(pageWindow.DstId));
+
+ RemoteCallData callData = new RemoteCallData
+ {
+ SrcId = uint.Parse(pageWindow.RadioSystem.Rid),
+ DstId = uint.Parse(pageWindow.DstId),
+ LCO = P25Defines.TSBK_IOSP_EXT_FNCT
+ };
+
+ byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES];
+
+ extFunc.Encode(ref tsbk);
+
+ handler.SendP25TSBK(callData, tsbk);
}
- else
- canvasScrollViewer.Height = Height - heightOffset;
+ }
- if (WindowState == WindowState.Maximized)
- ResizeCanvasToWindow_Click(sender, e);
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async void ManualPage_Click(object sender, RoutedEventArgs e)
+ {
+ QuickCallPage pageWindow = new QuickCallPage();
+ pageWindow.Owner = this;
+
+ if (pageWindow.ShowDialog() == true)
+ {
+ foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
+ {
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+
+ if (channel.PageState)
+ {
+ ToneGenerator generator = new ToneGenerator();
+
+ double toneADuration = 1.0;
+ double toneBDuration = 3.0;
+
+ byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), toneADuration);
+ byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.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 = 320;
+
+ int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
+
+ Task.Run(() =>
+ {
+ //_waveProvider.ClearBuffer();
+ 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 (chunk.Length == 320)
+ P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
+ }
+ });
+
+ double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 750;
+ await Task.Delay((int)totalDurationMs + 4000);
+
+ handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
+
+ Dispatcher.Invoke(() =>
+ {
+ //channel.PageState = false; // TODO: Investigate
+ channel.PageSelectButton.Background = ChannelBox.GRAY_GRADIENT;
+ });
+ }
+ }
+ }
+ }
+
+ /** Widget Controls */
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void ChannelBox_HoldChannelButtonClicked(object sender, ChannelBox e)
+ {
+ if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
+ return;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void ChannelBox_PageButtonClicked(object sender, ChannelBox e)
+ {
+ if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
+ return;
+
+ Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+
+ if (e.PageState)
+ handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), true);
else
- settingsManager.Maximized = false;
+ handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
+ {
+ if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
+ return;
+
+ Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
+ PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+
+ if (!e.IsSelected)
+ return;
- settingsManager.CanvasWidth = channelsCanvas.ActualWidth;
- settingsManager.CanvasHeight = channelsCanvas.ActualHeight;
+ FneUtils.Memset(e.mi, 0x00, P25Defines.P25_MI_LENGTH);
- settingsManager.WindowWidth = ActualWidth;
- settingsManager.WindowHeight = ActualHeight;
+ uint srcId = uint.Parse(system.Rid);
+ uint dstId = uint.Parse(cpgChannel.Tgid);
+
+ if (e.PttState)
+ {
+ e.TxStreamId = handler.NewStreamId();
+ handler.SendP25TDU(srcId, dstId, true);
+ }
+ else
+ handler.SendP25TDU(srcId, dstId, false);
}
///
@@ -1220,41 +1251,19 @@ namespace dvmconsole
///
///
///
- private void MainWindow_Loaded(object sender, RoutedEventArgs e)
+ private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
- const double widthOffset = 16;
- const double heightOffset = 115;
-
- if (!string.IsNullOrEmpty(settingsManager.LastCodeplugPath) && File.Exists(settingsManager.LastCodeplugPath))
- LoadCodeplug(settingsManager.LastCodeplugPath);
- else
- GenerateChannelWidgets();
+ if (!isEditMode || !(sender is UIElement element)) return;
- if (settingsManager.Maximized)
- {
- windowLoaded = true;
- WindowState = WindowState.Maximized;
- //Application.Current.MainWindow.WindowState = WindowState.Maximized;
- ResizeCanvasToWindow_Click(sender, e);
- }
- else
- {
- Width = settingsManager.WindowWidth;
- channelsCanvas.Width = settingsManager.CanvasWidth;
- if (settingsManager.CanvasWidth > settingsManager.WindowWidth)
- canvasScrollViewer.Width = Width - widthOffset;
- else
- canvasScrollViewer.Width = Width;
+ draggedElement = element;
+ startPoint = e.GetPosition(channelsCanvas);
+ offsetX = startPoint.X - Canvas.GetLeft(draggedElement);
+ offsetY = startPoint.Y - Canvas.GetTop(draggedElement);
+ isDragging = true;
- Height = settingsManager.WindowHeight;
- channelsCanvas.Height = settingsManager.CanvasHeight;
- if (settingsManager.CanvasHeight > settingsManager.WindowHeight)
- canvasScrollViewer.Height = Height - heightOffset;
- else
- canvasScrollViewer.Height = Height;
+ Cursor = Cursors.ScrollAll;
- windowLoaded = true;
- }
+ element.CaptureMouse();
}
///
@@ -1262,43 +1271,45 @@ namespace dvmconsole
///
///
///
- private async void OnHoldTimerElapsed(object sender, ElapsedEventArgs e)
+ private void ChannelBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
- foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
- {
- if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
- continue;
-
- Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
- Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
- PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
+ if (!isEditMode || !isDragging || draggedElement == null)
+ return;
- if (channel.HoldState && !channel.IsReceiving && !channel.PttState && !channel.PageState)
- {
- handler.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), true);
- await Task.Delay(1000);
+ Cursor = Cursors.Arrow;
- SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/hold.wav"), true);
- }
- }
+ isDragging = false;
+ draggedElement.ReleaseMouseCapture();
+ draggedElement = null;
}
///
///
///
+ ///
///
- protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
+ private void ChannelBox_MouseMove(object sender, MouseEventArgs e)
{
- if (!noSaveSettingsOnClose)
- {
- if (WindowState == WindowState.Maximized)
- settingsManager.Maximized = true;
+ if (!isEditMode || !isDragging || draggedElement == null)
+ return;
- settingsManager.SaveSettings();
- }
+ Point currentPosition = e.GetPosition(channelsCanvas);
- base.OnClosing(e);
- Application.Current.Shutdown();
+ // 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;
+
+ // 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));
+
+ // Apply snapped 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);
}
///
@@ -1306,13 +1317,26 @@ namespace dvmconsole
///
///
///
- private void ClearEmergency_Click(object sender, RoutedEventArgs e)
+ private void SystemStatusBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) => ChannelBox_MouseLeftButtonDown(sender, e);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void SystemStatusBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
- emergencyAlertPlayback.Stop();
- flashingManager.Stop();
+ if (!isEditMode)
+ return;
- foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
- channel.Emergency = false;
+ if (sender is SystemStatusBox systemStatusBox)
+ {
+ double x = Canvas.GetLeft(systemStatusBox);
+ double y = Canvas.GetTop(systemStatusBox);
+ settingsManager.SystemStatusPositions[systemStatusBox.SystemName] = new ChannelPosition { X = x, Y = y };
+
+ ChannelBox_MouseLeftButtonUp(sender, e);
+ }
}
///
@@ -1320,37 +1344,39 @@ namespace dvmconsole
///
///
///
- private void btnAlert1_Click(object sender, RoutedEventArgs e)
- {
- Dispatcher.Invoke(() => {
- SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert1.wav"));
- });
- }
+ private void SystemStatusBox_MouseMove(object sender, MouseEventArgs e) => ChannelBox_MouseMove(sender, e);
///
///
///
///
///
- private void btnAlert2_Click(object sender, RoutedEventArgs e)
+ private void AlertTone_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
- Dispatcher.Invoke(() =>
+ if (!isEditMode) return;
+
+ if (sender is AlertTone alertTone)
{
- SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert2.wav"));
- });
+ double x = Canvas.GetLeft(alertTone);
+ double y = Canvas.GetTop(alertTone);
+ settingsManager.UpdateAlertTonePosition(alertTone.AlertFilePath, x, y);
+ }
}
+ /** WPF Ribbon Controls */
+
///
///
///
///
///
- private void btnAlert3_Click(object sender, RoutedEventArgs e)
+ private void ClearEmergency_Click(object sender, RoutedEventArgs e)
{
- Dispatcher.Invoke(() =>
- {
- SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert3.wav"));
- });
+ emergencyAlertPlayback.Stop();
+ flashingManager.Stop();
+
+ foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels())
+ channel.Emergency = false;
}
///
@@ -1374,7 +1400,7 @@ namespace dvmconsole
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
PeerSystem handler = fneSystemManager.GetFneSystem(system.Name);
- channel.txStreamId = handler.NewStreamId();
+ channel.TxStreamId = handler.NewStreamId();
if (globalPttState)
{
@@ -1399,6 +1425,44 @@ namespace dvmconsole
}
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void btnAlert1_Click(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.Invoke(() => {
+ SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert1.wav"));
+ });
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void btnAlert2_Click(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert2.wav"));
+ });
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void btnAlert3_Click(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ SendAlertTone(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Audio/alert3.wav"));
+ });
+ }
+
///
///
///
@@ -1425,6 +1489,31 @@ namespace dvmconsole
}
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void KeyStatus_Click(object sender, RoutedEventArgs e)
+ {
+ KeyStatusWindow keyStatus = new KeyStatusWindow(Codeplug, this);
+ keyStatus.Owner = this;
+ keyStatus.Show();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void CallHist_Click(object sender, RoutedEventArgs e)
+ {
+ callHistoryWindow.Owner = this;
+ callHistoryWindow.Show();
+ }
+
+ /** fnecore Hooks / Helpers */
+
///
/// Helper to encode and transmit PCM audio as P25 IMBE frames.
///
@@ -1511,9 +1600,7 @@ namespace dvmconsole
Random random = new Random();
for (int i = 0; i < P25Defines.P25_MI_LENGTH; i++)
- {
channel.mi[i] = (byte)random.Next(0x00, 0x100);
- }
}
channel.Crypter.Prepare(cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi);
@@ -1618,7 +1705,7 @@ namespace dvmconsole
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU1, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi);
handler.CreateP25LDU1Message(channel.netLDU1, ref payload, srcId, dstId);
- peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.txStreamId);
+ peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.TxStreamId);
}
// send P25 LDU2
@@ -1636,7 +1723,7 @@ namespace dvmconsole
handler.CreateNewP25MessageHdr((byte)P25DUID.LDU2, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi);
handler.CreateP25LDU2Message(channel.netLDU2, ref payload, new CryptoParams { AlgId = cpgChannel.GetAlgoId(), KeyId = cpgChannel.GetKeyId(), MI = channel.mi });
- peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.txStreamId);
+ peer.SendMaster(new Tuple(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, pktSeq, channel.TxStreamId);
}
channel.p25SeqNo++;
@@ -1781,18 +1868,6 @@ namespace dvmconsole
}
}
- ///
- ///
- ///
- ///
- ///
- private void KeyStatus_Click(object sender, RoutedEventArgs e)
- {
- KeyStatusWindow keyStatus = new KeyStatusWindow(Codeplug, this);
- keyStatus.Owner = this;
- keyStatus.Show();
- }
-
///
/// Event handler used to process incoming P25 data.
///
@@ -2040,26 +2115,5 @@ namespace dvmconsole
}
});
}
-
- ///
- ///
- ///
- ///
- ///
- private void CallHist_Click(object sender, RoutedEventArgs e)
- {
- callHistoryWindow.Owner = this;
- callHistoryWindow.Show();
- }
-
- ///
- ///
- ///
- ///
- ///
- private void Exit_Click(object sender, RoutedEventArgs e)
- {
- Application.Current.Shutdown();
- }
} // public partial class MainWindow : Window
} // namespace dvmconsole
diff --git a/dvmconsole/QuickCallPage.xaml.cs b/dvmconsole/QuickCallPage.xaml.cs
index 322b70f..a82b896 100644
--- a/dvmconsole/QuickCallPage.xaml.cs
+++ b/dvmconsole/QuickCallPage.xaml.cs
@@ -20,7 +20,13 @@ namespace dvmconsole
///
public partial class QuickCallPage : Window
{
+ ///
+ /// Tone A.
+ ///
public string ToneA;
+ ///
+ /// Tone B.
+ ///
public string ToneB;
/*
@@ -35,6 +41,8 @@ namespace dvmconsole
InitializeComponent();
}
+ /** WPF Events */
+
///
///
///
diff --git a/dvmconsole/SettingsManager.cs b/dvmconsole/SettingsManager.cs
index fe79d60..94d749c 100644
--- a/dvmconsole/SettingsManager.cs
+++ b/dvmconsole/SettingsManager.cs
@@ -82,6 +82,10 @@ namespace dvmconsole
///
///
///
+ public bool DarkMode { get; set; } = false;
+ ///
+ ///
+ ///
public double WindowWidth { get; set; } = MainWindow.MIN_WIDTH;
///
///
@@ -128,6 +132,7 @@ namespace dvmconsole
AlertTonePositions = loadedSettings.AlertTonePositions ?? new Dictionary();
ChannelOutputDevices = loadedSettings.ChannelOutputDevices ?? new Dictionary();
Maximized = loadedSettings.Maximized;
+ DarkMode = loadedSettings.DarkMode;
WindowWidth = loadedSettings.WindowWidth;
if (WindowWidth == 0)
WindowWidth = MainWindow.MIN_WIDTH;
diff --git a/dvmconsole/WidgetSelectionWindow.xaml.cs b/dvmconsole/WidgetSelectionWindow.xaml.cs
index e09f6f4..af827aa 100644
--- a/dvmconsole/WidgetSelectionWindow.xaml.cs
+++ b/dvmconsole/WidgetSelectionWindow.xaml.cs
@@ -25,15 +25,15 @@ namespace dvmconsole
*/
///
- ///
+ /// Flag indicating whether or not the system status widgets appear.
///
public bool ShowSystemStatus { get; private set; } = true;
///
- ///
+ /// Flag indicating whether or not the channel widgets appear.
///
public bool ShowChannels { get; private set; } = true;
///
- ///
+ /// Flag indicating whether or not alert tone widgets appear.
///
public bool ShowAlertTones { get; private set; } = true;
@@ -49,6 +49,8 @@ namespace dvmconsole
InitializeComponent();
}
+ /** WPF Events */
+
///
///
///