Audio input settings (device picker, VAD/PTT mode, VAD sensitivity, mic gain, PTT key) move from the always-visible bottom panel into Settings > Audio..., matching the macOS/iOS pattern. The device ComboBox now uses DataSource + DisplayMember="Name" instead of Items.Add() with no DisplayMember — this fixes both the display bug (was showing the full DeviceInfo record ToString()) and the NVDA silence on dropdown open (DataSource binding exposes proper MSAA text per item). Changes apply live for immediate feedback; Cancel reverts. VoiceSettings gains InputDeviceId to persist the chosen device across sessions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
286 lines
13 KiB
C#
286 lines
13 KiB
C#
namespace VoiceCat.App.Forms;
|
|
|
|
partial class MainForm
|
|
{
|
|
private System.ComponentModel.IContainer components = null!;
|
|
|
|
// Menu bar + toolbar
|
|
private MenuStrip menuStrip = null!;
|
|
private ToolStrip toolStrip = null!;
|
|
private ToolStripButton tsbJoinVoice = null!;
|
|
private ToolStripButton tsbScreenShare = null!;
|
|
|
|
// Status bar
|
|
private Label lblStatus = null!;
|
|
|
|
// Main left/right split
|
|
private SplitContainer splitMain = null!;
|
|
|
|
// Left panel: channel tree on top, user list on bottom
|
|
private SplitContainer splitLeft = null!;
|
|
private Label lblChannels = null!;
|
|
private TreeView tvChannels = null!;
|
|
private Label lblUsers = null!;
|
|
private ListBox lstUsers = null!;
|
|
|
|
// Right panel: unified log, compose row, output volume
|
|
private TableLayoutPanel tblRight = null!;
|
|
private Label lblLog = null!;
|
|
private RichTextBox rtbLog = null!;
|
|
private TableLayoutPanel tblCompose = null!;
|
|
private TextBox txtCompose = null!;
|
|
private Button btnSend = null!;
|
|
private Label lblOutputVolume = null!;
|
|
private TrackBar trkOutputVolume = null!;
|
|
|
|
// Voice control panel (docked Bottom)
|
|
private Panel pnlVoice = null!;
|
|
private FlowLayoutPanel flpVoiceTop = null!;
|
|
private CheckBox chkMute = null!;
|
|
private CheckBox chkDeafen = null!;
|
|
private Label lblLevel = null!;
|
|
private ProgressBar pbLevel = null!;
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing) components?.Dispose();
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
components = new System.ComponentModel.Container();
|
|
|
|
lblStatus = new Label();
|
|
splitMain = new SplitContainer();
|
|
splitLeft = new SplitContainer();
|
|
lblChannels = new Label();
|
|
tvChannels = new TreeView();
|
|
lblUsers = new Label();
|
|
lstUsers = new ListBox();
|
|
tblRight = new TableLayoutPanel();
|
|
lblLog = new Label();
|
|
rtbLog = new RichTextBox();
|
|
tblCompose = new TableLayoutPanel();
|
|
txtCompose = new TextBox();
|
|
btnSend = new Button();
|
|
lblOutputVolume = new Label();
|
|
trkOutputVolume = new TrackBar();
|
|
pnlVoice = new Panel();
|
|
flpVoiceTop = new FlowLayoutPanel();
|
|
chkMute = new CheckBox();
|
|
chkDeafen = new CheckBox();
|
|
lblLevel = new Label();
|
|
pbLevel = new ProgressBar();
|
|
menuStrip = new MenuStrip();
|
|
toolStrip = new ToolStrip();
|
|
tsbJoinVoice = new ToolStripButton();
|
|
tsbScreenShare = new ToolStripButton();
|
|
|
|
// ── Status label ──────────────────────────────────────────────────────
|
|
lblStatus.AccessibleName = "Connection status";
|
|
lblStatus.Dock = DockStyle.Top;
|
|
lblStatus.AutoSize = true;
|
|
lblStatus.Padding = new Padding(6, 4, 6, 4);
|
|
lblStatus.TabIndex = 0;
|
|
lblStatus.Text = "Connecting...";
|
|
|
|
// ── Channel tree ──────────────────────────────────────────────────────
|
|
lblChannels.Text = "Channels:";
|
|
lblChannels.Dock = DockStyle.Top;
|
|
lblChannels.AutoSize = true;
|
|
lblChannels.Padding = new Padding(4, 4, 4, 2);
|
|
|
|
tvChannels.AccessibleName = "Channel list";
|
|
tvChannels.AccessibleDescription =
|
|
"Double-click or press Enter to join a channel. " +
|
|
"Channels marked [password] require a password.";
|
|
tvChannels.Dock = DockStyle.Fill;
|
|
tvChannels.HideSelection = false;
|
|
tvChannels.TabIndex = 0;
|
|
|
|
// ── User list ─────────────────────────────────────────────────────────
|
|
lblUsers.Text = "Users in channel:";
|
|
lblUsers.Dock = DockStyle.Top;
|
|
lblUsers.AutoSize = true;
|
|
lblUsers.Padding = new Padding(4, 4, 4, 2);
|
|
|
|
lstUsers.AccessibleName = "Users in current channel";
|
|
lstUsers.AccessibleDescription =
|
|
"People in the same channel. Double-click or press Enter for per-user volume settings. " +
|
|
"Talking users are marked (talking).";
|
|
lstUsers.Dock = DockStyle.Fill;
|
|
lstUsers.TabIndex = 1;
|
|
|
|
// ── Left split (channels top, users bottom) ───────────────────────────
|
|
splitLeft.Orientation = Orientation.Horizontal;
|
|
splitLeft.Dock = DockStyle.Fill;
|
|
splitLeft.Panel1MinSize = 100;
|
|
splitLeft.Panel2MinSize = 80;
|
|
splitLeft.TabIndex = 0;
|
|
// Keep the resize splitter out of the Tab cycle so focus moves control-to-control.
|
|
splitLeft.TabStop = false;
|
|
// Name the otherwise-anonymous "pane" containers so screen readers announce
|
|
// orientation instead of a stack of unnamed panes.
|
|
splitLeft.AccessibleName = "Channels and users";
|
|
splitLeft.Panel1.AccessibleName = "Channels";
|
|
splitLeft.Panel2.AccessibleName = "Users";
|
|
splitLeft.Panel1.Controls.Add(tvChannels);
|
|
splitLeft.Panel1.Controls.Add(lblChannels);
|
|
splitLeft.Panel2.Controls.Add(lstUsers);
|
|
splitLeft.Panel2.Controls.Add(lblUsers);
|
|
|
|
// ── Unified log ───────────────────────────────────────────────────────
|
|
lblLog.Text = "Chat & Activity:";
|
|
lblLog.AutoSize = true;
|
|
lblLog.Padding = new Padding(2, 2, 2, 1);
|
|
|
|
rtbLog.AccessibleName = "Chat and activity log";
|
|
rtbLog.AccessibleDescription = "Combined history of chat messages (normal) and activity events (gray).";
|
|
rtbLog.Dock = DockStyle.Fill;
|
|
rtbLog.ReadOnly = true;
|
|
rtbLog.ScrollBars = RichTextBoxScrollBars.Vertical;
|
|
rtbLog.BackColor = SystemColors.Window;
|
|
rtbLog.TabIndex = 0;
|
|
|
|
// ── Compose row ───────────────────────────────────────────────────────
|
|
txtCompose.AccessibleName = "Message text";
|
|
txtCompose.AccessibleDescription = "Type your channel message. Press Enter or click Send to send.";
|
|
txtCompose.Dock = DockStyle.Fill;
|
|
txtCompose.TabIndex = 1;
|
|
|
|
btnSend.Text = "&Send";
|
|
btnSend.Dock = DockStyle.Fill;
|
|
btnSend.TabIndex = 2;
|
|
|
|
tblCompose.ColumnCount = 2;
|
|
tblCompose.RowCount = 1;
|
|
tblCompose.Dock = DockStyle.Fill;
|
|
tblCompose.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
|
tblCompose.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 68F));
|
|
tblCompose.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
|
tblCompose.Padding = new Padding(0, 3, 0, 0);
|
|
tblCompose.Controls.Add(txtCompose, 0, 0);
|
|
tblCompose.Controls.Add(btnSend, 1, 0);
|
|
|
|
// ── Output volume row ─────────────────────────────────────────────────
|
|
lblOutputVolume.Text = "Output volume:";
|
|
lblOutputVolume.AutoSize = true;
|
|
lblOutputVolume.Padding = new Padding(2, 6, 4, 0);
|
|
|
|
trkOutputVolume.AccessibleName = "Output volume";
|
|
trkOutputVolume.AccessibleDescription = "Global playback volume for all incoming audio.";
|
|
trkOutputVolume.Minimum = 0;
|
|
trkOutputVolume.Maximum = 100;
|
|
trkOutputVolume.Value = 80;
|
|
trkOutputVolume.TickFrequency = 10;
|
|
trkOutputVolume.SmallChange = 1;
|
|
trkOutputVolume.LargeChange = 10;
|
|
trkOutputVolume.Dock = DockStyle.Fill;
|
|
trkOutputVolume.TabIndex = 3;
|
|
|
|
// ── Right table layout ────────────────────────────────────────────────
|
|
tblRight.ColumnCount = 1;
|
|
tblRight.RowCount = 5;
|
|
tblRight.Dock = DockStyle.Fill;
|
|
tblRight.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
|
tblRight.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // row 0: lblLog
|
|
tblRight.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); // row 1: rtbLog
|
|
tblRight.RowStyles.Add(new RowStyle(SizeType.Absolute, 34F)); // row 2: compose
|
|
tblRight.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // row 3: vol label
|
|
tblRight.RowStyles.Add(new RowStyle(SizeType.Absolute, 45F)); // row 4: vol slider
|
|
tblRight.Controls.Add(lblLog, 0, 0);
|
|
tblRight.Controls.Add(rtbLog, 0, 1);
|
|
tblRight.Controls.Add(tblCompose, 0, 2);
|
|
tblRight.Controls.Add(lblOutputVolume, 0, 3);
|
|
tblRight.Controls.Add(trkOutputVolume, 0, 4);
|
|
|
|
// ── Main split ────────────────────────────────────────────────────────
|
|
splitMain.Dock = DockStyle.Fill;
|
|
splitMain.Panel1MinSize = 150;
|
|
splitMain.TabIndex = 1;
|
|
splitMain.TabStop = false;
|
|
splitMain.AccessibleName = "Main";
|
|
splitMain.Panel1.AccessibleName = "Channels and users";
|
|
splitMain.Panel2.AccessibleName = "Chat and activity";
|
|
splitMain.Panel1.Controls.Add(splitLeft);
|
|
splitMain.Panel2.Controls.Add(tblRight);
|
|
|
|
// ── Voice control panel ───────────────────────────────────────────────
|
|
chkMute.Text = "&Mute mic";
|
|
chkMute.AutoSize = true;
|
|
chkMute.Enabled = false;
|
|
chkMute.Margin = new Padding(0, 4, 6, 0);
|
|
chkMute.TabIndex = 1;
|
|
|
|
chkDeafen.Text = "&Deafen";
|
|
chkDeafen.AutoSize = true;
|
|
chkDeafen.Enabled = false;
|
|
chkDeafen.Margin = new Padding(0, 4, 12, 0);
|
|
chkDeafen.TabIndex = 2;
|
|
|
|
flpVoiceTop.Dock = DockStyle.Fill;
|
|
flpVoiceTop.AutoSize = false;
|
|
flpVoiceTop.Padding = new Padding(4, 2, 4, 0);
|
|
flpVoiceTop.Controls.Add(chkMute);
|
|
flpVoiceTop.Controls.Add(chkDeafen);
|
|
|
|
lblLevel.Text = "Level:";
|
|
lblLevel.AutoSize = true;
|
|
lblLevel.Margin = new Padding(12, 5, 4, 0);
|
|
|
|
pbLevel.AccessibleName = "Microphone level";
|
|
pbLevel.AccessibleDescription = "Current input level from the microphone.";
|
|
pbLevel.Width = 120;
|
|
pbLevel.Height = 16;
|
|
pbLevel.Maximum = 100;
|
|
pbLevel.Margin = new Padding(0, 6, 0, 0);
|
|
pbLevel.Style = ProgressBarStyle.Continuous;
|
|
pbLevel.TabStop = false;
|
|
|
|
flpVoiceTop.Controls.Add(lblLevel);
|
|
flpVoiceTop.Controls.Add(pbLevel);
|
|
|
|
pnlVoice.Dock = DockStyle.Bottom;
|
|
pnlVoice.Height = 38;
|
|
pnlVoice.BorderStyle = BorderStyle.FixedSingle;
|
|
pnlVoice.Padding = new Padding(0);
|
|
pnlVoice.Controls.Add(flpVoiceTop);
|
|
|
|
// ── Toolbar ───────────────────────────────────────────────────────────
|
|
tsbJoinVoice.Text = "Join Voice";
|
|
tsbJoinVoice.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
|
tsbJoinVoice.CheckOnClick = false;
|
|
|
|
tsbScreenShare.Text = "Share Screen Audio";
|
|
tsbScreenShare.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
|
tsbScreenShare.CheckOnClick = false;
|
|
|
|
toolStrip.Dock = DockStyle.Top;
|
|
toolStrip.Items.Add(tsbJoinVoice);
|
|
toolStrip.Items.Add(new ToolStripSeparator());
|
|
toolStrip.Items.Add(tsbScreenShare);
|
|
toolStrip.TabIndex = 1;
|
|
toolStrip.AccessibleName = "Toolbar";
|
|
|
|
// ── Menu strip ────────────────────────────────────────────────────────
|
|
menuStrip.AccessibleName = "Main menu";
|
|
menuStrip.Dock = DockStyle.Top;
|
|
menuStrip.TabIndex = 0;
|
|
MainMenuStrip = menuStrip;
|
|
|
|
// ── Form ──────────────────────────────────────────────────────────────
|
|
AutoScaleMode = AutoScaleMode.Font;
|
|
ClientSize = new Size(960, 680);
|
|
MinimumSize = new Size(700, 540);
|
|
KeyPreview = true;
|
|
Controls.Add(splitMain); // DockStyle.Fill
|
|
Controls.Add(pnlVoice); // DockStyle.Bottom
|
|
Controls.Add(lblStatus); // DockStyle.Top
|
|
Controls.Add(toolStrip); // DockStyle.Top (above status)
|
|
Controls.Add(menuStrip); // DockStyle.Top (topmost)
|
|
StartPosition = FormStartPosition.CenterScreen;
|
|
Text = "VoiceCat";
|
|
}
|
|
}
|