You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
20 KiB
644 lines
20 KiB
// SPDX-License-Identifier: AGPL-3.0-only
|
|
/**
|
|
* Digital Voice Modem - Desktop Dispatch Console
|
|
* AGPLv3 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* @package DVM / Desktop Dispatch Console
|
|
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
|
|
*
|
|
* Copyright (C) 2025 Caleb, K4PHP
|
|
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
using fnecore.P25;
|
|
|
|
namespace dvmconsole.Controls
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public partial class ChannelBox : UserControl, INotifyPropertyChanged
|
|
{
|
|
public readonly static LinearGradientBrush GRAY_GRADIENT;
|
|
public readonly static LinearGradientBrush DARK_GRAY_GRADIENT; // Delected/Disconnected Color
|
|
public readonly static LinearGradientBrush BLUE_GRADIENT; // Selected Channel Color
|
|
public readonly static LinearGradientBrush RED_GRADIENT; // Playback Selected Color
|
|
public readonly static LinearGradientBrush GREEN_GRADIENT; // Clear Rx Color
|
|
public readonly static LinearGradientBrush ORANGE_GRADIENT; // Encrypted Rx Color
|
|
|
|
private readonly SelectedChannelsManager selectedChannelsManager;
|
|
private readonly AudioManager audioManager;
|
|
|
|
private bool pttState;
|
|
private bool pageState;
|
|
private bool holdState;
|
|
private bool emergency;
|
|
private string lastSrcId = "0";
|
|
private double volume = 1.0;
|
|
private bool isSelected;
|
|
|
|
public FlashingBackgroundManager flashingBackgroundManager;
|
|
|
|
public byte[] netLDU1 = new byte[9 * 25];
|
|
public byte[] netLDU2 = new byte[9 * 25];
|
|
|
|
public int p25N { get; set; } = 0;
|
|
public int p25SeqNo { get; set; } = 0;
|
|
public int p25Errs { get; set; } = 0;
|
|
|
|
public byte[] mi = new byte[P25Defines.P25_MI_LENGTH]; // Message Indicator
|
|
public byte algId = 0; // Algorithm ID
|
|
public ushort kId = 0; // Key ID
|
|
|
|
public List<byte[]> chunkedPcm = new List<byte[]>();
|
|
|
|
public bool ExternalVocoderEnabled = false;
|
|
public AmbeVocoder ExtFullRateVocoder = null;
|
|
public AmbeVocoder ExtHalfRateVocoder = null;
|
|
public MBEEncoder Encoder = null;
|
|
public MBEDecoder Decoder = null;
|
|
|
|
public MBEToneDetector ToneDetector = new MBEToneDetector();
|
|
|
|
public P25Crypto Crypter = new P25Crypto();
|
|
|
|
private bool pttToggleMode = false;
|
|
|
|
/*
|
|
** Properties
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Textual name of channel.
|
|
/// </summary>
|
|
public string ChannelName { get; set; }
|
|
/// <summary>
|
|
/// Textual name of system channel belongs to.
|
|
/// </summary>
|
|
public string SystemName { get; set; }
|
|
/// <summary>
|
|
/// Destination ID.
|
|
/// </summary>
|
|
public string DstId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="Brush"/> that fills the area between the bounds of the control border.
|
|
/// </summary>
|
|
public new Brush Background
|
|
{
|
|
get => ControlBorder.Background;
|
|
set => ControlBorder.Background = value;
|
|
}
|
|
|
|
/*
|
|
** Events
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Event action that handles the PTT button being clicked.
|
|
/// </summary>
|
|
public event EventHandler<ChannelBox> PTTButtonClicked;
|
|
/// <summary>
|
|
/// Event action that handles the PTT button being pressed.
|
|
/// </summary>
|
|
public event EventHandler<ChannelBox> PTTButtonPressed;
|
|
/// <summary>
|
|
/// Event action that handles the PTT button being released.
|
|
/// </summary>
|
|
public event EventHandler<ChannelBox> PTTButtonReleased;
|
|
/// <summary>
|
|
/// Event action that handles the page button being clicked.
|
|
/// </summary>
|
|
public event EventHandler<ChannelBox> PageButtonClicked;
|
|
/// <summary>
|
|
/// Event action that handles the hold channel button being clicked.
|
|
/// </summary>
|
|
public event EventHandler<ChannelBox> HoldChannelButtonClicked;
|
|
/// <summary>
|
|
/// Event action that occurs when a property changes on this control.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Flag indicating whether or not this channel is receiving.
|
|
/// </summary>
|
|
public bool IsReceiving { get; set; } = false;
|
|
/// <summary>
|
|
/// Flag indicating whether or not this channel is receiving encrypted.
|
|
/// </summary>
|
|
public bool IsReceivingEncrypted { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Last Source ID received.
|
|
/// </summary>
|
|
public string LastSrcId
|
|
{
|
|
get => lastSrcId;
|
|
set
|
|
{
|
|
if (lastSrcId != value)
|
|
{
|
|
lastSrcId = value;
|
|
OnPropertyChanged(nameof(LastSrcId));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag indicating the current PTT state of this channel.
|
|
/// </summary>
|
|
public bool PttState
|
|
{
|
|
get => pttState;
|
|
set
|
|
{
|
|
pttState = value;
|
|
UpdatePTTColor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag indicating the current page state of this channel.
|
|
/// </summary>
|
|
public bool PageState
|
|
{
|
|
get => pageState;
|
|
set
|
|
{
|
|
pageState = value;
|
|
UpdatePageColor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag indicating the hold state of this channel.
|
|
/// </summary>
|
|
public bool HoldState
|
|
{
|
|
get => holdState;
|
|
set
|
|
{
|
|
holdState = value;
|
|
UpdateHoldColor();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag indicating the channel is in toggle PTT or regular PTT.
|
|
/// </summary>
|
|
public bool PTTToggleMode
|
|
{
|
|
get => pttToggleMode;
|
|
set => pttToggleMode = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag indicating the emergency state of this channel.
|
|
/// </summary>
|
|
public bool Emergency
|
|
{
|
|
get => emergency;
|
|
set
|
|
{
|
|
emergency = value;
|
|
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
if (value)
|
|
flashingBackgroundManager.Start();
|
|
else
|
|
flashingBackgroundManager.Stop();
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public string VoiceChannel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag indicating whether or not edit mode is enabled.
|
|
/// </summary>
|
|
public bool IsEditMode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag indicating whether or not this channel is selected.
|
|
/// </summary>
|
|
public bool IsSelected
|
|
{
|
|
get => isSelected;
|
|
set
|
|
{
|
|
isSelected = value;
|
|
if (!isSelected)
|
|
DisableControls();
|
|
else
|
|
EnableControls();
|
|
UpdateBackground();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current volume for this channel.
|
|
/// </summary>
|
|
public double Volume
|
|
{
|
|
get => volume;
|
|
set
|
|
{
|
|
if (volume != value)
|
|
{
|
|
volume = value;
|
|
OnPropertyChanged(nameof(Volume));
|
|
audioManager.SetTalkgroupVolume(DstId, (float)value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public uint TxStreamId { get; internal set; }
|
|
|
|
/*
|
|
** Methods
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Static initialize for the <see cref="ChannelBox" class. />
|
|
/// </summary>
|
|
static ChannelBox()
|
|
{
|
|
GRAY_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
GRAY_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0F0F0F0"), 0.485));
|
|
GRAY_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0C2C2C2"), 0.517));
|
|
|
|
DARK_GRAY_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
DARK_GRAY_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0979797"), 0.485));
|
|
DARK_GRAY_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0686767"), 0.517));
|
|
|
|
BLUE_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
BLUE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0150189"), 0.485));
|
|
BLUE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F00B004B"), 0.517));
|
|
|
|
RED_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
RED_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0FF0000"), 0.485));
|
|
RED_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0C60000"), 0.517));
|
|
|
|
GREEN_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
GREEN_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F000AF00"), 0.485));
|
|
GREEN_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0008E00"), 0.517));
|
|
|
|
ORANGE_GRADIENT = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0.5, 0),
|
|
EndPoint = new Point(0.5, 1)
|
|
};
|
|
|
|
ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0FFAF00"), 0.485));
|
|
ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0C68700"), 0.517));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ChannelBox"/> class.
|
|
/// </summary>
|
|
/// <param name="selectedChannelsManager"></param>
|
|
/// <param name="audioManager"></param>
|
|
/// <param name="channelName"></param>
|
|
/// <param name="systemName"></param>
|
|
/// <param name="dstId"></param>
|
|
/// <param name="pttToggleMode"></param>
|
|
public ChannelBox(SelectedChannelsManager selectedChannelsManager, AudioManager audioManager, string channelName, string systemName, string dstId, bool pttToggleMode = false)
|
|
{
|
|
InitializeComponent();
|
|
|
|
DataContext = this;
|
|
|
|
this.selectedChannelsManager = selectedChannelsManager;
|
|
this.audioManager = audioManager;
|
|
|
|
flashingBackgroundManager = new FlashingBackgroundManager(this);
|
|
|
|
ChannelName = channelName;
|
|
DstId = dstId;
|
|
SystemName = $"System: {systemName}";
|
|
LastSrcId = $"Last ID: {LastSrcId}";
|
|
|
|
UpdateBackground();
|
|
|
|
MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
|
|
|
|
PttButton.PreviewMouseLeftButtonDown += PttButton_MouseLeftButtonDown;
|
|
PttButton.PreviewMouseLeftButtonUp += PttButton_MouseLeftButtonUp;
|
|
PttButton.MouseRightButtonDown += PttButton_MouseRightButtonDown;
|
|
|
|
this.pttToggleMode = pttToggleMode;
|
|
|
|
PttButton.Background = GRAY_GRADIENT;
|
|
PageSelectButton.Background = GRAY_GRADIENT;
|
|
ChannelMarkerBtn.Background = GRAY_GRADIENT;
|
|
|
|
DisableControls();
|
|
|
|
if (SystemName == MainWindow.PLAYBACKSYS || ChannelName == MainWindow.PLAYBACKCHNAME || DstId == MainWindow.PLAYBACKTG)
|
|
{
|
|
PttButton.IsEnabled = false;
|
|
PageSelectButton.IsEnabled = false;
|
|
ChannelMarkerBtn.IsEnabled = false;
|
|
}
|
|
|
|
// initialize external AMBE vocoder
|
|
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
|
|
UriBuilder uri = new UriBuilder(codeBase);
|
|
string path = Uri.UnescapeDataString(uri.Path);
|
|
|
|
// if the assembly executing directory contains the external DVSI USB-3000 interface DLL
|
|
// setup the external vocoder code
|
|
if (File.Exists(Path.Combine(new string[] { Path.GetDirectoryName(path), "AMBE.DLL" })))
|
|
{
|
|
ExternalVocoderEnabled = true;
|
|
ExtFullRateVocoder = new AmbeVocoder();
|
|
ExtHalfRateVocoder = new AmbeVocoder(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to enable controls.
|
|
/// </summary>
|
|
private void EnableControls()
|
|
{
|
|
PttButton.IsEnabled = true;
|
|
ChannelMarkerBtn.IsEnabled = true;
|
|
PageSelectButton.IsEnabled = true;
|
|
|
|
VolumeSlider.IsEnabled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to disable controls.
|
|
/// </summary>
|
|
private void DisableControls()
|
|
{
|
|
PttButton.IsEnabled = false;
|
|
ChannelMarkerBtn.IsEnabled = false;
|
|
PageSelectButton.IsEnabled = false;
|
|
|
|
VolumeSlider.IsEnabled = false;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private void UpdatePTTColor()
|
|
{
|
|
if (IsEditMode)
|
|
return;
|
|
|
|
if (PttState)
|
|
PttButton.Background = RED_GRADIENT;
|
|
else
|
|
PttButton.Background = GRAY_GRADIENT;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private void UpdatePageColor()
|
|
{
|
|
if (IsEditMode)
|
|
return;
|
|
|
|
if (PageState)
|
|
PageSelectButton.Background = ORANGE_GRADIENT;
|
|
else
|
|
PageSelectButton.Background = GRAY_GRADIENT;
|
|
}
|
|
|
|
private void UpdateHoldColor()
|
|
{
|
|
if (IsEditMode)
|
|
return;
|
|
|
|
if (HoldState)
|
|
ChannelMarkerBtn.Background = ORANGE_GRADIENT;
|
|
else
|
|
ChannelMarkerBtn.Background = GRAY_GRADIENT;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private void UpdateBackground()
|
|
{
|
|
if (SystemName == MainWindow.PLAYBACKSYS || ChannelName == MainWindow.PLAYBACKCHNAME || DstId == MainWindow.PLAYBACKTG)
|
|
{
|
|
ControlBorder.Background = IsSelected ? RED_GRADIENT : DARK_GRAY_GRADIENT;
|
|
return;
|
|
}
|
|
|
|
ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="propertyName"></param>
|
|
protected virtual void OnPropertyChanged(string propertyName)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
/** WPF Events */
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void PttButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!IsSelected)
|
|
return;
|
|
|
|
PttState = !PttState;
|
|
PTTButtonClicked?.Invoke(sender, this);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
private async void PttButton_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (pttToggleMode)
|
|
return;
|
|
|
|
if (PttState)
|
|
await Task.Delay(500);
|
|
|
|
PttButton_Click(sender, e);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
private async void PttButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (!IsSelected)
|
|
return;
|
|
|
|
if (PttState)
|
|
await Task.Delay(500);
|
|
|
|
if (pttToggleMode)
|
|
PttButton_Click(sender, e);
|
|
else
|
|
{
|
|
PttState = true;
|
|
PTTButtonPressed?.Invoke(sender, this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
private void PttButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (pttToggleMode)
|
|
return;
|
|
if (!IsSelected)
|
|
return;
|
|
|
|
PTTButtonReleased?.Invoke(sender, this);
|
|
PttState = false;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void PageSelectButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!IsSelected)
|
|
return;
|
|
|
|
PageState = !PageState;
|
|
PageButtonClicked?.Invoke(sender, this);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
|
{
|
|
Volume = e.NewValue;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void ChannelMarkerBtn_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!IsSelected)
|
|
return;
|
|
|
|
HoldState = !HoldState;
|
|
HoldChannelButtonClicked.Invoke(sender, this);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void PttButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
|
|
{
|
|
if (!IsSelected || PttState)
|
|
return;
|
|
|
|
((Button)sender).Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF3FA0FF"));
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void PttButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
|
|
{
|
|
if (!IsSelected || PttState)
|
|
return;
|
|
|
|
((Button)sender).Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFDDDDDD"));
|
|
}
|
|
} // public partial class ChannelBox : UserControl, INotifyPropertyChanged
|
|
} // namespace dvmconsole.Controls
|