diff --git a/Example/demo.cs b/Example/demo.cs
index dbba7ae12..b7bf1e79e 100644
--- a/Example/demo.cs
+++ b/Example/demo.cs
@@ -3,7 +3,10 @@ using System;
using Mono.Terminal;
using System.Collections;
using System.Collections.Generic;
-
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
+using NStack;
static class Demo {
class Box10x : View {
@@ -42,6 +45,9 @@ static class Demo {
Rune r;
switch (x % 3) {
case 0:
+ Driver.AddRune (y.ToString ().ToCharArray (0, 1) [0]);
+ if (y > 9)
+ Driver.AddRune (y.ToString ().ToCharArray (1, 1) [0]);
r = '.';
break;
case 1:
@@ -60,11 +66,15 @@ static class Demo {
static void ShowTextAlignments (View container)
{
+ int i = 0;
+ string txt = "Hello world, how are you doing today";
container.Add (
- new Label (new Rect (0, 0, 40, 3), "1-Hello world, how are you doing today") { TextAlignment = TextAlignment.Left },
- new Label (new Rect (0, 4, 40, 3), "2-Hello world, how are you doing today") { TextAlignment = TextAlignment.Right },
- new Label (new Rect (0, 8, 40, 3), "3-Hello world, how are you doing today") { TextAlignment = TextAlignment.Centered },
- new Label (new Rect (0, 12, 40, 3), "4-Hello world, how are you doing today") { TextAlignment = TextAlignment.Justified });
+ new FrameView (new Rect (75, 1, txt.Length + 6, 20), "Text Alignments") {
+ new Label(new Rect(0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
+ new Label(new Rect(0, 5, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
+ new Label(new Rect(0, 9, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
+ new Label(new Rect(0, 13, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
+ });
}
static void ShowEntries (View container)
@@ -75,9 +85,11 @@ static class Demo {
ShowVerticalScrollIndicator = true,
ShowHorizontalScrollIndicator = true
};
-
+#if false
scrollView.Add (new Box10x (0, 0));
- //scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
+#else
+ scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
+#endif
// This is just to debug the visuals of the scrollview when small
var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) {
@@ -142,10 +154,10 @@ static class Demo {
new Button (10, 19, "Cancel"),
new TimeField (3, 20, DateTime.Now),
new TimeField (23, 20, DateTime.Now, true),
- new DateField(3, 22, DateTime.Now),
- new DateField(23, 22, DateTime.Now, true),
progress,
- new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar")
+ new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
+ menuKeysStyle,
+ menuAutoMouseNav
);
@@ -166,10 +178,11 @@ static class Demo {
//
// Creates a nested editor
- static void Editor(Toplevel top) {
+ static void Editor (Toplevel top)
+ {
var tframe = top.Frame;
- var ntop = new Toplevel(tframe);
- var menu = new MenuBar(new MenuBarItem[] {
+ var ntop = new Toplevel (tframe);
+ var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
}),
@@ -179,25 +192,25 @@ static class Demo {
new MenuItem ("_Paste", "", null)
}),
});
- ntop.Add(menu);
+ ntop.Add (menu);
string fname = null;
- foreach (var s in new[] { "/etc/passwd", "c:\\windows\\win.ini" })
- if (System.IO.File.Exists(s)) {
+ foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" })
+ if (System.IO.File.Exists (s)) {
fname = s;
break;
}
- var win = new Window(fname ?? "Untitled") {
+ var win = new Window (fname ?? "Untitled") {
X = 0,
Y = 1,
- Width = Dim.Fill(),
- Height = Dim.Fill()
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
};
- ntop.Add(win);
+ ntop.Add (win);
+
+ var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
- var text = new TextView(new Rect(0, 0, tframe.Width - 2, tframe.Height - 3));
-
if (fname != null)
text.Text = System.IO.File.ReadAllText (fname);
win.Add (text);
@@ -213,7 +226,7 @@ static class Demo {
static void Close ()
{
- MessageBox.ErrorQuery (50, 5, "Error", "There is nothing to close", "Ok");
+ MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
}
// Watch what happens when I try to introduce a newline after the first open brace
@@ -222,13 +235,10 @@ static class Demo {
public static void Open ()
{
- var d = new OpenDialog ("Open", "Open a file") {
- AllowsMultipleSelection = true
- };
+ var d = new OpenDialog ("Open", "Open a file");
Application.Run (d);
- if (!d.Canceled)
- MessageBox.Query(50, 7, "Selected File", string.Join(", ", d.FilePaths), "Ok");
+ MessageBox.Query (50, 7, "Selected File", string.Join (", ", d.FilePaths), "Ok");
}
public static void ShowHex (Toplevel top)
@@ -259,9 +269,63 @@ static class Demo {
};
win.Add (hex);
Application.Run (ntop);
-
+
}
+ public class MenuItemDetails : MenuItem {
+ ustring title;
+ string help;
+ Action action;
+
+ public MenuItemDetails (ustring title, string help, Action action) : base (title, help, action)
+ {
+ this.title = title;
+ this.help = help;
+ this.action = action;
+ }
+
+ public static MenuItemDetails Instance (MenuItem mi)
+ {
+ return (MenuItemDetails)mi.GetMenuItem ();
+ }
+ }
+
+ public delegate MenuItem MenuItemDelegate (MenuItemDetails menuItem);
+
+ public static void ShowMenuItem (MenuItem mi)
+ {
+ BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
+ MethodInfo minfo = typeof (MenuItemDetails).GetMethod ("Instance", flags);
+ MenuItemDelegate mid = (MenuItemDelegate)Delegate.CreateDelegate (typeof (MenuItemDelegate), minfo);
+ MessageBox.Query (70, 7, mi.Title.ToString (),
+ $"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok");
+ }
+
+ private static void MenuKeysStyle_Toggled (object sender, EventArgs e)
+ {
+ menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked;
+ }
+
+ private static void MenuAutoMouseNav_Toggled (object sender, EventArgs e)
+ {
+ menu.WantMousePositionReports = menuAutoMouseNav.Checked;
+ }
+
+ //private static TextField GetTextFieldSelText (View vt)
+ //{
+ // TextField textField;
+ // foreach (View v in vt.Subviews) {
+ // if (v is TextField && ((TextField)v).SelText != "")
+ // return v as TextField;
+ // else
+ // textField = GetTextFieldSelText (v);
+ // if (textField != null)
+ // return textField;
+ // }
+ // return null;
+ //}
+
+
#region Selection Demo
static void ListSelectionDemo ()
@@ -300,14 +364,19 @@ static class Demo {
public static Label ml;
+ public static MenuBar menu;
+ public static CheckBox menuKeysStyle;
+ public static CheckBox menuAutoMouseNav;
static void Main ()
{
+ if (Debugger.IsAttached)
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+
//Application.UseSystemConsole = true;
Application.Init ();
-
+
var top = Application.Top;
-
- var tframe = top.Frame;
+
//Open ();
#if true
var win = new Window ("Hello") {
@@ -317,27 +386,64 @@ static class Demo {
Height = Dim.Fill ()
};
#else
+ var tframe = top.Frame;
+
var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello");
#endif
- var menu = new MenuBar (new MenuBarItem [] {
+ MenuItemDetails [] menuItems = {
+ new MenuItemDetails ("F_ind", "", null),
+ new MenuItemDetails ("_Replace", "", null),
+ new MenuItemDetails ("_Item1", "", null),
+ new MenuItemDetails ("_Not From Sub Menu", "", null)
+ };
+
+ menuItems [0].Action = () => ShowMenuItem (menuItems [0]);
+ menuItems [1].Action = () => ShowMenuItem (menuItems [1]);
+ menuItems [2].Action = () => ShowMenuItem (menuItems [2]);
+ menuItems [3].Action = () => ShowMenuItem (menuItems [3]);
+
+ menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("Text Editor Demo", "", () => { Editor (top); }),
new MenuItem ("_New", "Creates new file", NewFile),
new MenuItem ("_Open", "", Open),
new MenuItem ("_Hex", "", () => ShowHex (top)),
new MenuItem ("_Close", "", () => Close ()),
+ new MenuItem ("_Disabled", "", () => { }, () => false),
+ null,
new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
}),
new MenuBarItem ("_Edit", new MenuItem [] {
new MenuItem ("_Copy", "", null),
new MenuItem ("C_ut", "", null),
- new MenuItem ("_Paste", "", null)
+ new MenuItem ("_Paste", "", null),
+ new MenuItem ("_Find and Replace",
+ new MenuBarItem (new MenuItem[] {menuItems [0], menuItems [1] })),
+ menuItems[3]
}),
- new MenuBarItem ("_List Demos", new MenuItem [] {
+ new MenuBarItem ("_List Demos", new MenuItem [] {
new MenuItem ("Select Items", "", ListSelectionDemo),
}),
+ new MenuBarItem ("Test Menu and SubMenus", new MenuItem [] {
+ new MenuItem ("SubMenu1Item1",
+ new MenuBarItem (new MenuItem[] {
+ new MenuItem ("SubMenu2Item1",
+ new MenuBarItem (new MenuItem [] {
+ new MenuItem ("SubMenu3Item1",
+ new MenuBarItem (new MenuItem [] { menuItems [2] })
+ )
+ })
+ )
+ })
+ )
+ }),
});
+ menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true);
+ menuKeysStyle.Toggled += MenuKeysStyle_Toggled;
+ menuAutoMouseNav = new CheckBox (40, 25, "UseMenuAutoNavigation", true);
+ menuAutoMouseNav.Toggled += MenuAutoMouseNav_Toggled;
+
ShowEntries (win);
int count = 0;
@@ -346,14 +452,14 @@ static class Demo {
ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
};
-
+
var test = new Label (3, 18, "Se iniciará el análisis");
win.Add (test);
win.Add (ml);
-
- // ShowTextAlignments (win);
+
+ ShowTextAlignments (win);
top.Add (win);
top.Add (menu);
Application.Run ();
}
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index 113f7dcde..755730193 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -228,6 +228,8 @@ namespace Terminal.Gui {
View container = null;
View focused = null;
Direction focusDirection;
+ public event EventHandler OnEnter;
+ public event EventHandler OnLeave;
internal Direction FocusDirection {
get => SuperView?.FocusDirection ?? focusDirection;
@@ -826,11 +828,16 @@ namespace Terminal.Gui {
}
internal set {
if (base.HasFocus != value)
+ if (value == true)
+ OnEnter?.Invoke (this, new EventArgs ());
+ else
+ OnLeave?.Invoke (this, new EventArgs ());
SetNeedsDisplay ();
base.HasFocus = value;
// Remove focus down the chain of subviews if focus is removed
if (value == false && focused != null) {
+ OnLeave?.Invoke (focused, new EventArgs ());
focused.HasFocus = false;
focused = null;
}
diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs
index 684245cc6..fffb90ee4 100644
--- a/Terminal.Gui/Drivers/ConsoleDriver.cs
+++ b/Terminal.Gui/Drivers/ConsoleDriver.cs
@@ -161,6 +161,10 @@ namespace Terminal.Gui {
///
public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } }
+ ///
+ /// The default color for text, when the view is disabled.
+ ///
+ public Attribute Disabled;
///
/// The color for text when the view has the focus.
///
diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs
index 6697579b0..0b9ce8cde 100644
--- a/Terminal.Gui/Drivers/CursesDriver.cs
+++ b/Terminal.Gui/Drivers/CursesDriver.cs
@@ -264,6 +264,7 @@ namespace Terminal.Gui {
Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
+ Colors.Menu.Disabled = MakeColor(Curses.COLOR_WHITE, Curses.COLOR_CYAN);
Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs
index 0487d0d7b..aec15a73c 100644
--- a/Terminal.Gui/Drivers/NetDriver.cs
+++ b/Terminal.Gui/Drivers/NetDriver.cs
@@ -144,6 +144,7 @@ namespace Terminal.Gui {
Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
+ Colors.Menu.Disabled = MakeColor(ConsoleColor.DarkGray, ConsoleColor.Cyan);
Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs
index 616820144..42fdadf9f 100644
--- a/Terminal.Gui/Drivers/WindowsDriver.cs
+++ b/Terminal.Gui/Drivers/WindowsDriver.cs
@@ -813,7 +813,8 @@ namespace Terminal.Gui {
Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
- Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+ Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
+ Colors.Menu.Disabled = MakeColor(ConsoleColor.DarkGray, ConsoleColor.Cyan);
Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index ee120eb5f..d26b5fd67 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -1,515 +1,796 @@
-//
-// Menu.cs: application menus and submenus
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-// TODO:
-// Add accelerator support, but should also support chords (ShortCut in MenuItem)
-// Allow menus inside menus
-
-using System;
-using NStack;
-using System.Linq;
-
-namespace Terminal.Gui {
-
- ///
- /// A menu item has a title, an associated help text, and an action to execute on activation.
- ///
- public class MenuItem {
-
- ///
- /// Initializes a new .
- ///
- /// Title for the menu item.
- /// Help text to display.
- /// Action to invoke when the menu item is activated.
- public MenuItem (ustring title, string help, Action action)
- {
- Title = title ?? "";
- Help = help ?? "";
- Action = action;
- bool nextIsHot = false;
- foreach (var x in Title) {
- if (x == '_')
- nextIsHot = true;
- else {
- if (nextIsHot) {
- HotKey = Char.ToUpper ((char)x);
- break;
- }
- nextIsHot = false;
- }
- }
- }
-
- //
- //
-
- ///
- /// The hotkey is used when the menu is active, the shortcut can be triggered when the menu is not active.
- /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
- /// if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
- ///
- public Rune HotKey;
-
- ///
- /// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
- ///
- public Key ShortCut;
-
- ///
- /// Gets or sets the title.
- ///
- /// The title.
- public ustring Title { get; set; }
-
- ///
- /// Gets or sets the help text for the menu item.
- ///
- /// The help text.
- public ustring Help { get; set; }
-
- ///
- /// Gets or sets the action to be invoked when the menu is triggered
- ///
- /// Method to invoke.
- public Action Action { get; set; }
- internal int Width => Title.Length + Help.Length + 1 + 2;
- }
-
- ///
- /// A menu bar item contains other menu items.
- ///
- public class MenuBarItem {
- public MenuBarItem (ustring title, MenuItem [] children)
- {
- SetTitle (title ?? "");
- Children = children;
- }
-
- void SetTitle (ustring title)
- {
- if (title == null)
- title = "";
- Title = title;
- int len = 0;
- foreach (var ch in Title) {
- if (ch == '_')
- continue;
- len++;
- }
- TitleLength = len;
- }
-
- ///
- /// Gets or sets the title to display.
- ///
- /// The title.
- public ustring Title { get; set; }
-
- ///
- /// Gets or sets the children for this MenuBarItem
- ///
- /// The children.
- public MenuItem [] Children { get; set; }
- internal int TitleLength { get; private set; }
- }
-
- class Menu : View {
- MenuBarItem barItems;
- MenuBar host;
- int current;
-
- static Rect MakeFrame (int x, int y, MenuItem [] items)
- {
- int maxW = items.Max(z=>z?.Width) ?? 0;
-
- return new Rect (x, y, maxW + 2, items.Length + 2);
- }
-
- public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
- {
- this.barItems = barItems;
- this.host = host;
- current = -1;
- for (int i = 0; i < barItems.Children.Length; i++) {
- if (barItems.Children[i] != null) {
- current = i;
- break;
- }
- }
- ColorScheme = Colors.Menu;
- CanFocus = true;
- }
-
- public override void Redraw (Rect region)
- {
- Driver.SetAttribute (ColorScheme.Normal);
- DrawFrame (region, padding: 0, fill: true);
-
- for (int i = 0; i < barItems.Children.Length; i++){
- var item = barItems.Children [i];
- Move (1, i+1);
- Driver.SetAttribute (item == null ? Colors.Base.Focus : i == current ? ColorScheme.Focus : ColorScheme.Normal);
- for (int p = 0; p < Frame.Width-2; p++)
- if (item == null)
- Driver.AddRune (Driver.HLine);
- else
- Driver.AddRune (' ');
-
- if (item == null)
- continue;
-
- Move (2, i + 1);
- DrawHotString (item.Title,
- i == current? ColorScheme.HotFocus : ColorScheme.HotNormal,
- i == current ? ColorScheme.Focus : ColorScheme.Normal);
-
- // The help string
- var l = item.Help.Length;
- Move (Frame.Width - l - 2, 1 + i);
- Driver.AddStr (item.Help);
- }
- }
-
- public override void PositionCursor ()
- {
- Move (2, 1 + current);
- }
-
- void Run (Action action)
- {
- if (action == null)
- return;
-
- Application.MainLoop.AddIdle (() => {
- action ();
- return false;
- });
- }
-
- public override bool ProcessKey (KeyEvent kb)
- {
- switch (kb.Key) {
- case Key.CursorUp:
- if (current == -1)
- break;
- do {
- current--;
- if (current < 0)
- current = barItems.Children.Length - 1;
- } while (barItems.Children [current] == null);
- SetNeedsDisplay ();
- break;
- case Key.CursorDown:
- do {
- current++;
- if (current == barItems.Children.Length)
- current = 0;
- } while (barItems.Children [current] == null);
- SetNeedsDisplay ();
- break;
- case Key.CursorLeft:
- host.PreviousMenu ();
- break;
- case Key.CursorRight:
- host.NextMenu ();
- break;
- case Key.Esc:
- host.CloseMenu ();
- break;
- case Key.Enter:
- host.CloseMenu ();
- Run (barItems.Children [current].Action);
- break;
- default:
- // TODO: rune-ify
- if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
- var x = Char.ToUpper ((char)kb.KeyValue);
-
- foreach (var item in barItems.Children) {
- if (item.HotKey == x) {
- host.CloseMenu ();
- Run (item.Action);
- return true;
- }
- }
- }
- break;
- }
- return true;
- }
-
- public override bool MouseEvent(MouseEvent me)
- {
- if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
- if (me.Y < 1)
- return true;
- var item = me.Y - 1;
- if (item >= barItems.Children.Length)
- return true;
- host.CloseMenu ();
- Run (barItems.Children [item].Action);
- return true;
- }
- if (me.Flags == MouseFlags.Button1Pressed) {
- if (me.Y < 1)
- return true;
- if (me.Y - 1 >= barItems.Children.Length)
- return true;
- current = me.Y - 1;
- SetNeedsDisplay ();
- return true;
- }
- return false;
- }
- }
-
- ///
- /// A menu bar for your application.
- ///
- public class MenuBar : View {
- ///
- /// The menus that were defined when the menubar was created. This can be updated if the menu is not currently visible.
- ///
- /// The menu array.
- public MenuBarItem [] Menus { get; set; }
- int selected;
- Action action;
-
-
- ///
- /// Initializes a new instance of the class with the specified set of toplevel menu items.
- ///
- /// Individual menu items, if one of those contains a null, then a separator is drawn.
- public MenuBar (MenuBarItem [] menus) : base ()
- {
- X = 0;
- Y = 0;
- Width = Dim.Fill ();
- Height = 1;
- Menus = menus;
- CanFocus = false;
- selected = -1;
- ColorScheme = Colors.Menu;
- }
-
- public override void Redraw (Rect region)
- {
- Move (0, 0);
- Driver.SetAttribute (Colors.Base.Focus);
- for (int i = 0; i < Frame.Width; i++)
- Driver.AddRune (' ');
-
- Move (1, 0);
- int pos = 1;
-
- for (int i = 0; i < Menus.Length; i++) {
- var menu = Menus [i];
- Move (pos, 0);
- Attribute hotColor, normalColor;
- if (i == selected){
- hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
- normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
- } else {
- hotColor = Colors.Base.Focus;
- normalColor = Colors.Base.Focus;
- }
- DrawHotString (" " + menu.Title + " " + " ", hotColor, normalColor);
- pos += menu.TitleLength+ 3;
- }
- PositionCursor ();
- }
-
- public override void PositionCursor ()
- {
- int pos = 0;
- for (int i = 0; i < Menus.Length; i++) {
- if (i == selected) {
- pos++;
- Move (pos, 0);
- return;
- } else {
- pos += Menus [i].TitleLength + 4;
- }
- }
- Move (0, 0);
- }
-
- void Selected (MenuItem item)
- {
- // TODO: Running = false;
- action = item.Action;
- }
-
- public event EventHandler OnOpenMenu;
- Menu openMenu;
- View previousFocused;
-
- void OpenMenu (int index)
- {
- OnOpenMenu?.Invoke(this, null);
- if (openMenu != null)
- SuperView.Remove (openMenu);
-
- int pos = 0;
- for (int i = 0; i < index; i++)
- pos += Menus [i].Title.Length + 3;
-
- openMenu = new Menu (this, pos, 1, Menus [index]);
-
- SuperView.Add (openMenu);
- SuperView.SetFocus (openMenu);
- }
-
- // Starts the menu from a hotkey
- void StartMenu ()
- {
- if (openMenu != null)
- return;
- selected = 0;
- SetNeedsDisplay ();
-
- previousFocused = SuperView.Focused;
- OpenMenu (selected);
- }
-
- // Activates the menu, handles either first focus, or activating an entry when it was already active
- // For mouse events.
- void Activate (int idx)
- {
- selected = idx;
- if (openMenu == null)
- previousFocused = SuperView.Focused;
-
- OpenMenu (idx);
- SetNeedsDisplay ();
- }
-
- internal void CloseMenu ()
- {
- selected = -1;
- SetNeedsDisplay ();
- SuperView.Remove (openMenu);
- previousFocused?.SuperView?.SetFocus (previousFocused);
- openMenu = null;
- }
-
- internal void PreviousMenu ()
- {
- if (selected <= 0)
- selected = Menus.Length - 1;
- else
- selected--;
-
- OpenMenu (selected);
- }
-
- internal void NextMenu ()
- {
- if (selected == -1)
- selected = 0;
- else if (selected + 1 == Menus.Length)
- selected = 0;
- else
- selected++;
- OpenMenu (selected);
- }
-
- internal bool FindAndOpenMenuByHotkey(KeyEvent kb)
- {
- int pos = 0;
+//
+// Menu.cs: application menus and submenus
+//
+// Authors:
+// Miguel de Icaza (miguel@gnome.org)
+//
+// TODO:
+// Add accelerator support, but should also support chords (ShortCut in MenuItem)
+// Allow menus inside menus
+
+using System;
+using NStack;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Terminal.Gui {
+
+ ///
+ /// A menu item has a title, an associated help text, and an action to execute on activation.
+ ///
+ public class MenuItem {
+
+ ///
+ /// Initializes a new .
+ ///
+ /// Title for the menu item.
+ /// Help text to display.
+ /// Action to invoke when the menu item is activated.
+ /// Function to determine if the action can currently be executred.
+ public MenuItem (ustring title, string help, Action action, Func canExecute = null)
+ {
+ Title = title ?? "";
+ Help = help ?? "";
+ Action = action;
+ CanExecute = canExecute;
+ bool nextIsHot = false;
+ foreach (var x in Title) {
+ if (x == '_')
+ nextIsHot = true;
+ else {
+ if (nextIsHot) {
+ HotKey = Char.ToUpper ((char)x);
+ break;
+ }
+ nextIsHot = false;
+ }
+ }
+ }
+
+ public MenuItem(ustring title, MenuBarItem subMenu) : this (title, "", null)
+ {
+ SubMenu = subMenu;
+ IsFromSubMenu = true;
+ }
+
+ //
+ //
+
+ ///
+ /// The hotkey is used when the menu is active, the shortcut can be triggered when the menu is not active.
+ /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
+ /// if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
+ ///
+ public Rune HotKey;
+
+ ///
+ /// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
+ ///
+ public Key ShortCut;
+
+ ///
+ /// Gets or sets the title.
+ ///
+ /// The title.
+ public ustring Title { get; set; }
+
+ ///
+ /// Gets or sets the help text for the menu item.
+ ///
+ /// The help text.
+ public ustring Help { get; set; }
+
+ ///
+ /// Gets or sets the action to be invoked when the menu is triggered
+ ///
+ /// Method to invoke.
+ public Action Action { get; set; }
+
+ ///
+ /// Gets or sets the action to be invoked if the menu can be triggered
+ ///
+ /// Function to determine if action is ready to be executed.
+ public Func CanExecute { get; set; }
+
+ ///
+ /// Shortcut to check if the menu item is enabled
+ ///
+ public bool IsEnabled ()
+ {
+ return CanExecute == null ? true : CanExecute ();
+ }
+
+ internal int Width => Title.Length + Help.Length + 1 + 2;
+
+ ///
+ /// Gets or sets the parent for this MenuBarItem
+ ///
+ /// The parent.
+ internal MenuBarItem SubMenu { get; set; }
+ internal bool IsFromSubMenu { get; set; }
+
+ ///
+ /// Merely a debugging aid to see the interaction with main
+ ///
+ public MenuItem GetMenuItem ()
+ {
+ return this;
+ }
+
+ ///
+ /// Merely a debugging aid to see the interaction with main
+ ///
+ public bool GetMenuBarItem ()
+ {
+ return IsFromSubMenu;
+ }
+ }
+
+ ///
+ /// A menu bar item contains other menu items.
+ ///
+ public class MenuBarItem {
+ public MenuBarItem (ustring title, MenuItem [] children)
+ {
+ SetTitle (title ?? "");
+ Children = children;
+ }
+
+ public MenuBarItem (MenuItem[] children) : this (new string (' ', GetMaxTitleLength (children)), children)
+ {
+ }
+
+ private static int GetMaxTitleLength (MenuItem[] children)
+ {
+ int maxLength = 0;
+ foreach (var item in children) {
+ int len = GetMenuBarItemLength (item.Title);
+ if (len > maxLength)
+ maxLength = len;
+ item.IsFromSubMenu = true;
+ }
+
+ return maxLength;
+ }
+
+ void SetTitle (ustring title)
+ {
+ if (title == null)
+ title = "";
+ Title = title;
+ TitleLength = GetMenuBarItemLength(Title);
+ }
+
+ static int GetMenuBarItemLength(ustring title)
+ {
+ int len = 0;
+ foreach (var ch in title) {
+ if (ch == '_')
+ continue;
+ len++;
+ }
+
+ return len;
+ }
+
+ ///
+ /// Gets or sets the title to display.
+ ///
+ /// The title.
+ public ustring Title { get; set; }
+
+ ///
+ /// Gets or sets the children for this MenuBarItem
+ ///
+ /// The children.
+ public MenuItem [] Children { get; set; }
+ internal int TitleLength { get; private set; }
+ }
+
+ class Menu : View {
+ MenuBarItem barItems;
+ MenuBar host;
+ int current;
+
+ static Rect MakeFrame (int x, int y, MenuItem [] items)
+ {
+ int maxW = 0;
+
+ foreach (var item in items) {
+ if (item == null) continue;
+ var l = item.Width;
+ maxW = Math.Max (l, maxW);
+ }
+
+ return new Rect (x, y, maxW + 2, items.Length + 2);
+ }
+
+ public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
+ {
+ this.barItems = barItems;
+ this.host = host;
+ current = -1;
+ for (int i = 0; i < barItems.Children.Length; i++) {
+ if (barItems.Children[i] != null) {
+ current = i;
+ break;
+ }
+ }
+ ColorScheme = Colors.Menu;
+ CanFocus = true;
+ WantMousePositionReports = host.WantMousePositionReports;
+ selectedSub = -1;
+ OnLeave += Menu_OnLeave;
+ }
+
+ internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
+ {
+ if (item != null) {
+ if (index == current) return ColorScheme.Focus;
+ if (!item.IsEnabled ()) return ColorScheme.Disabled;
+ }
+ return ColorScheme.Normal;
+ }
+
+ public override void Redraw (Rect region)
+ {
+ Driver.SetAttribute (ColorScheme.Normal);
+ DrawFrame (region, padding: 0, fill: true);
+
+ for (int i = 0; i < barItems.Children.Length; i++) {
+ var item = barItems.Children [i];
+ Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
+ if (item == null) {
+ Move (0, i + 1);
+ Driver.AddRune (Driver.LeftTee);
+ } else
+ Move (1, i+1);
+
+ Driver.SetAttribute (DetermineColorSchemeFor (item, i));
+ for (int p = 0; p < Frame.Width - 2; p++)
+ if (item == null)
+ Driver.AddRune (Driver.HLine);
+ else if (p == Frame.Width - 3 && barItems.Children[i].SubMenu != null)
+ Driver.AddRune ('>');
+ else
+ Driver.AddRune (' ');
+
+ if (item == null) {
+ Move (region.Right - 1, i + 1);
+ Driver.AddRune (Driver.RightTee);
+ continue;
+ }
+
+ Move (2, i + 1);
+ if (!item.IsEnabled ())
+ DrawHotString (item.Title, ColorScheme.Disabled, ColorScheme.Disabled);
+ else
+ DrawHotString (item.Title,
+ i == current? ColorScheme.HotFocus : ColorScheme.HotNormal,
+ i == current ? ColorScheme.Focus : ColorScheme.Normal);
+
+ // The help string
+ var l = item.Help.Length;
+ Move (Frame.Width - l - 2, 1 + i);
+ Driver.AddStr (item.Help);
+ }
+ PositionCursor ();
+ }
+
+ public override void PositionCursor ()
+ {
+ Move (2, 1 + current);
+ }
+
+ void Run (Action action)
+ {
+ if (action == null)
+ return;
+
+ CloseSubMenu ();
+ host.CloseMenu ();
+
+ Application.MainLoop.AddIdle (() => {
+ action ();
+ return false;
+ });
+ }
+
+ public override bool ProcessKey (KeyEvent kb)
+ {
+ bool disabled;
+ switch (kb.Key) {
+ case Key.CursorUp:
+ if (current == -1)
+ break;
+ do {
+ disabled = false;
+ current--;
+ if (host.UseKeysUpDownAsKeysLeftRight) {
+ if (current == -1 && barItems.Children [current + 1].IsFromSubMenu && selectedSub > -1) {
+ current++;
+ PreviousMenu ();
+ break;
+ }
+ }
+ if (current < 0)
+ current = barItems.Children.Length - 1;
+ var item = barItems.Children [current];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ } while (barItems.Children [current] == null || disabled);
+ SetNeedsDisplay ();
+ break;
+ case Key.CursorDown:
+ do {
+ current++;
+ disabled = false;
+ if (current == barItems.Children.Length)
+ current = 0;
+ var item = barItems.Children [current];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (host.UseKeysUpDownAsKeysLeftRight && barItems.Children [current] != null && !disabled) {
+ CheckSubMenu ();
+ break;
+ }
+ } while (barItems.Children [current] == null || disabled);
+ SetNeedsDisplay ();
+ break;
+ case Key.CursorLeft:
+ PreviousMenu ();
+ break;
+ case Key.CursorRight:
+ NextMenu ();
+ break;
+ case Key.Esc:
+ CloseSubMenu ();
+ host.CloseMenu ();
+ break;
+ case Key.Enter:
+ CheckSubMenu ();
+ Run (barItems.Children [current].Action);
+ break;
+ default:
+ // TODO: rune-ify
+ if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
+ var x = Char.ToUpper ((char)kb.KeyValue);
+
+ foreach (var item in barItems.Children) {
+ if (item == null) continue;
+ if (item.IsEnabled () && item.HotKey == x) {
+ host.CloseMenu ();
+ Run (item.Action);
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+ public override bool MouseEvent(MouseEvent me)
+ {
+ bool disabled;
+ if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
+ disabled = false;
+ if (me.Y < 1)
+ return true;
+ var meY = me.Y - 1;
+ if (meY >= barItems.Children.Length)
+ return true;
+ var item = barItems.Children [meY];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (item != null && !disabled)
+ Run (barItems.Children [meY].Action);
+ return true;
+ }
+ if (me.Flags == MouseFlags.Button1Pressed ||
+ me.Flags == MouseFlags.ReportMousePosition) {
+ disabled = false;
+ if (me.Y < 1)
+ return true;
+ if (me.Y - 1 >= barItems.Children.Length)
+ return true;
+ var item = barItems.Children [me.Y - 1];
+ if (item == null || !item.IsEnabled ()) disabled = true;
+ if (item != null && !disabled)
+ current = me.Y - 1;
+ HasFocus = true;
+ SetNeedsDisplay ();
+ CheckSubMenu ();
+ return true;
+ }
+ return false;
+ }
+
+ private void CheckSubMenu ()
+ {
+ if (barItems.Children [current] == null)
+ return;
+ var subMenu = barItems.Children [current].SubMenu;
+ if (subMenu != null) {
+ int pos = -1;
+ if (openSubMenu != null)
+ pos = openSubMenu.FindIndex (o => o?.barItems == subMenu);
+ Activate (pos);
+ } else if (openSubMenu != null && !barItems.Children [current].IsFromSubMenu)
+ CloseSubMenu ();
+ }
+
+ internal static List