Skip to content

Commit f66a9df

Browse files
author
tznind
committed
WIP - Restore menu bar tests and WIP change how we handle separators
1 parent 2fec02d commit f66a9df

14 files changed

Lines changed: 492 additions & 466 deletions

src/Design.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public Design CreateSubControlDesign(string name, View subView)
203203
if (subView is MenuBar mb)
204204
{
205205
MenuTracker.Instance.Register(mb);
206+
mb.ConvertLineSeparatorsToSentinels();
206207
}
207208

208209
if (subView is CheckBox cb)

src/MenuBarExtensions.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Terminal.Gui;
22
using Terminal.Gui.Text;
33
using Terminal.Gui.Views;
4+
using TerminalGuiDesigner.Operations.MenuOperations;
45

56
namespace TerminalGuiDesigner;
67

@@ -28,6 +29,56 @@ public static class MenuBarExtensions
2829
return menuBar.SubViews.OfType<MenuBarItem>().ElementAt(selected);
2930
}
3031

32+
/// <summary>
33+
/// Walks all menus in <paramref name="menuBar"/> and replaces any <see cref="Line"/> views
34+
/// (produced by code generation for separators) with sentinel <see cref="MenuItem"/> instances
35+
/// whose <see cref="MenuItem.Title"/> is <see cref="ConvertMenuItemToSeperatorOperation.SeparatorTitle"/>.
36+
/// Call this after loading a <see cref="MenuBar"/> from generated code.
37+
/// </summary>
38+
public static void ConvertLineSeparatorsToSentinels(this MenuBar menuBar)
39+
{
40+
foreach (var mbi in menuBar.SubViews.OfType<MenuBarItem>())
41+
{
42+
if (mbi.PopoverMenu?.Root != null)
43+
{
44+
ConvertLineSeparatorsInMenu(mbi.PopoverMenu.Root);
45+
}
46+
}
47+
}
48+
49+
private static void ConvertLineSeparatorsInMenu(Menu menu)
50+
{
51+
var allViews = menu.SubViews.ToList();
52+
53+
if (allViews.Any(v => v is Line))
54+
{
55+
foreach (var v in allViews)
56+
{
57+
menu.Remove(v);
58+
}
59+
60+
foreach (var v in allViews)
61+
{
62+
menu.Add(v is Line
63+
? new MenuItem { Title = ConvertMenuItemToSeperatorOperation.SeparatorTitle }
64+
: v);
65+
}
66+
}
67+
68+
// Recurse into child submenus
69+
foreach (var child in menu.SubViews.OfType<MenuItem>())
70+
{
71+
if (child is MenuBarItem mbi && mbi.PopoverMenu?.Root != null)
72+
{
73+
ConvertLineSeparatorsInMenu(mbi.PopoverMenu.Root);
74+
}
75+
else if (child.SubMenu != null)
76+
{
77+
ConvertLineSeparatorsInMenu(child.SubMenu);
78+
}
79+
}
80+
}
81+
3182
/// <summary>
3283
/// Returns the <see cref="MenuBarItem"/> that appears at the <paramref name="screenX"/> of the click.
3384
/// </summary>
Lines changed: 20 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Terminal.Gui;
1+
using Terminal.Gui;
22
using Terminal.Gui.App;
33
using Terminal.Gui.Views;
44

