Files
RemSound/src/RemSound.App/KeyboardShortcutImportDialog.cs
T
EdnunpandClaude Opus 4.8 962f35ab80 Release v4.5: import shortcuts on pre-4.4 upgrade + clearer shortcuts dialog
- Pre-v4.4 upgraders are offered a one-time dialog to copy their keyboard shortcuts
  from one of their profiles (still readable in the profile files) instead of being
  reset. Users who already went through v4.4's reset are deliberately NOT re-offered
  (gated on KeyboardShortcutsGlobalNoticeShown). New KeyboardShortcutImportDialog +
  AppConfig.KeyboardShortcutsImportOffered + MainFormHotkeyController.ReloadAndReRegisterAll.
- Keyboard shortcuts dialog: new "Clear this shortcut" button; Delete inside the
  capture box leaves a shortcut unassigned.
- Relabelled the three RemSound-app remote rows to "Send remote RemSound volume/..."
  to distinguish them from the Windows-global ones.
- Manual (readme.html + MANUAL.md), About changelog, RELEASE_NOTES updated; version 4.5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 06:36:00 +01:00

138 lines
4.9 KiB
C#

using RemSound.Core;
namespace RemSound.App;
/// <summary>
/// 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.
/// </summary>
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,
};
/// <summary>The profile the user chose to copy shortcuts from, or null if they chose to start fresh.
/// Only meaningful when <see cref="Form.ShowDialog()"/> returned <see cref="DialogResult.OK"/>.</summary>
public string? ChosenProfileTitle { get; private set; }
public KeyboardShortcutImportDialog(IReadOnlyList<string> 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);
}
}