using RemSound.Core; namespace RemSound.App; /// /// One-time upgrade dialog: keyboard shortcuts moved from per-profile to machine-wide storage, so the /// user is offered the choice of copying their shortcut set from one of their existing profiles (whose /// old per-profile shortcuts are still readable in the profile files) — or starting fresh at the /// defaults. Accessible: a profile list plus two clearly-labelled buttons, focus landing on the list. /// internal sealed class KeyboardShortcutImportDialog : Form { private readonly ListBox profileList = new() { IntegralHeight = false, Width = 400, Height = 150, AccessibleName = "Profile to copy keyboard shortcuts from", TabIndex = 0, }; /// The profile the user chose to copy shortcuts from, or null if they chose to start fresh. /// Only meaningful when returned . public string? ChosenProfileTitle { get; private set; } public KeyboardShortcutImportDialog(IReadOnlyList profileTitles) { Text = "Keyboard shortcuts"; AccessibleName = "Bring your keyboard shortcuts across"; AccessibleRole = AccessibleRole.Dialog; FormBorderStyle = FormBorderStyle.FixedDialog; MinimizeBox = false; MaximizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterScreen; KeyPreview = true; ClientSize = new Size(470, 330); var info = new Label { AutoSize = true, MaximumSize = new Size(440, 0), Text = "Keyboard shortcuts are now shared across all your profiles, instead of being saved " + "separately in each one.\n\n" + "You can copy your shortcuts from one of your existing profiles, or start fresh with " + "the defaults. Pick a profile below, then choose what to do.", }; var listLabel = new MnemonicLabel { Text = "Copy shortcuts from this &profile:", AutoSize = true, Anchor = AnchorStyles.Left, MnemonicTarget = profileList, }; foreach (var t in profileTitles) profileList.Items.Add(t); if (profileList.Items.Count > 0) profileList.SelectedIndex = 0; var useButton = new Button { Text = "&Use the shortcuts from this profile", AccessibleName = "Use the shortcuts from this profile", AutoSize = true, TabIndex = 1, }; useButton.Click += (_, _) => { if (profileList.SelectedItem is string title) { ChosenProfileTitle = title; DialogResult = DialogResult.OK; Close(); } }; var freshButton = new Button { Text = "Start &fresh with the defaults", AccessibleName = "Start fresh with the defaults", AutoSize = true, TabIndex = 2, }; freshButton.Click += (_, _) => { ChosenProfileTitle = null; DialogResult = DialogResult.OK; Close(); }; var buttons = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, AutoSize = true, Padding = new Padding(0, 8, 0, 0), }; buttons.Controls.Add(useButton); buttons.Controls.Add(freshButton); var layout = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), ColumnCount = 1, RowCount = 4, }; layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.Controls.Add(info, 0, 0); layout.Controls.Add(listLabel, 0, 1); layout.Controls.Add(profileList, 0, 2); layout.Controls.Add(buttons, 0, 3); Controls.Add(layout); AcceptButton = useButton; // Escape leaves the choice unmade — the caller treats a non-OK result as "ask again next launch", // so the user is never forced into a decision they didn't mean to make. KeyDown += (_, e) => { if (e.KeyCode == Keys.Escape) { DialogResult = DialogResult.Cancel; Close(); e.Handled = true; e.SuppressKeyPress = true; } }; } protected override void OnShown(EventArgs e) { base.OnShown(e); // Land focus on the profile list so NVDA announces the dialog and the first profile. ActiveControl = null; profileList.Focus(); WinEventNotifier.NotifyFocus(profileList); } }