@@ -7,22 +7,31 @@ namespace TerminalGuiDesigner.Operations.MenuOperations;
77
/// <summary>
88
/// <para>
99
/// Converts a <see cref="MenuItem"/> into a Separator (horizontal line in menu).
10-
/// In the new Terminal.Gui API this is represented as a Line view.
10+
/// In the designer the separator is stored as a <see cref="MenuItem"/> with
11+
/// <see cref="SeparatorTitle"/> as its <see cref="MenuItem.Title"/>.
12+
/// When code is generated it becomes a <c>new Line { Orientation = Orientation.Horizontal }</c>
13+
/// and on load those Line views are converted back to sentinel MenuItems.
1114
/// </para>
1215
/// </summary>
1316
public class ConvertMenuItemToSeperatorOperation : MenuItemOperation
1417
{
15-
private int removedAtIdx;
16-
private Line? addedLine;
18+
/// <summary>The title used to mark a MenuItem as a separator in the designer.</summary>
19+
public const string SeparatorTitle = "---";
20+
21+
private string? originalTitle;
1722

1823
/// <summary>
1924
/// Initializes a new instance of the <see cref="ConvertMenuItemToSeperatorOperation"/> class.
2025
/// </summary>
2126
/// <param name="app">The application instance.</param>
22-
/// <param name="toConvert">A <see cref="MenuItem"/> to replace with a separator (Line) in it's parent menu.</param>
27+
/// <param name="toConvert">A <see cref="MenuItem"/> to convert into a separator.</param>
2328
public ConvertMenuItemToSeperatorOperation(IApplication app, MenuItem toConvert)
2429
: base(app, toConvert)
2530
{
31+
if (toConvert.Title?.ToString() == SeparatorTitle)
32+
{
33+
IsImpossible = true;
34+
}
2635
}
2736

2837
/// <inheritdoc/>
@@ -34,92 +43,26 @@ protected override void RedoImpl()
3443
/// <inheritdoc/>
3544
protected override void UndoImpl()
3645
{
37-
if (this.Parent == null || this.OperateOn == null || this.addedLine == null)
46+
if (this.OperateOn == null)
3847
{
3948
return;
4049
}
4150

42-
var menu = this.Parent.GetChildMenu(out _);
43-
if (menu == null)
44-
{
45-
return;
46-
}
47-
48-
// Find the index of the separator line and restore MenuItem at that position
49-
var allViews = menu.SubViews.ToList();
50-
int lineIdx = allViews.IndexOf(this.addedLine);
51-
52-
if (lineIdx >= 0)
53-
{
54-
// Remove the separator
55-
menu.Remove(this.addedLine);
56-
57-
// Rebuild views with MenuItem at the correct position
58-
var nonLineViews = allViews.Where(v => v != this.addedLine).ToList();
59-
nonLineViews.Insert(Math.Min(lineIdx, nonLineViews.Count), this.OperateOn);
60-
61-
// Clear and re-add all views in order
62-
foreach (var v in allViews.Where(v => v != this.addedLine).ToList())
63-
{
64-
menu.Remove(v);
65-
}
66-
67-
foreach (var v in nonLineViews)
68-
{
69-
menu.Add(v);
70-
}
71-
}
72-
51+
this.OperateOn.Title = this.originalTitle ?? string.Empty;
7352
this.Bar?.SetNeedsDraw();
7453
}
7554

7655
/// <inheritdoc/>
7756
protected override bool DoImpl()
7857
{
79-
if (this.Parent == null || this.OperateOn == null)
58+
if (this.OperateOn == null)
8059
{
8160
return false;
8261
}
8362

84-
var menu = this.Parent.GetChildMenu(out _);
85-
if (menu == null)
86-
{
87-
return false;
88-
}
89-
90-
var items = this.Parent.GetMenuItems(out _);
91-
this.removedAtIdx = Math.Max(0, items.IndexOf(this.OperateOn));
92-
93-
// Find the actual index in SubViews
94-
var allViews = menu.SubViews.ToList();
95-
int actualIdx = allViews.IndexOf(this.OperateOn);
96-
97-
if (actualIdx < 0)
98-
{
99-
return false;
100-
}
101-
102-
// Remove the MenuItem
103-
menu.Remove(this.OperateOn);
104-
105-
// Create Line separator and rebuild views with it at the correct position
106-
this.addedLine = new Line { Orientation = Terminal.Gui.ViewBase.Orientation.Horizontal };
107-
var newViews = allViews.Where(v => v != this.OperateOn).ToList();
108-
newViews.Insert(Math.Min(actualIdx, newViews.Count), this.addedLine);
109-
110-
// Clear and re-add all views in order
111-
foreach (var v in allViews.Where(v => v != this.OperateOn).ToList())
112-
{
113-
menu.Remove(v);
114-
}
115-
116-
foreach (var v in newViews)
117-
{
118-
menu.Add(v);
119-
}
120-
63+
this.originalTitle = this.OperateOn.Title?.ToString();
64+
this.OperateOn.Title = SeparatorTitle;
12165
this.Bar?.SetNeedsDraw();
122-
12366
return true;
12467
}
125-
}
68+
}

