Files
voice-cat/clients/windows/VoiceCat.App/Forms/PrivateMessageForm.cs
Talon 65736df464 fix(windows): polish hotkeys, user list, titlebar, and PM window
- Suppress the system ding on global hotkeys/PTT and the user-list Enter
  key by setting SuppressKeyPress (Handled alone leaves WM_CHAR to beep).
- Preserve the user-list keyboard selection across talking/mute refreshes
  instead of resetting it on every Items.Clear().
- Include the connected server name in the main window titlebar.
- Close the private-message window on Escape.
- Show the PM window without an owner so focus is no longer trapped to it
  and the main window can be worked in while a PM is open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:30:48 +02:00

121 lines
4.0 KiB
C#

using VoiceCat.Interop;
namespace VoiceCat.App.Forms;
/// <summary>
/// Modeless window for a single private message conversation.
/// Created by MainForm and shown non-modally; MainForm routes incoming PMs here.
/// </summary>
public sealed class PrivateMessageForm : Form
{
private readonly VoiceCatClient _client;
private readonly uint _userId;
private readonly uint _selfUserId;
private readonly RichTextBox _rtbHistory;
private readonly TextBox _txtCompose;
private readonly Button _btnSend;
public PrivateMessageForm(VoiceCatClient client, uint userId, string nickname, uint selfUserId)
{
_client = client;
_userId = userId;
_selfUserId = selfUserId;
Text = $"Private Message — {nickname}";
ClientSize = new Size(480, 360);
MinimumSize = new Size(320, 240);
StartPosition = FormStartPosition.Manual;
KeyPreview = true;
KeyDown += (_, e) =>
{
if (e.KeyCode == Keys.Escape) { e.Handled = e.SuppressKeyPress = true; Close(); }
};
_rtbHistory = new RichTextBox
{
ReadOnly = true,
Dock = DockStyle.Fill,
ScrollBars = RichTextBoxScrollBars.Vertical,
BackColor = SystemColors.Window,
AccessibleName = "Message history",
};
var composePanel = new TableLayoutPanel
{
Dock = DockStyle.Bottom,
Height = 34,
ColumnCount = 2,
RowCount = 1,
Padding = new Padding(0, 3, 0, 0),
};
composePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
composePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 68F));
composePanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
_txtCompose = new TextBox
{
Dock = DockStyle.Fill,
AccessibleName = "Message text",
};
_btnSend = new Button
{
Text = "&Send",
Dock = DockStyle.Fill,
};
composePanel.Controls.Add(_txtCompose, 0, 0);
composePanel.Controls.Add(_btnSend, 1, 0);
Controls.Add(_rtbHistory);
Controls.Add(composePanel);
_txtCompose.KeyDown += (_, e) =>
{
if (e.KeyCode == Keys.Enter) { Send(); e.Handled = e.SuppressKeyPress = true; }
};
_btnSend.Click += (_, _) => Send();
}
// Called by MainForm when a PM message arrives (or we send one and the server echoes it).
public void AppendMessage(string time, bool isSelf, string sender, string text)
{
if (IsDisposed) return;
string line = $"[{time}] {sender}: {text}\n";
int selStart = _rtbHistory.TextLength;
_rtbHistory.AppendText(line);
if (isSelf)
{
_rtbHistory.Select(selStart, line.Length);
_rtbHistory.SelectionColor = Color.Gray;
_rtbHistory.Select(_rtbHistory.TextLength, 0);
_rtbHistory.SelectionColor = _rtbHistory.ForeColor;
}
_rtbHistory.ScrollToCaret();
}
// Called by MainForm for status events (e.g., the other user disconnected).
public void AppendActivity(string text)
{
if (IsDisposed) return;
string line = $"[{DateTime.Now:HH:mm}] {text}\n";
int selStart = _rtbHistory.TextLength;
_rtbHistory.AppendText(line);
_rtbHistory.Select(selStart, line.Length);
_rtbHistory.SelectionColor = Color.Gray;
_rtbHistory.Select(_rtbHistory.TextLength, 0);
_rtbHistory.SelectionColor = _rtbHistory.ForeColor;
_rtbHistory.ScrollToCaret();
}
private void Send()
{
string msg = _txtCompose.Text.Trim();
if (string.IsNullOrEmpty(msg)) return;
_client.SendText(VcTextScope.Private, _userId, msg);
_txtCompose.Clear();
// No optimistic echo: the server relays our own message back, so AppendMessage is
// called from MainForm.HandleTextMessage when the echo arrives.
}
}