Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/DiffEngineViewer.Windows.Tests/AmpersandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// <summary>
/// An ampersand in a solution name, a path or an applier message. WinForms reads one as a mnemonic
/// wherever it renders text, so "R&amp;D" draws as "R_D" - with D live as an accelerator, which is
/// worse than the missing character.
/// </summary>
[NotInParallel]
[TUnit.Core.Executors.STAThreadExecutor]
public class AmpersandTests
{
[Test]
public async Task A_menu_label_keeps_its_ampersand()
{
using var strip = ViewerMenu.Build(new(0, ["Accept all in R&D"]));

var item = strip.Items
.Cast<System.Windows.Forms.ToolStripItem>()
.Single();
// Doubled, which is how a literal one is written. What is drawn is one
await Assert.That(item.Text).IsEqualTo("Accept all in R&&D");
}

[Test]
public async Task The_status_line_does_not_read_one_as_a_mnemonic()
{
using var form = new ViewerForm("title", 800, 600);

var status = form.Controls
.Find("status", true)
.OfType<System.Windows.Forms.Label>()
.Single();

await Assert.That(status.UseMnemonic).IsFalse();
}
}
7 changes: 6 additions & 1 deletion src/DiffEngineViewer.Windows/ViewerForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ sealed class ViewerForm : Form

readonly Label status = new()
{
Name = "status",
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleRight,
ForeColor = Palette.Dim,
AutoSize = false
AutoSize = false,
// The status line is built from paths, solution names and whatever the applier said, and a
// Label reads an ampersand in any of those as a mnemonic: "R&D" drew as "R_D" with D live
// as an accelerator.
UseMnemonic = false
};

readonly List<FormsButton> pool = [];
Expand Down
11 changes: 10 additions & 1 deletion src/DiffEngineViewer.Windows/ViewerMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,21 @@ public static void Fill(ContextMenuStrip strip, MenuOverlay menu, Action<int> cl
for (var index = 0; index < menu.Labels.Count; index++)
{
var captured = index;
var item = new ToolStripMenuItem(menu.Labels[index]);
var item = new ToolStripMenuItem(Escape(menu.Labels[index]));
item.Click += (_, _) => clicked(captured);
strip.Items.Add(item);
}
}

/// <summary>
/// Menu labels carry solution names and file names - "Accept all in R&amp;D" - and a menu item
/// reads an ampersand as a mnemonic, so that one drew as "Accept all in R_D" with D live as an
/// accelerator. Doubling it is how a literal one is written; ToolStripItem has no
/// <c>UseMnemonic</c> to turn the reading off.
/// </summary>
static string Escape(string label) =>
label.Replace("&", "&&");

/// <summary>
/// A strip on its own, for tests and for anything that wants one without a window.
/// </summary>
Expand Down
Loading