src/ToCode/MenuBarItemsToCode.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,26 @@ private void ToCode(CodeDomArgs args, MenuItem child, out string fieldName)
8181
// plus again let user name these
8282
foreach (var mi in child.GetMenuItems(out wasPopover))
8383
{
84+
// Separators are stored as MenuItem("---") in the designer but emitted as Line in code
85+
if (mi.Title?.ToString() == Operations.MenuOperations.ConvertMenuItemToSeperatorOperation.SeparatorTitle)
86+
{
87+
children.Add("new Line { Orientation = Terminal.Gui.ViewBase.Orientation.Horizontal }");
88+
continue;
89+
}
90+
8491
string subFieldName;
8592

86-
// If it has its own children e.g.
87-
// File->New->Project
88-
if (mi.SubMenu != null)
93+
// If it has its own children e.g. File->New->Project
94+
if (mi.SubMenu != null || mi is MenuBarItem)
8995
{
9096
ToCode(args, mi, out subFieldName);
9197
}
9298
else
9399
{
94-
95-
// It has no children of its own e.g. its just Edit->Paste
100+
// It has no children of its own e.g. its just Edit->Paste
96101
CreateMenuMembersAndPropertyAssignments(args, mi, out subFieldName);
97102
}
98-
103+
99104
children.Add(subFieldName);
100105
}
101106

