using VoiceCat.Interop; namespace VoiceCat.App.Forms; /// /// Create or edit a channel, including its full per-channel Opus audio config. /// Returns DialogResult.OK with Result set. /// public sealed class ChannelEditDialog : Form { private readonly bool _isCreate; private readonly uint _editingId; private readonly List _channels; private TextBox _txtName = null!; private TextBox _txtTopic = null!; private ComboBox _cboParent = null!; private NumericUpDown _numMaxUsers = null!; private NumericUpDown _numSortOrder = null!; private CheckBox _chkPassword = null!; private TextBox _txtPassword = null!; private ComboBox _cboMode = null!; private NumericUpDown _numSampleRate = null!; private NumericUpDown _numBitrate = null!; private NumericUpDown _numFrameMs = null!; private ComboBox _cboApplication = null!; private CheckBox _chkFec = null!; private NumericUpDown _numExpectedLoss = null!; private CheckBox _chkDtx = null!; private NumericUpDown _numComplexity = null!; public ChannelEditInfo? Result { get; private set; } public ChannelEditDialog(IEnumerable channels, ChannelEditInfo? existing = null) { _isCreate = existing is null; _editingId = existing?.Id ?? 0; _channels = channels.Where(c => c.Id != _editingId).ToList(); Text = _isCreate ? "Create channel" : "Edit channel"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; StartPosition = FormStartPosition.CenterParent; AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(520, 520); var tabs = new TabControl { Dock = DockStyle.Fill, TabIndex = 0, }; var pageGeneral = new TabPage("General"); BuildGeneralPage(pageGeneral, existing); var pageAudio = new TabPage("Audio config"); BuildAudioPage(pageAudio, existing?.Audio); tabs.TabPages.Add(pageGeneral); tabs.TabPages.Add(pageAudio); var btnOk = new Button { Text = "&OK", DialogResult = DialogResult.OK, Location = new Point(350, 486), Size = new Size(75, 27), TabIndex = 100, }; var btnCancel = new Button { Text = "&Cancel", DialogResult = DialogResult.Cancel, Location = new Point(431, 486), Size = new Size(75, 27), TabIndex = 101, }; AcceptButton = btnOk; CancelButton = btnCancel; Controls.Add(tabs); Controls.Add(btnOk); Controls.Add(btnCancel); btnOk.Click += BtnOk_Click; } private void BuildGeneralPage(TabPage page, ChannelEditInfo? existing) { int y = 16; int labelWidth = 110; int inputX = 128; AddLabel(page, "&Name:", 12, y, labelWidth); _txtName = new TextBox { Location = new Point(inputX, y - 2), Size = new Size(360, 23), Text = existing?.Name ?? "", TabIndex = 1, }; page.Controls.Add(_txtName); y += 36; AddLabel(page, "&Topic:", 12, y, labelWidth); _txtTopic = new TextBox { Location = new Point(inputX, y - 2), Size = new Size(360, 23), Text = existing?.Topic ?? "", TabIndex = 2, }; page.Controls.Add(_txtTopic); y += 36; AddLabel(page, "&Parent channel:", 12, y, labelWidth); _cboParent = new ComboBox { Location = new Point(inputX, y - 2), Size = new Size(360, 23), DropDownStyle = ComboBoxStyle.DropDownList, TabIndex = 3, }; _cboParent.Items.Add(new ChannelItem("(root)", 0)); foreach (var ch in _channels.OrderBy(c => c.Name)) _cboParent.Items.Add(new ChannelItem(ch.Name, ch.Id)); SelectParent(existing?.ParentId ?? 0); page.Controls.Add(_cboParent); y += 36; AddLabel(page, "&Max users:", 12, y, labelWidth); _numMaxUsers = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = 10000, Value = existing?.MaxUsers ?? 0, TabIndex = 4, }; page.Controls.Add(_numMaxUsers); AddLabel(page, "(0 = unlimited)", inputX + 128, y, 100); y += 36; AddLabel(page, "&Sort order:", 12, y, labelWidth); _numSortOrder = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = uint.MaxValue, Value = existing?.SortOrder ?? 0, TabIndex = 5, }; page.Controls.Add(_numSortOrder); y += 36; _chkPassword = new CheckBox { Text = "&Password protected", Location = new Point(inputX, y), AutoSize = true, Checked = existing?.PasswordProtected ?? false, TabIndex = 6, }; page.Controls.Add(_chkPassword); y += 28; _txtPassword = new TextBox { Location = new Point(inputX, y), Size = new Size(360, 23), UseSystemPasswordChar = true, Enabled = _chkPassword.Checked, Text = existing?.Password ?? "", TabIndex = 7, }; page.Controls.Add(_txtPassword); _chkPassword.CheckedChanged += (_, _) => _txtPassword.Enabled = _chkPassword.Checked; } private void BuildAudioPage(TabPage page, AudioConfigInfo? audio) { audio ??= new AudioConfigInfo(0, false, 48000, 0, 20, 0, true, 0, false, 10); int y = 16; int labelWidth = 150; int inputX = 168; AddLabel(page, "Codec (0 = Opus):", 12, y, labelWidth); var numCodec = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = 0, Value = audio.Codec, Enabled = false, }; page.Controls.Add(numCodec); y += 34; AddLabel(page, "&Mode:", 12, y, labelWidth); _cboMode = new ComboBox { Location = new Point(inputX, y - 2), Size = new Size(160, 23), DropDownStyle = ComboBoxStyle.DropDownList, TabIndex = 10, }; _cboMode.Items.AddRange(["Mono", "Stereo"]); _cboMode.SelectedIndex = audio.Stereo ? 1 : 0; page.Controls.Add(_cboMode); y += 34; AddLabel(page, "Sample &rate (Hz):", 12, y, labelWidth); _numSampleRate = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 8000, Maximum = 96000, Value = audio.SampleRate, TabIndex = 11, }; page.Controls.Add(_numSampleRate); y += 34; AddLabel(page, "&Bitrate (bps, 0 = default):", 12, y, labelWidth); _numBitrate = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = 512000, Increment = 1000, Value = audio.BitrateBps, TabIndex = 12, }; page.Controls.Add(_numBitrate); y += 34; AddLabel(page, "Frame &ms:", 12, y, labelWidth); _numFrameMs = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 5, Maximum = 120, Value = audio.FrameMs, TabIndex = 13, }; page.Controls.Add(_numFrameMs); y += 34; AddLabel(page, "&Application:", 12, y, labelWidth); _cboApplication = new ComboBox { Location = new Point(inputX, y - 2), Size = new Size(160, 23), DropDownStyle = ComboBoxStyle.DropDownList, TabIndex = 14, }; _cboApplication.Items.AddRange(["VoIP", "Audio", "Low delay"]); _cboApplication.SelectedIndex = (int)Math.Min(2, audio.Application); page.Controls.Add(_cboApplication); y += 34; AddLabel(page, "Expected packet loss (%):", 12, y, labelWidth); _numExpectedLoss = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = 100, Value = audio.ExpectedPacketLoss, TabIndex = 15, }; page.Controls.Add(_numExpectedLoss); y += 34; AddLabel(page, "Com&plexity (0–10):", 12, y, labelWidth); _numComplexity = new NumericUpDown { Location = new Point(inputX, y - 2), Size = new Size(120, 23), Minimum = 0, Maximum = 10, Value = audio.Complexity, TabIndex = 16, }; page.Controls.Add(_numComplexity); y += 34; _chkFec = new CheckBox { Text = "&FEC", Location = new Point(inputX, y), AutoSize = true, Checked = audio.Fec, TabIndex = 17, }; page.Controls.Add(_chkFec); y += 28; _chkDtx = new CheckBox { Text = "&DTX", Location = new Point(inputX, y), AutoSize = true, Checked = audio.Dtx, TabIndex = 18, }; page.Controls.Add(_chkDtx); } private static void AddLabel(Control parent, string text, int x, int y, int width) { parent.Controls.Add(new Label { Text = text, Location = new Point(x, y), Size = new Size(width, 17), TextAlign = System.Drawing.ContentAlignment.MiddleLeft, }); } private void SelectParent(uint parentId) { for (int i = 0; i < _cboParent.Items.Count; i++) { if (_cboParent.Items[i] is ChannelItem item && item.Id == parentId) { _cboParent.SelectedIndex = i; return; } } _cboParent.SelectedIndex = 0; } private void BtnOk_Click(object? sender, EventArgs e) { string name = _txtName.Text.Trim(); if (string.IsNullOrEmpty(name)) { MessageBox.Show(this, "Channel name is required.", "VoiceCat", MessageBoxButtons.OK, MessageBoxIcon.Warning); DialogResult = DialogResult.None; return; } uint parentId = (_cboParent.SelectedItem as ChannelItem)?.Id ?? 0; var audio = new AudioConfigInfo( Codec: 0, Stereo: _cboMode.SelectedIndex == 1, SampleRate: (uint)_numSampleRate.Value, BitrateBps: (uint)_numBitrate.Value, FrameMs: (uint)_numFrameMs.Value, Application: (uint)_cboApplication.SelectedIndex, Fec: _chkFec.Checked, ExpectedPacketLoss: (uint)_numExpectedLoss.Value, Dtx: _chkDtx.Checked, Complexity: (uint)_numComplexity.Value); Result = new ChannelEditInfo( Id: _editingId, ParentId: parentId, Name: name, Topic: _txtTopic.Text.Trim(), PasswordProtected: _chkPassword.Checked, Password: _chkPassword.Checked ? _txtPassword.Text : null, MaxUsers: (uint)_numMaxUsers.Value, SortOrder: (uint)_numSortOrder.Value, Audio: audio); } private sealed class ChannelItem(string name, uint id) { public uint Id { get; } = id; public override string ToString() => name; } }