tests/KeyMapTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ internal class KeyMapTests
5252
5353
""";
5454

55+
[Test]
56+
public void BadCtor()
57+
{
58+
var f9 = Key.F9;
59+
60+
var alsoF9 = new Key(f9.KeyCode);
61+
}
62+
5563
[Test]
5664
[Category( "Configuration" )]
5765
public void Configuration_LoadingAndBinding( )

tests/MenuBarExtensionsTests.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ public void ScreenToMenuBarItem_MultipleMenuItems_ReturnsExpectedItem_IfItemsCli
3939
// Expect a MenuBar to be rendered that is
4040
// ".test..next..more.." (with 1 unit of preceding whitespace and 1 after each)
4141
// Note that this test is brittle and subject to changes in Terminal.Gui e.g. pushing menus closer together.
42-
v.Menus[ 0 ].Title = "test";
42+
v.SubViews.OfType<MenuBarItem>().First().Title = "test";
4343

4444
Assume.That( ( ) => new AddMenuOperation(App, d, "next" ).Do( ), Throws.Nothing );
4545
Assume.That( ( ) => new AddMenuOperation(App, d, "more" ).Do( ), Throws.Nothing );
4646

47-
Assume.That( v.Menus, Has.Exactly( 3 ).InstanceOf<MenuBarItem>( ) );
47+
Assume.That( v.SubViews.OfType<MenuBarItem>(), Has.Exactly( 3 ).InstanceOf<MenuBarItem>( ) );
4848

4949
// Clicks in the "test" region
5050
var a = v.ScreenToMenuBarItem(clickXCoordinate + xOffset);
51-
var b = v.Menus[expectedMenuItem];
51+
var b = v.SubViews.OfType<MenuBarItem>().ElementAt(expectedMenuItem);
5252
Assert.That( a, Is.SameAs(b));
5353
}, out _ );
5454
}
@@ -72,12 +72,12 @@ public void ScreenToMenuBarItem_MultipleMenuItems_ReturnsNull_IfClickedBeforeAnd
7272
// Expect a MenuBar to be rendered that is
7373
// ".test..next..more.." (with 1 unit of preceding whitespace and 2 after each)
7474
// Note that this test is brittle and subject to changes in Terminal.Gui e.g. pushing menus closer together.
75-
v.Menus[ 0 ].Title = "test";
75+
v.SubViews.OfType<MenuBarItem>().First().Title = "test";
7676

7777
Assume.That( ( ) => new AddMenuOperation(App, d, "next" ).Do( ), Throws.Nothing );
7878
Assume.That( ( ) => new AddMenuOperation(App, d, "more" ).Do( ), Throws.Nothing );
7979

80-
Assume.That( v.Menus, Has.Exactly( 3 ).InstanceOf<MenuBarItem>( ) );
80+
Assume.That( v.SubViews.OfType<MenuBarItem>(), Has.Exactly( 3 ).InstanceOf<MenuBarItem>( ) );
8181

8282
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.Null );
8383
}, out _ );
@@ -99,14 +99,14 @@ public void ScreenToMenuBarItem_OneMenuItem_ReturnsExpectedMenuBarItem_IfClicked
9999
v.Y = yOffset;
100100

101101
v.SuperView!.LayoutSubViews();
102-
103-
// Expect a MenuBar to be rendered that is
102+
103+
// Expect a MenuBar to be rendered that is
104104
// ".test.." (with 1 unit of preceding whitespace and 2 after)
105105
// Note that this test is brittle and subject to changes in Terminal.Gui e.g. pushing menus closer together.
106-
Assume.That( v.Menus, Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
107-
v.Menus[ 0 ].Title = "test";
106+
Assume.That( v.SubViews.OfType<MenuBarItem>(), Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
107+
v.SubViews.OfType<MenuBarItem>().First().Title = "test";
108108

109-
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.SameAs( v.Menus[ 0 ] ) );
109+
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.SameAs( v.SubViews.OfType<MenuBarItem>().First() ) );
110110
}, out _ );
111111
}
112112

@@ -127,13 +127,13 @@ public void ScreenToMenuBarItem_OneMenuItem_ReturnsExpectedMenuBarItem_IfItemCli
127127

128128
v.SuperView!.LayoutSubViews();
129129

130-
// Expect a MenuBar to be rendered that is
130+
// Expect a MenuBar to be rendered that is
131131
// ".test.." (with 1 unit of preceding whitespace and 2 after)
132132
// Note that this test is brittle and subject to changes in Terminal.Gui e.g. pushing menus closer together.
133-
Assume.That( v.Menus, Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
134-
v.Menus[ 0 ].Title = "test";
133+
Assume.That( v.SubViews.OfType<MenuBarItem>(), Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
134+
v.SubViews.OfType<MenuBarItem>().First().Title = "test";
135135

136-
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.SameAs( v.Menus[ 0 ] ) );
136+
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.SameAs( v.SubViews.OfType<MenuBarItem>().First() ) );
137137
}, out _ );
138138
}
139139

@@ -154,11 +154,11 @@ public void ScreenToMenuBarItem_OneMenuItem_ReturnsNull_IfClickedBeforeAndAfterI
154154

155155
v.SuperView!.LayoutSubViews();
156156

157-
// Expect a MenuBar to be rendered that is
157+
// Expect a MenuBar to be rendered that is
158158
// ".test.." (with 1 unit of preceding whitespace and 2 after)
159159
// Note that this test is brittle and subject to changes in Terminal.Gui e.g. pushing menus closer together.
160-
Assume.That( v.Menus, Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
161-
v.Menus[ 0 ].Title = "test";
160+
Assume.That( v.SubViews.OfType<MenuBarItem>(), Has.Exactly( 1 ).InstanceOf<MenuBarItem>( ) );
161+
v.SubViews.OfType<MenuBarItem>().First().Title = "test";
162162

163163
Assert.That( v.ScreenToMenuBarItem( clickXCoordinate + xOffset ), Is.Null,
164164
"Expected Terminal.Gui MenuBar to have 1 unit of whitespace before and 2 after any MenuBarItems (e.g. File) get rendered. This may change in future, if so then update this test." );

0 commit comments

Comments
 (0)