diff --git a/Example/demo.cs b/Example/demo.cs index 8c794f305..9ab69011c 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -105,7 +105,7 @@ static class Demo { static void ShowTextAlignments () { - var container = new Window ($"Show Text Alignments") { + var container = new Window ("Show Text Alignments - Press Esc to return") { X = 0, Y = 0, Width = Dim.Fill (), @@ -244,12 +244,12 @@ static class Demo { var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }), + new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }, null, null, Key.AltMask | Key.F4), }), new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", null), - new MenuItem ("C_ut", "", null), - new MenuItem ("_Paste", "", null) + new MenuItem ("_Copy", "", null, null, null, Key.ControlC), + new MenuItem ("C_ut", "", null, null, null, Key.ControlX), + new MenuItem ("_Paste", "", null, null, null, Key.ControlV) }), }); ntop.Add (menu); @@ -309,13 +309,12 @@ static class Demo { MessageBox.Query (50, 7, "Selected File", d.FilePaths.Count > 0 ? string.Join (", ", d.FilePaths) : d.FilePath, "Ok"); } - public static void ShowHex (Toplevel top) + public static void ShowHex () { - var tframe = top.Frame; - var ntop = new Toplevel (tframe); + var ntop = Application.Top; var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Close", "", () => {Application.RequestStop ();}), + new MenuItem ("_Close", "", () => { running = MainApp; Application.RequestStop (); }, null, null, Key.AltMask | Key.F4), }), }); ntop.Add (menu); @@ -338,7 +337,6 @@ static class Demo { }; win.Add (hex); Application.Run (ntop); - } public class MenuItemDetails : MenuItem { @@ -382,7 +380,7 @@ static class Demo { static void Copy () { - TextField textField = menu.LastFocused as TextField; + TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; if (textField != null && textField.SelectedLength != 0) { textField.Copy (); } @@ -390,7 +388,7 @@ static class Demo { static void Cut () { - TextField textField = menu.LastFocused as TextField; + TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; if (textField != null && textField.SelectedLength != 0) { textField.Cut (); } @@ -398,10 +396,8 @@ static class Demo { static void Paste () { - TextField textField = menu.LastFocused as TextField; - if (textField != null) { - textField.Paste (); - } + TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; + textField?.Paste (); } static void Help () @@ -601,31 +597,31 @@ static class Demo { menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }), - new MenuItem ("_New", "Creates new file", NewFile), - new MenuItem ("_Open", "", Open), - new MenuItem ("_Hex", "", () => ShowHex (top)), - new MenuItem ("_Close", "", () => Close ()), + new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }, null, null, Key.AltMask | Key.ControlD), + new MenuItem ("_New", "Creates new file", NewFile, null, null, Key.AltMask | Key.ControlN), + new MenuItem ("_Open", "", Open, null, null, Key.AltMask | Key.ControlO), + new MenuItem ("_Hex", "", () => { running = ShowHex; Application.RequestStop (); }, null, null, Key.AltMask | Key.ControlH), + new MenuItem ("_Close", "", Close, null, null, Key.AltMask | Key.F4), new MenuItem ("_Disabled", "", () => { }, () => false), null, - new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }) + new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }, null, null, Key.ControlQ) }), new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", Copy), - new MenuItem ("C_ut", "", Cut), - new MenuItem ("_Paste", "", Paste), + new MenuItem ("_Copy", "", Copy, null, null, Key.AltMask | Key.ControlC), + new MenuItem ("C_ut", "", Cut, null, null, Key.AltMask | Key.ControlX), + new MenuItem ("_Paste", "", Paste, null, null, Key.AltMask | Key.ControlV), new MenuBarItem ("_Find and Replace", new MenuItem [] { menuItems [0], menuItems [1] }), menuItems[3] }), new MenuBarItem ("_List Demos", new MenuItem [] { - new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), - new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), - new MenuItem ("Search Single Item", "", ComboBoxDemo) + new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true), null, null, Key.AltMask + 0.ToString () [0]), + new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false), null, null, Key.AltMask + 1.ToString () [0]), + new MenuItem ("Search Single Item", "", ComboBoxDemo, null, null, Key.AltMask + 2.ToString () [0]) }), new MenuBarItem ("A_ssorted", new MenuItem [] { - new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), - new MenuItem ("_OnKeyDown/Press/Up", "", () => OnKeyDownPressUpDemo ()) + new MenuItem ("_Show text alignments", "", () => ShowTextAlignments (), null, null, Key.AltMask | Key.ControlG), + new MenuItem ("_OnKeyDown/Press/Up", "", () => OnKeyDownPressUpDemo (), null, null, Key.AltMask | Key.ControlK) }), new MenuBarItem ("_Test Menu and SubMenus", new MenuBarItem [] { new MenuBarItem ("SubMenu1Item_1", new MenuBarItem [] { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index fc652219b..20dedb5c6 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -439,11 +439,11 @@ namespace Terminal.Gui { if (keyModifiers == null) keyModifiers = new KeyModifiers (); - if (!keyModifiers.Shift && key.HasFlag (Key.ShiftMask)) + if (!keyModifiers.Shift && (key & Key.ShiftMask) != 0) keyModifiers.Shift = true; - if (!keyModifiers.Alt && key.HasFlag (Key.AltMask)) + if (!keyModifiers.Alt && (key & Key.AltMask) != 0) keyModifiers.Alt = true; - if (!keyModifiers.Ctrl && key.HasFlag (Key.CtrlMask)) + if (!keyModifiers.Ctrl && (key & Key.CtrlMask) != 0) keyModifiers.Ctrl = true; return keyModifiers; @@ -472,8 +472,23 @@ namespace Terminal.Gui { mouseHandler (ToDriverMouse (ev)); return; } - keyHandler (new KeyEvent (MapCursesKey (wch), keyModifiers)); - keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers)); + k = MapCursesKey (wch); + if (wch >= 277 && wch <= 288) { // Shift+(F1 - F12) + wch -= 12; + k = Key.ShiftMask | MapCursesKey (wch); + } else if (wch >= 289 && wch <= 300) { // Ctrl+(F1 - F12) + wch -= 24; + k = Key.CtrlMask | MapCursesKey (wch); + } else if (wch >= 301 && wch <= 312) { // Ctrl+Shift+(F1 - F12) + wch -= 36; + k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch); + } else if (wch >= 313 && wch <= 324) { // Alt+(F1 - F12) + wch -= 48; + k = Key.AltMask | MapCursesKey (wch); + } + keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); + keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); return; } @@ -498,19 +513,65 @@ namespace Terminal.Gui { } else if (wch2 - (int)Key.Space >= 'A' && wch2 - (int)Key.Space <= 'Z') { k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space)); key = new KeyEvent (k, MapKeyModifiers (k)); - } else if (wch2 >= '1' && wch <= '9') { - k = (Key)((int)Key.F1 + (wch2 - '0' - 1)); + } else if (wch2 >= (uint)Key.ControlA && wch2 <= (uint)Key.ControlZ) { + k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2); key = new KeyEvent (k, MapKeyModifiers (k)); - } else if (wch2 == '0') { - k = Key.F10; + } else if (wch2 >= '0' && wch2 <= '9') { + k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - '0')); key = new KeyEvent (k, MapKeyModifiers (k)); } else if (wch2 == 27) { k = (Key)wch2; key = new KeyEvent (k, MapKeyModifiers (k)); + } else if (wch2 == 91) { + int [] c = null; + while (code == 0) { + code = Curses.get_wch (out wch2); + if (wch2 > 0) { + Array.Resize (ref c, c == null ? 1 : c.Length + 1); + c [c.Length - 1] = wch2; + } + } + if (c [0] == 49 && c [1] == 59 && c [2] == 55) { // Ctrl+Alt+(F1 - F4) + wch2 = c [3] + 185; + k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2); + } else if (c [0] == 49 && c [2] == 59 && c [3] == 55 && c [4] == 126) { // Ctrl+Alt+(F5 - F8) + wch2 = c [1] == 53 ? c [1] + 216 : c [1] + 215; + k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2); + } else if (c [0] == 50 && c [2] == 59 && c [3] == 55 && c [4] == 126) { // Ctrl+Alt+(F9 - F12) + wch2 = c [1] < 51 ? c [1] + 225 : c [1] + 224; + k = Key.CtrlMask | Key.AltMask | MapCursesKey (wch2); + } else if (c [0] == 49 && c [1] == 59 && c [2] == 56) { // Ctrl+Shift+Alt+(F1 - F4) + wch2 = c [3] + 185; + k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2); + } else if (c [0] == 49 && c [2] == 59 && c [3] == 56 && c [4] == 126) { // Ctrl+Shift+Alt+(F5 - F8) + wch2 = c [1] == 53 ? c [1] + 216 : c [1] + 215; + k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2); + } else if (c [0] == 50 && c [2] == 59 && c [3] == 56 && c [4] == 126) { // Ctrl+Shift+Alt+(F9 - F12) + wch2 = c [1] < 51 ? c [1] + 225 : c [1] + 224; + k = Key.CtrlMask | Key.ShiftMask | Key.AltMask | MapCursesKey (wch2); + } else { + k = MapCursesKey (wch2); + } + key = new KeyEvent (k, MapKeyModifiers (k)); } else { - k = Key.AltMask | (Key)wch2; + // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. + if (wch2 == 0) { + k = Key.CtrlMask | Key.AltMask | Key.Space; + } else { + if (((Key)wch2).ToString ().Contains ("Control")) { + keyModifiers.Ctrl = true; + } + if (wch2 < 256) { + k = Key.AltMask | (Key)wch2; + } else { + //k = (Key)wch2; + //keyModifiers.Alt = true; + k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2); + } + } key = new KeyEvent (k, MapKeyModifiers (k)); } + keyDownHandler (key); keyHandler (key); } else { k = Key.Esc; @@ -521,7 +582,16 @@ namespace Terminal.Gui { keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); keyHandler (new KeyEvent (k, MapKeyModifiers (k))); } else { + // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. k = (Key)wch; + if (wch == 0) { + k = Key.CtrlMask | Key.Space; + } else if (wch >= (uint)Key.ControlA && wch <= (uint)Key.ControlZ) { + k = (Key)wch; + keyModifiers.Ctrl = true; + } else if (wch >= 'A' && wch <= 'Z') { + keyModifiers.Shift = true; + } keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); keyHandler (new KeyEvent (k, MapKeyModifiers (k))); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index ab963b8fc..7fb87438d 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -271,7 +271,9 @@ namespace Unix.Terminal { static public int mouseinterval (int interval) => methods.mouseinterval (interval); } +#pragma warning disable RCS1102 // Make class static. internal class Delegates { +#pragma warning restore RCS1102 // Make class static. public delegate IntPtr initscr (); public delegate int endwin (); public delegate bool isendwin (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index a08da19f2..9812983cc 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1118,30 +1118,42 @@ namespace Terminal.Gui { if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) + if (keyInfo.Modifiers == ConsoleModifiers.Control) { return (Key)((uint)Key.ControlA + delta); - if (keyInfo.Modifiers == ConsoleModifiers.Alt) + } + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta)); + } if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0) - return (Key)(((uint)Key.AltMask) + ((uint)Key.ControlA + delta)); - else + if (keyInfo.KeyChar == 0) { + return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.ControlA + delta)); + } else { return (Key)((uint)keyInfo.KeyChar); + } } //return (Key)((uint)alphaBase + delta); return (Key)((uint)keyInfo.KeyChar); } if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { return (Key)(((uint)Key.AltMask) | ((uint)'0' + delta)); - + } + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)'0' + delta)); + } + if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) { + return MapKeyModifiers (keyInfo, (Key)((uint)'0' + delta)); + } return (Key)((uint)keyInfo.KeyChar); } if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { var delta = key - ConsoleKey.F1; + if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); + } - return (Key)((int)Key.F1 + delta); + return (Key)((uint)Key.F1 + delta); } if (keyInfo.KeyChar != 0) { return (Key)((uint)keyInfo.KeyChar); @@ -1153,11 +1165,11 @@ namespace Terminal.Gui { private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) { Key keyMod = new Key (); - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) + if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) keyMod = Key.ShiftMask; - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) + if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) keyMod |= Key.CtrlMask; - if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)) + if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) keyMod |= Key.AltMask; return keyMod != Key.ControlSpace ? keyMod | key : key; diff --git a/Terminal.Gui/Core/Event.cs b/Terminal.Gui/Core/Event.cs index d107bebc1..0939fb435 100644 --- a/Terminal.Gui/Core/Event.cs +++ b/Terminal.Gui/Core/Event.cs @@ -318,6 +318,46 @@ namespace Terminal.Gui { /// BackTab, /// + /// Digit 0. + /// + D0 = 48, + /// + /// Digit 1. + /// + D1, + /// + /// Digit 2. + /// + D2, + /// + /// Digit 3. + /// + D3, + /// + /// Digit 4. + /// + D4, + /// + /// Digit 5. + /// + D5, + /// + /// Digit 6. + /// + D6, + /// + /// Digit 7. + /// + D7, + /// + /// Digit 8. + /// + D8, + /// + /// Digit 9. + /// + D9, + /// /// A key with an unknown mapping was raised. /// Unknown diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 4ce014f7f..97d8d4df8 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2055,5 +2055,26 @@ namespace Terminal.Gui { return true; } + + /// + /// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later. + /// + /// The to check. + /// The with all the keys modifiers. + public Key GetModifiersKey (KeyEvent kb) + { + var key = kb.Key; + if (kb.IsAlt && (key & Key.AltMask) == 0) { + key |= Key.AltMask; + } + if (kb.IsCtrl && (key & Key.CtrlMask) == 0) { + key |= Key.CtrlMask; + } + if (kb.IsShift && (key & Key.ShiftMask) == 0) { + key |= Key.ShiftMask; + } + + return key; + } } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 67d27a85f..cc1644ebe 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -40,14 +40,19 @@ namespace Terminal.Gui { /// A has a title, an associated help text, and an action to execute on activation. /// public class MenuItem { + ustring title; + Key shortCut; /// /// Initializes a new instance of /// - public MenuItem () + public MenuItem (Key shortCut = Key.ControlSpace) { Title = ""; Help = ""; + if (shortCut != Key.ControlSpace) { + ShortCut = shortCut; + } } /// @@ -57,26 +62,18 @@ namespace Terminal.Gui { /// Help text to display. /// Action to invoke when the menu item is activated. /// Function to determine if the action can currently be executed. - /// The parent of this menu item. - public MenuItem (ustring title, ustring help, Action action, Func canExecute = null, MenuItem parent = null) + /// The of this menu item. + /// The keystroke combination. + public MenuItem (ustring title, ustring help, Action action, Func canExecute = null, MenuItem parent = null, Key shortCut = Key.ControlSpace) { 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; - } - } Parent = parent; + if (shortCut != Key.ControlSpace) { + ShortCut = shortCut; + } } /// @@ -86,11 +83,41 @@ namespace Terminal.Gui { /// 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 { + get => shortCut; + set { + if (shortCut != value) { + if (GetKeyToString (value).Contains ("Control")) { + shortCut = Key.CtrlMask | value; + } else { + shortCut = value; + } + ShortCutTag = GetShortCutTag (shortCut); + } + } + } + + /// + /// The keystroke combination used in the as string. + /// + public ustring ShortCutTag { get; private set; } = ustring.Empty; + /// /// Gets or sets the title. /// /// The title. - public ustring Title { get; set; } + public ustring Title { + get { return title; } + set { + if (title != value) { + title = value; + GetHotKey (); + } + } + } /// /// Gets or sets the help text for the menu item. @@ -119,7 +146,8 @@ namespace Terminal.Gui { } internal int Width => Title.RuneCount + Help.RuneCount + 1 + 2 + - (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0); + (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + + (ShortCutTag.RuneCount > 0 ? ShortCutTag.RuneCount + 2 : 0); /// /// Sets or gets whether the shows a check indicator or not. See . @@ -157,6 +185,154 @@ namespace Terminal.Gui { { return IsFromSubMenu; } + + void GetHotKey () + { + bool nextIsHot = false; + foreach (var x in title) { + if (x == '_') { + nextIsHot = true; + } else { + if (nextIsHot) { + HotKey = Char.ToUpper ((char)x); + break; + } + nextIsHot = false; + HotKey = default; + } + } + } + + /// + /// Get the key as string. + /// + /// The shortcut key. + /// + public ustring GetShortCutTag (Key shortCut) + { + if (shortCut == Key.ControlSpace) { + return ""; + } + + var k = (uint)shortCut; + var delimiter = MenuBar.ShortCutDelimiter; + ustring tag = ustring.Empty; + var sCut = GetKeyToString (shortCut).ToString (); + if (sCut.Contains ("Control") || (shortCut & Key.CtrlMask) != 0) { + tag = "Ctrl"; + } + if ((shortCut & Key.ShiftMask) != 0) { + if (!tag.IsEmpty) { + tag += delimiter; + } + tag += "Shift"; + } + if ((shortCut & Key.AltMask) != 0) { + if (!tag.IsEmpty) { + tag += delimiter; + } + tag += "Alt"; + } + + ustring [] keys = ustring.Make (sCut).Split (","); + for (int i = 0; i < keys.Length; i++) { + var key = keys [i].TrimSpace (); + if (key == Key.AltMask.ToString () || key == Key.ShiftMask.ToString () || key == Key.CtrlMask.ToString ()) { + continue; + } + if (!tag.IsEmpty) { + tag += delimiter; + } + if (key.Contains ("Control")) { + tag += ((char)key.ElementAt (7)).ToString (); + } else if (!key.Contains ("F") && key.Length > 2 && keys.Length == 1) { + k = (uint)Key.AltMask + k; + tag += ((char)k).ToString (); + } else if (key.Length == 2 && key.StartsWith ("D")) { + tag += ((char)key.ElementAt (1)).ToString (); + } else { + tag += key; + } + } + + return tag; + } + + ustring GetKeyToString (Key key) + { + if (key == Key.ControlSpace) { + return ""; + } + + var mK = key & (Key.AltMask | Key.CtrlMask | Key.ShiftMask); + for (uint i = (uint)Key.F1; i < (uint)Key.F12; i++) { + if ((key | (Key)i) == key) { + mK |= (Key)i; + } + } + var k = key; + k &= ~mK; + int.TryParse (k.ToString (), out int c); + var s = mK == Key.ControlSpace ? "" : mK.ToString (); + if (s != "" && (k != Key.ControlSpace || c > 0)) { + s += ","; + } + s += c == 0 ? k == Key.ControlSpace ? "" : k.ToString () : ((char)c).ToString (); + return s; + } + + /// + /// Allows to generate a from a + /// + /// The key as string. + /// + public Key CreateShortCutFromTag (ustring tag) + { + var sCut = tag; + if (sCut.IsEmpty) { + return default; + } + + Key key = Key.ControlSpace; + var hasCtrl = false; + var delimiter = MenuBar.ShortCutDelimiter; + + ustring [] keys = sCut.Split (MenuBar.ShortCutDelimiter); + for (int i = 0; i < keys.Length; i++) { + var k = keys [i]; + if (k == "Ctrl") { + hasCtrl = true; + key |= Key.CtrlMask; + } else if (k == "Shift") { + key |= Key.ShiftMask; + } else if (k == "Alt") { + key |= Key.AltMask; + } else if (k.StartsWith ("F") && k.Length > 1) { + int.TryParse (k.Substring (1).ToString (), out int n); + for (uint j = (uint)Key.F1; j < (uint)Key.F12; j++) { + int.TryParse (((Key)j).ToString ().Substring (1), out int f); + if (f == n) { + key |= (Key)j; + } + } + } else if (k [0] >= 'A' && k [0] <= 'Z') { + if (hasCtrl) { + var n = k [0] - 'A' + 1; + var d = n - (uint)Key.ControlA; + key |= (Key)((uint)Key.ControlA + d); + } else { + key |= (Key)k [0]; + } + } else if (k [0] >= '0' && k [0] <= '9') { + //var n = k [0] - (uint)Key.D0 + 1; + //var d = n - (uint)Key.D0; + //key |= (Key)((uint)Key.D0 + d); + key |= (Key)k [0]; + } + } + + return key; + } } /// @@ -307,7 +483,7 @@ namespace Terminal.Gui { internal int TitleLength => GetMenuBarItemLength (Title); - internal bool IsTopLevel { get => Parent == null && (Children == null || Children.Length == 0); } + internal bool IsTopLevel { get => Parent == null && (Children == null || Children.Length == 0) && Action != null; } } @@ -416,9 +592,16 @@ namespace Terminal.Gui { i == current ? ColorScheme.Focus : ColorScheme.Normal); // The help string - var l = item.Help.RuneCount; + var l = item.ShortCutTag.RuneCount == 0 ? item.Help.RuneCount : item.Help.RuneCount + item.ShortCutTag.RuneCount + 2; Move (Frame.Width - l - 2, 1 + i); Driver.AddStr (item.Help); + + // The shortcut tag string + if (!item.ShortCutTag.IsEmpty) { + l = item.ShortCutTag.RuneCount; + Move (Frame.Width - l - 2, 1 + i); + Driver.AddStr (item.ShortCutTag); + } } PositionCursor (); } @@ -704,6 +887,11 @@ namespace Terminal.Gui { /// public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true; + /// + /// Used for change the shortcut delimiter separator. + /// + public static ustring ShortCutDelimiter { get; set; } = "+"; + /// /// Initializes a new instance of the . /// @@ -828,8 +1016,8 @@ namespace Terminal.Gui { hotColor = ColorScheme.Normal; normalColor = ColorScheme.Normal; } - DrawHotString ($" {menu.Title} ", hotColor, normalColor); - pos += 1 + menu.TitleLength + 2; + DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} {menu.Help} ", hotColor, normalColor); + pos += 1 + menu.TitleLength + (menu.Help.Length > 0 ? menu.Help.Length + 2 : 0) + 2; } PositionCursor (); } @@ -843,14 +1031,14 @@ namespace Terminal.Gui { pos++; if (IsMenuOpen) Move (pos + 1, 0); - else + else { Move (pos + 1, 0); + } return; + } else if (IsMenuOpen) { + pos += 1 + Menus [i].TitleLength + (Menus [i].Help.Length > 0 ? Menus [i].Help.Length + 2 : 0) + 2; } else { - if (IsMenuOpen) - pos += 1 + Menus [i].TitleLength + 2; - else - pos += 2 + Menus [i].TitleLength + 1; + pos += 2 + Menus [i].TitleLength + (Menus [i].Help.Length > 0 ? Menus [i].Help.Length + 2 : 0) + 1; } } //Move (0, 0); @@ -923,7 +1111,7 @@ namespace Terminal.Gui { } for (int i = 0; i < index; i++) - pos += Menus [i].Title.RuneCount + 2; + pos += Menus [i].Title.RuneCount + (Menus [i].Help.RuneCount > 0 ? Menus [i].Help.RuneCount + 2 : 0) + 2; openMenu = new Menu (this, pos, 1, Menus [index]); openCurrentMenu = openMenu; openCurrentMenu.previousSubFocused = openMenu; @@ -1258,6 +1446,44 @@ namespace Terminal.Gui { return false; } + internal bool FindAndOpenMenuByShortCut (KeyEvent kb, MenuItem [] children = null) + { + if (children == null) { + children = Menus; + } + + var key = kb.KeyValue; + var keys = GetModifiersKey (kb); + key |= (int)keys; + //if (kb.IsShift) { + // key |= (int)Key.ShiftMask; + //} + //if (kb.IsAlt) { + // key |= unchecked((int)Key.AltMask); + //} + for (int i = 0; i < children.Length; i++) { + var mi = children [i]; + if (mi == null) { + continue; + } + if ((!(mi is MenuBarItem mbiTopLevel) || mbiTopLevel.IsTopLevel) && mi.ShortCut != Key.ControlSpace && mi.ShortCut == (Key)key) { + var action = mi.Action; + if (action != null) { + Application.MainLoop.AddIdle (() => { + action (); + return false; + }); + } + return true; + } + if (mi is MenuBarItem menuBarItem && !menuBarItem.IsTopLevel && FindAndOpenMenuByShortCut (kb, menuBarItem.Children)) { + return true; + } + } + + return false; + } + private void ProcessMenu (int i, MenuBarItem mi) { if (mi.IsTopLevel) { @@ -1293,7 +1519,7 @@ namespace Terminal.Gui { OnKeyDown (kb); OnKeyUp (kb); return true; - } else if (kb.IsAlt) { + } else if (kb.IsAlt && !kb.IsCtrl && !kb.IsShift) { if (FindAndOpenMenuByHotkey (kb)) return true; } //var kc = kb.KeyValue; @@ -1358,6 +1584,12 @@ namespace Terminal.Gui { return true; } + /// + public override bool ProcessColdKey (KeyEvent kb) + { + return FindAndOpenMenuByShortCut (kb); + } + /// public override bool MouseEvent (MouseEvent me) { @@ -1372,7 +1604,7 @@ namespace Terminal.Gui { int pos = 1; int cx = me.X; for (int i = 0; i < Menus.Length; i++) { - if (cx >= pos && cx < pos + 1 + Menus [i].TitleLength + 2) { + if (cx >= pos && cx < pos + 1 + Menus [i].TitleLength + Menus [i].Help.RuneCount + 2) { if (me.Flags == MouseFlags.Button1Clicked) { if (Menus [i].IsTopLevel) { var menu = new Menu (this, i, 0, Menus [i]); @@ -1424,6 +1656,7 @@ namespace Terminal.Gui { }; v.MouseEvent (nme); + return false; } } else if (!(me.View is MenuBar || me.View is Menu) && (me.Flags.HasFlag (MouseFlags.Button1Clicked) || me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked)) { diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index fdd042da0..6dd9e3ec4 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -345,9 +345,9 @@ namespace Terminal.Gui { // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2 var oldCursorPos = point; - switch (kb.Key) { + switch (GetModifiersKey (kb)) { case Key.DeleteChar: - case Key.ControlD: + case Key.ControlD | Key.CtrlMask: if (ReadOnly) return true; @@ -398,7 +398,7 @@ namespace Terminal.Gui { // Home, C-A case Key.Home: - case Key.ControlA: + case Key.ControlA | Key.CtrlMask: ClearAllSelection (); point = 0; Adjust (); @@ -441,7 +441,7 @@ namespace Terminal.Gui { break; case Key.CursorLeft: - case Key.ControlB: + case Key.ControlB | Key.CtrlMask: ClearAllSelection (); if (point > 0) { point--; @@ -450,14 +450,14 @@ namespace Terminal.Gui { break; case Key.End: - case Key.ControlE: // End + case Key.ControlE | Key.CtrlMask: // End ClearAllSelection (); point = text.Count; Adjust (); break; case Key.CursorRight: - case Key.ControlF: + case Key.ControlF | Key.CtrlMask: ClearAllSelection (); if (point == text.Count) break; @@ -465,7 +465,7 @@ namespace Terminal.Gui { Adjust (); break; - case Key.ControlK: // kill-to-end + case Key.ControlK | Key.CtrlMask: // kill-to-end if (ReadOnly) return true; @@ -478,7 +478,7 @@ namespace Terminal.Gui { break; // Undo - case Key.ControlZ: + case Key.ControlZ | Key.CtrlMask: if (ReadOnly) return true; @@ -494,7 +494,7 @@ namespace Terminal.Gui { break; //Redo - case Key.ControlY: // Control-y, yank + case Key.ControlY | Key.CtrlMask: // Control-y, yank if (ReadOnly) return true; @@ -552,18 +552,18 @@ namespace Terminal.Gui { SetNeedsDisplay (); break; - case Key.ControlC: + case Key.ControlC | Key.CtrlMask: Copy (); break; - case Key.ControlX: + case Key.ControlX | Key.CtrlMask: if (ReadOnly) return true; Cut (); break; - case Key.ControlV: + case Key.ControlV | Key.CtrlMask: Paste (); break; diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 4c71bc0ec..d04dd3fbe 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -19,7 +19,7 @@ namespace UICatalog { } } - class DynamicMenuItemList { + public class DynamicMenuItemList { public ustring Title { get; set; } public MenuItem MenuItem { get; set; } @@ -34,22 +34,24 @@ namespace UICatalog { public override string ToString () => $"{Title}, {MenuItem}"; } - class DynamicMenuItem { + public class DynamicMenuItem { public ustring title = "_New"; public ustring help = ""; public ustring action = ""; public bool isTopLevel; public bool hasSubMenu; public MenuItemCheckStyle checkStyle; + public ustring shortCut; public DynamicMenuItem () { } - public DynamicMenuItem (ustring title) + public DynamicMenuItem (ustring title, bool hasSubMenu = false) { this.title = title; + this.hasSubMenu = hasSubMenu; } - public DynamicMenuItem (ustring title, ustring help, ustring action, bool isTopLevel, bool hasSubMenu, MenuItemCheckStyle checkStyle = MenuItemCheckStyle.NoCheck) + public DynamicMenuItem (ustring title, ustring help, ustring action, bool isTopLevel, bool hasSubMenu, MenuItemCheckStyle checkStyle = MenuItemCheckStyle.NoCheck, ustring shortCut = null) { this.title = title; this.help = help; @@ -57,14 +59,16 @@ namespace UICatalog { this.isTopLevel = isTopLevel; this.hasSubMenu = hasSubMenu; this.checkStyle = checkStyle; + this.shortCut = shortCut; } } - class DynamicMenuBarSample : Window { + public class DynamicMenuBarSample : Window { MenuBar _menuBar; MenuItem _currentMenuBarItem; int _currentSelectedMenuBar; MenuItem _currentEditMenuBarItem; + ListView _lstMenus; public DynamicMenuItemModel DataContext { get; set; } @@ -72,6 +76,22 @@ namespace UICatalog { { DataContext = new DynamicMenuItemModel (); + var _frmDelimiter = new FrameView ("Delimiter") { + X = Pos.Center (), + Y = 3, + Width = 20, + Height = 4 + }; + + var _txtDelimiter = new TextField (MenuBar.ShortCutDelimiter.ToString ()) { + X = Pos.Center(), + Width = 2, + }; + _txtDelimiter.TextChanged += (_) => MenuBar.ShortCutDelimiter = _txtDelimiter.Text; + _frmDelimiter.Add (_txtDelimiter); + + Add (_frmDelimiter); + var _frmMenu = new FrameView ("Menus:") { Y = 7, Width = Dim.Percent (50), @@ -144,7 +164,7 @@ namespace UICatalog { }; _frmMenu.Add (_btnPreviowsParent); - var _lstMenus = new ListView (new List ()) { + _lstMenus = new ListView (new List ()) { ColorScheme = Colors.Dialog, X = Pos.Right (_btnPrevious) + 1, Y = Pos.Top (_btnPrevious) + 2, @@ -178,124 +198,13 @@ namespace UICatalog { Add (_frmMenu); - var _frmMenuDetails = new FrameView ("Menu Details:") { + + var _frmMenuDetails = new DynamicMenuBarDetails ("Menu Details:") { X = Pos.Right (_frmMenu), Y = Pos.Top (_frmMenu), Width = Dim.Fill (), - Height = Dim.Fill () + Height = Dim.Fill (2) }; - - var _lblTitle = new Label ("Title:") { - Y = 1 - }; - _frmMenuDetails.Add (_lblTitle); - - var _txtTitle = new TextField () { - X = Pos.Right (_lblTitle) + 2, - Y = Pos.Top (_lblTitle), - Width = Dim.Fill () - }; - _frmMenuDetails.Add (_txtTitle); - - var _lblHelp = new Label ("Help:") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblTitle) + 1 - }; - _frmMenuDetails.Add (_lblHelp); - - var _txtHelp = new TextField () { - X = Pos.Left (_txtTitle), - Y = Pos.Top (_lblHelp), - Width = Dim.Fill () - }; - _frmMenuDetails.Add (_txtHelp); - - var _lblAction = new Label ("Action:") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblHelp) + 1 - }; - _frmMenuDetails.Add (_lblAction); - - var _txtAction = new TextView () { - ColorScheme = Colors.Dialog, - X = Pos.Left (_txtTitle), - Y = Pos.Top (_lblAction), - Width = Dim.Fill (), - Height = 5 - }; - _frmMenuDetails.Add (_txtAction); - - var _ckbIsTopLevel = new CheckBox ("IsTopLevel") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblAction) + 5 - }; - _frmMenuDetails.Add (_ckbIsTopLevel); - - var _ckbSubMenu = new CheckBox ("Has sub-menus") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_ckbIsTopLevel) - }; - _frmMenuDetails.Add (_ckbSubMenu); - _ckbIsTopLevel.Toggled += (e) => { - if (_ckbIsTopLevel.Checked && _currentEditMenuBarItem.Parent != null) { - MessageBox.ErrorQuery ("Invalid IsTopLevel", "Only menu bar can have top level menu item!", "Ok"); - _ckbIsTopLevel.Checked = false; - return; - } - if (_ckbIsTopLevel.Checked) { - _ckbSubMenu.Checked = false; - _ckbSubMenu.SetNeedsDisplay (); - _txtAction.ReadOnly = false; - } else { - _txtAction.ReadOnly = true; - } - }; - _ckbSubMenu.Toggled += (e) => { - if (_ckbSubMenu.Checked) { - _ckbIsTopLevel.Checked = false; - _ckbIsTopLevel.SetNeedsDisplay (); - _txtAction.ReadOnly = true; - } else { - _txtAction.ReadOnly = false; - } - }; - - var _rChkLabels = new ustring [] { "NoCheck", "Checked", "Radio" }; - var _rbChkStyle = new RadioGroup (_rChkLabels) { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_ckbSubMenu) + 1, - }; - _frmMenuDetails.Add (_rbChkStyle); - - var _btnOk = new Button ("Ok") { - X = Pos.Left (_lblTitle) + 20, - Y = Pos.Bottom (_rbChkStyle) + 1, - }; - _btnOk.Clicked += () => { - if (ustring.IsNullOrEmpty (_txtTitle.Text) && _currentEditMenuBarItem != null) { - MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); - } else if (_currentEditMenuBarItem != null) { - var menuItem = new DynamicMenuItem (_txtTitle.Text, _txtHelp.Text, - _txtAction.Text, - _ckbIsTopLevel != null ? _ckbIsTopLevel.Checked : false, - _ckbSubMenu != null ? _ckbSubMenu.Checked : false, - _rbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck : - _rbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : - MenuItemCheckStyle.Radio); - UpdateMenuItem (_currentEditMenuBarItem, menuItem, _lstMenus.SelectedItem); - } - }; - _frmMenuDetails.Add (_btnOk); - - var _btnCancel = new Button ("Cancel") { - X = Pos.Right (_btnOk) + 3, - Y = Pos.Top (_btnOk), - }; - _btnCancel.Clicked += () => { - _txtTitle.Text = ustring.Empty; - }; - _frmMenuDetails.Add (_btnCancel); - Add (_frmMenuDetails); _btnAdd.Clicked += () => { @@ -305,15 +214,16 @@ namespace UICatalog { return; } - var item = EnterMenuItem (_currentMenuBarItem); - if (ustring.IsNullOrEmpty (item.title)) { + var frameDetails = new DynamicMenuBarDetails (null, _currentMenuBarItem != null); + var item = frameDetails.EnterMenuItem (); + if (item == null) { return; } if (!(_currentMenuBarItem is MenuBarItem)) { var parent = _currentMenuBarItem.Parent as MenuBarItem; var idx = parent.GetChildrenIndex (_currentMenuBarItem); - _currentMenuBarItem = new MenuBarItem (_currentMenuBarItem.Title, new MenuItem [] { new MenuItem ("_New", "", CreateAction (_currentEditMenuBarItem, new DynamicMenuItem ())) }, _currentMenuBarItem.Parent); + _currentMenuBarItem = new MenuBarItem (_currentMenuBarItem.Title, new MenuItem [] { }, _currentMenuBarItem.Parent); _currentMenuBarItem.CheckType = item.checkStyle; parent.Children [idx] = _currentMenuBarItem; } else { @@ -350,14 +260,18 @@ namespace UICatalog { if (childrens.Length == 0) { if (_currentMenuBarItem.Parent == null) { ((MenuBarItem)_currentMenuBarItem).Children = null; - _currentMenuBarItem.Action = CreateAction (_currentEditMenuBarItem, new DynamicMenuItem (_currentMenuBarItem.Title)); + _currentMenuBarItem.Action = _frmMenuDetails.CreateAction (_currentEditMenuBarItem, new DynamicMenuItem (_currentMenuBarItem.Title)); } else { - _currentMenuBarItem = new MenuItem (_currentMenuBarItem.Title, _currentMenuBarItem.Help, CreateAction (_currentEditMenuBarItem, new DynamicMenuItem (_currentEditMenuBarItem.Title)), null, _currentMenuBarItem.Parent); + _currentMenuBarItem = new MenuItem (_currentMenuBarItem.Title, _currentMenuBarItem.Help, _frmMenuDetails.CreateAction (_currentEditMenuBarItem, new DynamicMenuItem (_currentEditMenuBarItem.Title)), null, _currentMenuBarItem.Parent); } } else { ((MenuBarItem)_currentMenuBarItem).Children = childrens; } DataContext.Menus.RemoveAt (_lstMenus.SelectedItem); + if (_lstMenus.Source.Count > 0 && _lstMenus.SelectedItem > _lstMenus.Source.Count - 1) { + _lstMenus.SelectedItem = _lstMenus.Source.Count - 1; + } + _lstMenus.SetNeedsDisplay (); } }; @@ -419,9 +333,105 @@ namespace UICatalog { } }; + _btnPreviowsParent.Clicked += () => { + if (_currentMenuBarItem != null && _currentMenuBarItem.Parent != null) { + var mi = _currentMenuBarItem; + _currentMenuBarItem = _currentMenuBarItem.Parent as MenuBarItem; + SetListViewSource (_currentMenuBarItem, true); + var i = ((MenuBarItem)_currentMenuBarItem).GetChildrenIndex (mi); + if (i > -1) { + _lstMenus.SelectedItem = i; + } + if (_currentMenuBarItem.Parent != null) { + DataContext.Parent = _currentMenuBarItem.Title; + } else { + DataContext.Parent = ustring.Empty; + } + } else { + DataContext.Parent = ustring.Empty; + } + }; + + + var _btnOk = new Button ("Ok") { + X = Pos.Right (_frmMenu) + 20, + Y = Pos.Bottom (_frmMenuDetails), + }; + Add (_btnOk); + + var _btnCancel = new Button ("Cancel") { + X = Pos.Right (_btnOk) + 3, + Y = Pos.Top (_btnOk), + }; + _btnCancel.Clicked += () => { + SetFrameDetails (_currentEditMenuBarItem); + }; + Add (_btnCancel); + + _lstMenus.SelectedItemChanged += (e) => { + var menuBarItem = DataContext.Menus.Count > 0 ? DataContext.Menus [e.Item].MenuItem : null; + SetFrameDetails (menuBarItem); + }; + + _btnOk.Clicked += () => { + if (ustring.IsNullOrEmpty (_frmMenuDetails._txtTitle.Text) && _currentEditMenuBarItem != null) { + MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); + } else if (_currentEditMenuBarItem != null) { + var menuItem = new DynamicMenuItem (_frmMenuDetails._txtTitle.Text, _frmMenuDetails._txtHelp.Text, + _frmMenuDetails._txtAction.Text, + _frmMenuDetails._ckbIsTopLevel != null ? _frmMenuDetails._ckbIsTopLevel.Checked : false, + _frmMenuDetails._ckbSubMenu != null ? _frmMenuDetails._ckbSubMenu.Checked : false, + _frmMenuDetails._rbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck : + _frmMenuDetails._rbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : + MenuItemCheckStyle.Radio, + _frmMenuDetails._txtShortCut.Text); + UpdateMenuItem (_currentEditMenuBarItem, menuItem, _lstMenus.SelectedItem); + } + }; + + _lstMenus.OpenSelectedItem += (e) => { + _currentMenuBarItem = DataContext.Menus [e.Item].MenuItem; + if (!(_currentMenuBarItem is MenuBarItem)) { + MessageBox.ErrorQuery ("Menu Open Error", "Must allows sub menus first!", "Ok"); + return; + } + DataContext.Parent = _currentMenuBarItem.Title; + DataContext.Menus = new List (); + SetListViewSource (_currentMenuBarItem, true); + var menuBarItem = DataContext.Menus.Count > 0 ? DataContext.Menus [0].MenuItem : null; + SetFrameDetails (menuBarItem); + }; + + _lstMenus.Enter += (_) => { + var menuBarItem = DataContext.Menus.Count > 0 ? DataContext.Menus [_lstMenus.SelectedItem].MenuItem : null; + SetFrameDetails (menuBarItem); + }; + + _btnNext.Clicked += () => { + if (_menuBar != null && _currentSelectedMenuBar + 1 < _menuBar.Menus.Length) { + _currentSelectedMenuBar++; + } + SelectCurrentMenuBarItem (); + }; + + _btnPrevious.Clicked += () => { + if (_currentSelectedMenuBar - 1 > -1) { + _currentSelectedMenuBar--; + } + SelectCurrentMenuBarItem (); + }; + + _lblMenuBar.Enter += (e) => { + if (_menuBar?.Menus != null) { + _currentMenuBarItem = _menuBar.Menus [_currentSelectedMenuBar]; + SetFrameDetails (_menuBar.Menus [_currentSelectedMenuBar]); + } + }; + _btnAddMenuBar.Clicked += () => { - var item = EnterMenuItem (null); - if (ustring.IsNullOrEmpty (item.title)) { + var frameDetails = new DynamicMenuBarDetails (null, false); + var item = frameDetails.EnterMenuItem (); + if (item == null) { return; } @@ -441,11 +451,15 @@ namespace UICatalog { _menuBar.Menus [_currentSelectedMenuBar] = newMenu; _lblMenuBar.Text = newMenu.Title; SetListViewSource (_currentMenuBarItem, true); - EditMenuBarItem (_menuBar.Menus [_currentSelectedMenuBar]); + SetFrameDetails (_menuBar.Menus [_currentSelectedMenuBar]); _menuBar.SetNeedsDisplay (); }; _btnRemoveMenuBar.Clicked += () => { + if (_menuBar == null || _menuBar.Menus.Length == 0) { + return; + } + if (_menuBar != null && _menuBar.Menus.Length > 0) { _menuBar.Menus [_currentSelectedMenuBar] = null; int i = 0; @@ -474,62 +488,12 @@ namespace UICatalog { _lblMenuBar.Text = _menuBar.Menus [_currentSelectedMenuBar].Title; } SetListViewSource (_currentMenuBarItem, true); - EditMenuBarItem (null); + SetFrameDetails (null); }; - _lblMenuBar.Enter += (e) => { - if (_menuBar?.Menus != null) { - _currentMenuBarItem = _menuBar.Menus [_currentSelectedMenuBar]; - EditMenuBarItem (_menuBar.Menus [_currentSelectedMenuBar]); - } - }; - _btnPrevious.Clicked += () => { - if (_currentSelectedMenuBar - 1 > -1) { - _currentSelectedMenuBar--; - } - SelectCurrentMenuBarItem (); - }; + SetFrameDetails (); - _btnNext.Clicked += () => { - if (_menuBar != null && _currentSelectedMenuBar + 1 < _menuBar.Menus.Length) { - _currentSelectedMenuBar++; - } - SelectCurrentMenuBarItem (); - }; - - _lstMenus.SelectedItemChanged += (e) => { - var menuBarItem = DataContext.Menus.Count > 0 ? DataContext.Menus [e.Item].MenuItem : null; - EditMenuBarItem (menuBarItem); - }; - - _lstMenus.OpenSelectedItem += (e) => { - _currentMenuBarItem = DataContext.Menus [e.Item].MenuItem; - DataContext.Parent = _currentMenuBarItem.Title; - DataContext.Menus = new List (); - SetListViewSource (_currentMenuBarItem, true); - var menuBarItem = DataContext.Menus.Count > 0 ? DataContext.Menus [0].MenuItem : null; - EditMenuBarItem (menuBarItem); - }; - - _btnPreviowsParent.Clicked += () => { - if (_currentMenuBarItem != null && _currentMenuBarItem.Parent != null) { - var mi = _currentMenuBarItem; - _currentMenuBarItem = _currentMenuBarItem.Parent as MenuBarItem; - SetListViewSource (_currentMenuBarItem, true); - var i = ((MenuBarItem)_currentMenuBarItem).GetChildrenIndex (mi); - if (i > -1) { - _lstMenus.SelectedItem = i; - } - if (_currentMenuBarItem.Parent != null) { - DataContext.Parent = _currentMenuBarItem.Title; - } else { - DataContext.Parent = ustring.Empty; - } - } else { - DataContext.Parent = ustring.Empty; - } - }; var ustringConverter = new UStringValueConverter (); var listWrapperConverter = new ListWrapperConverter (); @@ -539,45 +503,29 @@ namespace UICatalog { var lstMenus = new Binding (this, "Menus", _lstMenus, "Source", listWrapperConverter); - ustring GetTargetAction (Action action) + void SetFrameDetails (MenuItem menuItem = null) { - var me = action.Target; - - if (me == null) { - throw new ArgumentException (); + _currentEditMenuBarItem = menuItem; + _frmMenuDetails.EditMenuBarItem (menuItem); + var f = _btnOk.CanFocus == _frmMenuDetails.CanFocus; + if (!f) { + _btnOk.CanFocus = _frmMenuDetails.CanFocus; + _btnCancel.CanFocus = _frmMenuDetails.CanFocus; } - object v = new object (); - foreach (var field in me.GetType ().GetFields ()) { - if (field.Name == "item") { - v = field.GetValue (me); - } - } - return v == null || !(v is DynamicMenuItem item) ? ustring.Empty : item.action; } - Action CreateAction (MenuItem menuItem, DynamicMenuItem item) + void SelectCurrentMenuBarItem () { - switch (menuItem.CheckType) { - case MenuItemCheckStyle.NoCheck: - return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok")); - case MenuItemCheckStyle.Checked: - return new Action (() => menuItem.Checked = !menuItem.Checked); - case MenuItemCheckStyle.Radio: - break; + MenuBarItem menuBarItem = null; + if (_menuBar?.Menus != null) { + menuBarItem = _menuBar.Menus [_currentSelectedMenuBar]; + _lblMenuBar.Text = menuBarItem.Title; } - return new Action (() => { - menuItem.Checked = true; - var parent = menuItem?.Parent as MenuBarItem; - if (parent != null) { - var childrens = parent.Children; - for (int i = 0; i < childrens.Length; i++) { - var child = childrens [i]; - if (child != menuItem) { - child.Checked = false; - } - } - } - }); + SetFrameDetails (menuBarItem); + _currentMenuBarItem = menuBarItem; + DataContext.Menus = new List (); + SetListViewSource (_currentMenuBarItem, true); + _lblParent.Text = ustring.Empty; } void SetListViewSource (MenuItem _currentMenuBarItem, bool fill = false) @@ -598,20 +546,24 @@ namespace UICatalog { } } - void EditMenuBarItem (MenuItem menuBarItem) + MenuItem CreateNewMenu (DynamicMenuItem item, MenuItem parent = null) { - if (menuBarItem == null) { - _frmMenuDetails.CanFocus = false; + MenuItem newMenu; + if (item.hasSubMenu) { + newMenu = new MenuBarItem (item.title, new MenuItem [] { }, parent); + } else if (parent != null) { + newMenu = new MenuItem (item.title, item.help, null, null, parent); + newMenu.CheckType = item.checkStyle; + newMenu.Action = _frmMenuDetails.CreateAction (newMenu, item); + } else if (item.isTopLevel) { + newMenu = new MenuBarItem (item.title, item.help, null); + newMenu.Action = _frmMenuDetails.CreateAction (newMenu, item); } else { - _frmMenuDetails.CanFocus = true; + newMenu = new MenuBarItem (item.title, item.help, null); + ((MenuBarItem)newMenu).Children [0].Action = _frmMenuDetails.CreateAction (newMenu, item); } - _currentEditMenuBarItem = menuBarItem; - _txtTitle.Text = menuBarItem?.Title ?? ""; - _txtHelp.Text = menuBarItem?.Help ?? ""; - _txtAction.Text = menuBarItem != null && menuBarItem.Action != null ? GetTargetAction (menuBarItem.Action) : ustring.Empty; - _ckbIsTopLevel.Checked = IsTopLevel (menuBarItem); - _ckbSubMenu.Checked = HasSubMenus (menuBarItem); - _rbChkStyle.SelectedItem = (int)(menuBarItem?.CheckType ?? MenuItemCheckStyle.NoCheck); + + return newMenu; } void UpdateMenuItem (MenuItem _currentEditMenuBarItem, DynamicMenuItem menuItem, int index) @@ -625,199 +577,406 @@ namespace UICatalog { } if (menuItem.isTopLevel && _currentEditMenuBarItem is MenuBarItem) { ((MenuBarItem)_currentEditMenuBarItem).Children = null; - _currentEditMenuBarItem.Action = CreateAction (_currentEditMenuBarItem, menuItem); + _currentEditMenuBarItem.Action = _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem); SetListViewSource (_currentEditMenuBarItem, true); } else if (menuItem.hasSubMenu) { _currentEditMenuBarItem.Action = null; if (_currentEditMenuBarItem is MenuBarItem && ((MenuBarItem)_currentEditMenuBarItem).Children == null) { - ((MenuBarItem)_currentEditMenuBarItem).Children = new MenuItem [] { new MenuItem ("_New", "", CreateAction (_currentEditMenuBarItem, new DynamicMenuItem ())) }; + ((MenuBarItem)_currentEditMenuBarItem).Children = new MenuItem [] { }; } else if (_currentEditMenuBarItem.Parent != null) { - UpdateParent (ref _currentEditMenuBarItem); + _frmMenuDetails.UpdateParent (ref _currentEditMenuBarItem); } else { - _currentEditMenuBarItem = new MenuBarItem (_currentEditMenuBarItem.Title, new MenuItem [] { new MenuItem ("_New", "", CreateAction (_currentEditMenuBarItem, new DynamicMenuItem ())) }, _currentEditMenuBarItem.Parent); + _currentEditMenuBarItem = new MenuBarItem (_currentEditMenuBarItem.Title, new MenuItem [] { }, _currentEditMenuBarItem.Parent); } SetListViewSource (_currentEditMenuBarItem, true); } else if (_currentEditMenuBarItem is MenuBarItem && _currentEditMenuBarItem.Parent != null) { - UpdateParent (ref _currentEditMenuBarItem); - _currentEditMenuBarItem = new MenuItem (menuItem.title, menuItem.help, CreateAction (_currentEditMenuBarItem, menuItem), null, _currentEditMenuBarItem.Parent); + _frmMenuDetails.UpdateParent (ref _currentEditMenuBarItem); + _currentEditMenuBarItem = new MenuItem (menuItem.title, menuItem.help, _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem), null, _currentEditMenuBarItem.Parent); } else { if (_currentEditMenuBarItem is MenuBarItem) { ((MenuBarItem)_currentEditMenuBarItem).Children = null; DataContext.Menus = new List (); } - _currentEditMenuBarItem.Action = CreateAction (_currentEditMenuBarItem, menuItem); + _currentEditMenuBarItem.Action = _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem); + _currentEditMenuBarItem.ShortCut = _currentEditMenuBarItem.CreateShortCutFromTag (menuItem.shortCut); } if (_currentEditMenuBarItem.Parent == null) { DataContext.MenuBar = _currentEditMenuBarItem.Title; } else { + if (DataContext.Menus.Count == 0) { + DataContext.Menus.Add (new DynamicMenuItemList (_currentEditMenuBarItem.Title, _currentEditMenuBarItem)); + } DataContext.Menus [index] = new DynamicMenuItemList (_currentEditMenuBarItem.Title, _currentEditMenuBarItem); } _currentEditMenuBarItem.CheckType = menuItem.checkStyle; - EditMenuBarItem (_currentEditMenuBarItem); + SetFrameDetails (_currentEditMenuBarItem); } - void UpdateParent (ref MenuItem menuItem) - { - var parent = menuItem.Parent as MenuBarItem; - var idx = parent.GetChildrenIndex (menuItem); - if (!(menuItem is MenuBarItem)) { - menuItem = new MenuBarItem (menuItem.Title, new MenuItem [] { new MenuItem ("_New", "", CreateAction (menuItem, new DynamicMenuItem ())) }, menuItem.Parent); - if (idx > -1) { - parent.Children [idx] = menuItem; - } + + _frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.CanFocus = false; + } + } + + public class DynamicMenuBarDetails : FrameView { + public MenuItem _menuItem; + public TextField _txtTitle; + public TextField _txtHelp; + public TextView _txtAction; + public CheckBox _ckbIsTopLevel; + public CheckBox _ckbSubMenu; + public RadioGroup _rbChkStyle; + public TextField _txtShortCut; + + bool hasParent; + + public DynamicMenuBarDetails (MenuItem menuItem = null, bool hasParent = false) : this (menuItem == null ? "Adding New Menu." : "Editing Menu.") + { + _menuItem = menuItem; + this.hasParent = hasParent; + } + + public DynamicMenuBarDetails (ustring title) : base (title) + { + var _lblTitle = new Label ("Title:") { + Y = 1 + }; + Add (_lblTitle); + + _txtTitle = new TextField () { + X = Pos.Right (_lblTitle) + 2, + Y = Pos.Top (_lblTitle), + Width = Dim.Fill () + }; + Add (_txtTitle); + + var _lblHelp = new Label ("Help:") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_lblTitle) + 1 + }; + Add (_lblHelp); + + _txtHelp = new TextField () { + X = Pos.Left (_txtTitle), + Y = Pos.Top (_lblHelp), + Width = Dim.Fill () + }; + Add (_txtHelp); + + var _lblAction = new Label ("Action:") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_lblHelp) + 1 + }; + Add (_lblAction); + + _txtAction = new TextView () { + ColorScheme = Colors.Dialog, + X = Pos.Left (_txtTitle), + Y = Pos.Top (_lblAction), + Width = Dim.Fill (), + Height = 5 + }; + Add (_txtAction); + + _ckbIsTopLevel = new CheckBox ("IsTopLevel") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_lblAction) + 5 + }; + Add (_ckbIsTopLevel); + + _ckbSubMenu = new CheckBox ("Has sub-menus") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_ckbIsTopLevel), + Checked = _menuItem == null ? !hasParent : HasSubMenus (_menuItem) + }; + Add (_ckbSubMenu); + + var _rChkLabels = new ustring [] { "NoCheck", "Checked", "Radio" }; + _rbChkStyle = new RadioGroup (_rChkLabels) { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_ckbSubMenu) + 1, + }; + Add (_rbChkStyle); + + var _lblShortCut = new Label ("ShortCut:") { + X = Pos.Right (_ckbSubMenu) + 10, + Y = Pos.Top (_ckbSubMenu) + }; + Add (_lblShortCut); + + _txtShortCut = new TextField () { + X = Pos.X (_lblShortCut), + Y = Pos.Bottom (_lblShortCut), + Width = Dim.Fill (), + ReadOnly = true + }; + _txtShortCut.KeyDown += (e) => { + var k = GetModifiersKey (e.KeyEvent); + if (_menuItem == null || ((k & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) == 0 && !CheckFlagRange (k, Key.F1, Key.F12))) { + _txtShortCut.Text = ""; + return; + } + + var s = _menuItem.GetShortCutTag (k); + if (s.Contains ("Unknow")) { + _txtShortCut.Text = ""; } else { - menuItem = new MenuItem (menuItem.Title, menuItem.Help, CreateAction (menuItem, new DynamicMenuItem ()), null, menuItem.Parent); - if (idx > -1) { - parent.Children [idx] = menuItem; + _txtShortCut.Text = s; + } + e.Handled = true; + }; + + bool CheckFlagRange (Key key, Key first, Key last) + { + for (uint i = (uint)first; i < (uint)last; i++) { + if ((key | (Key)i) == key) { + return true; } } + return false; } - bool IsTopLevel (MenuItem menuItem) - { - var topLevel = menuItem as MenuBarItem; - if (topLevel != null && topLevel.Parent == null && (topLevel.Children == null || topLevel.Children.Length == 0)) { - return true; + _txtShortCut.KeyUp += (e) => { + var k = GetModifiersKey (e.KeyEvent); + if (_menuItem == null || (k & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) == 0 || ((k | Key.D0) == 0 && !CheckFlagRange (k, Key.F1, Key.F12))) { + return; + } + + e.Handled = true; + var kVal = (Key)e.KeyEvent.KeyValue; + if ((kVal & Key.D0) != 0) { + + } else if ((kVal == Key.CtrlMask || kVal == Key.ShiftMask || kVal == Key.AltMask || + (kVal & (Key.CtrlMask | Key.ShiftMask)) != 0 || (kVal & (Key.CtrlMask | Key.AltMask)) != 0 || (kVal & (Key.ShiftMask | Key.AltMask)) != 0 || + (kVal & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) != 0) && !kVal.ToString ().Contains ("Control")) { + return; + } + var s = _menuItem.GetShortCutTag (k); + if (s.Contains ("Unknow")) { + _txtShortCut.Text = ""; } else { - return false; + _txtShortCut.Text = s; } - } + }; + Add (_txtShortCut); - bool HasSubMenus (MenuItem menuItem) - { - var menuBarItem = menuItem as MenuBarItem; - if (menuBarItem != null && menuBarItem.Children != null && menuBarItem.Children.Length > 0) { - return true; + var _btnShortCut = new Button ("Clear ShirtCut") { + X = Pos.X (_lblShortCut), + Y = Pos.Bottom (_txtShortCut) + 1 + }; + _btnShortCut.Clicked += () => { + _txtShortCut.Text = ""; + }; + Add (_btnShortCut); + + _ckbIsTopLevel.Toggled += (e) => { + if ((_menuItem != null && _menuItem.Parent != null && _ckbIsTopLevel.Checked) || + _menuItem == null && hasParent && _ckbIsTopLevel.Checked) { + MessageBox.ErrorQuery ("Invalid IsTopLevel", "Only menu bar can have top level menu item!", "Ok"); + _ckbIsTopLevel.Checked = false; + return; + } + if (_ckbIsTopLevel.Checked) { + _ckbSubMenu.Checked = false; + _ckbSubMenu.SetNeedsDisplay (); + _txtHelp.CanFocus = true; + _txtAction.CanFocus = true; + _txtShortCut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; } else { - return false; - } - } - - void SelectCurrentMenuBarItem () - { - MenuBarItem menuBarItem = null; - if (_menuBar?.Menus != null) { - menuBarItem = _menuBar.Menus [_currentSelectedMenuBar]; - _lblMenuBar.Text = menuBarItem.Title; - } - EditMenuBarItem (menuBarItem); - _currentMenuBarItem = menuBarItem; - DataContext.Menus = new List (); - SetListViewSource (_currentMenuBarItem, true); - _lblParent.Text = ustring.Empty; - } - - DynamicMenuItem EnterMenuItem (MenuItem menuItem) - { - var _lblTitle = new Label (1, 3, "Title:"); - var _txtTitle = new TextField ("_New") { - X = Pos.Right (_lblTitle) + 2, - Y = Pos.Top (_lblTitle), - Width = Dim.Fill (), - }; - var _lblHelp = new Label ("Help:") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblTitle) + 1 - }; - var _txtHelp = new TextField () { - X = Pos.Left (_txtTitle), - Y = Pos.Top (_lblHelp), - Width = Dim.Fill (), - }; - var _lblAction = new Label ("Action:") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblHelp) + 1 - }; - var _txtAction = new TextView () { - ColorScheme = Colors.Menu, - X = Pos.Left (_txtTitle), - Y = Pos.Top (_lblAction), - Width = Dim.Fill (), - Height = 5, - ReadOnly = true - }; - var _ckbIsTopLevel = new CheckBox ("IsTopLevel") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_lblAction) + 5 - }; - var _ckbSubMenu = new CheckBox ("Has sub-menus") { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_ckbIsTopLevel), - Checked = menuItem == null - }; - _ckbIsTopLevel.Toggled += (e) => { - if (_ckbIsTopLevel.Checked && menuItem != null) { - MessageBox.ErrorQuery ("Invalid IsTopLevel", "Only menu bar can have top level menu item!", "Ok"); - _ckbIsTopLevel.Checked = false; - return; - } - if (_ckbIsTopLevel.Checked) { - _ckbSubMenu.Checked = false; + if (_menuItem == null && !hasParent || _menuItem.Parent == null) { + _ckbSubMenu.Checked = true; _ckbSubMenu.SetNeedsDisplay (); - _txtAction.ReadOnly = false; - } else { - _txtAction.ReadOnly = true; + _txtShortCut.CanFocus = false; } - }; - _ckbSubMenu.Toggled += (e) => { - if (_ckbSubMenu.Checked) { - _ckbIsTopLevel.Checked = false; + _txtHelp.Text = ""; + _txtHelp.CanFocus = false; + _txtAction.Text = ""; + _txtAction.CanFocus = false; + } + }; + _ckbSubMenu.Toggled += (e) => { + if (_ckbSubMenu.Checked) { + _ckbIsTopLevel.Checked = false; + _ckbIsTopLevel.SetNeedsDisplay (); + _txtHelp.Text = ""; + _txtHelp.CanFocus = false; + _txtAction.Text = ""; + _txtAction.CanFocus = false; + _txtShortCut.CanFocus = false; + } else { + if (!hasParent) { + _ckbIsTopLevel.Checked = true; _ckbIsTopLevel.SetNeedsDisplay (); - _txtAction.ReadOnly = true; - } else { - _txtAction.ReadOnly = false; + _txtShortCut.CanFocus = false; } - }; - var _rChkLabels = new ustring [] { "NoCheck", "Checked", "Radio" }; - var _rbChkStyle = new RadioGroup (_rChkLabels) { - X = Pos.Left (_lblTitle), - Y = Pos.Bottom (_ckbSubMenu) + 1, - }; - var _btnOk = new Button ("Ok") { - IsDefault = true, - }; - _btnOk.Clicked += () => { - if (ustring.IsNullOrEmpty (_txtTitle.Text)) { - MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); - } else { - Application.RequestStop (); - } - }; - var _btnCancel = new Button ("Cancel"); - _btnCancel.Clicked += () => { - _txtTitle.Text = ustring.Empty; - Application.RequestStop (); - }; - var _dialog = new Dialog ("Please enter the menu details.", _btnOk, _btnCancel); - _dialog.Add (_lblTitle, _txtTitle, _lblHelp, _txtHelp, _lblAction, _txtAction, _ckbIsTopLevel, _ckbSubMenu, _rbChkStyle); - _txtTitle.SetFocus (); - Application.Run (_dialog); + _txtHelp.CanFocus = true; + _txtAction.CanFocus = true; + _txtShortCut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; + } + }; - return new DynamicMenuItem (_txtTitle.Text, _txtHelp.Text, _txtAction.Text, _ckbIsTopLevel != null ? _ckbIsTopLevel.Checked : false, _ckbSubMenu != null ? _ckbSubMenu.Checked : false, _rbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck : _rbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : MenuItemCheckStyle.Radio); + //Add (_frmMenuDetails); + + } + + + public DynamicMenuItem EnterMenuItem () + { + var valid = false; + + if (_menuItem == null) { + var m = new DynamicMenuItem (); + _txtTitle.Text = m.title; + _txtHelp.Text = m.help; + _txtAction.Text = m.action; + _ckbIsTopLevel.Checked = false; + _ckbSubMenu.Checked = !hasParent; + _txtHelp.CanFocus = hasParent; + _txtAction.CanFocus = hasParent; + _txtShortCut.CanFocus = hasParent; + } else { + EditMenuBarItem (_menuItem); } - MenuItem CreateNewMenu (DynamicMenuItem item, MenuItem parent = null) - { - MenuItem newMenu; - if (item.hasSubMenu) { - newMenu = new MenuBarItem (item.title, new MenuItem [] { new MenuItem ("_New", "", null) }, parent); - ((MenuBarItem)newMenu).Children [0].Action = CreateAction (newMenu, new DynamicMenuItem ()); - } else if (parent != null) { - newMenu = new MenuItem (item.title, item.help, null, null, parent); - newMenu.CheckType = item.checkStyle; - newMenu.Action = CreateAction (newMenu, item); + var _btnOk = new Button ("Ok") { + IsDefault = true, + }; + _btnOk.Clicked += () => { + if (ustring.IsNullOrEmpty (_txtTitle.Text)) { + MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); } else { - newMenu = new MenuBarItem (item.title, item.help, null); - ((MenuBarItem)newMenu).Children [0].Action = CreateAction (newMenu, item); + valid = true; + Application.RequestStop (); } + }; + var _btnCancel = new Button ("Cancel"); + _btnCancel.Clicked += () => { + _txtTitle.Text = ustring.Empty; + Application.RequestStop (); + }; + var _dialog = new Dialog ("Please enter the menu details.", _btnOk, _btnCancel); - return newMenu; + Width = Dim.Fill (); + Height = Dim.Fill () - 1; + _dialog.Add (this); + _txtTitle.SetFocus (); + Application.Run (_dialog); + + if (valid) { + return new DynamicMenuItem (_txtTitle.Text, _txtHelp.Text, _txtAction.Text, + _ckbIsTopLevel != null ? _ckbIsTopLevel.Checked : false, + _ckbSubMenu != null ? _ckbSubMenu.Checked : false, + _rbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck : + _rbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : MenuItemCheckStyle.Radio, + _txtShortCut.Text); + } else { + return null; + } + } + + public void EditMenuBarItem (MenuItem menuItem) + { + if (menuItem == null) { + hasParent = false; + CanFocus = false; + return; + } else { + hasParent = menuItem.Parent != null; + CanFocus = true; + } + _menuItem = menuItem; + _txtTitle.Text = menuItem?.Title ?? ""; + _txtHelp.Text = menuItem?.Help ?? ""; + _txtAction.Text = menuItem != null && menuItem.Action != null ? GetTargetAction (menuItem.Action) : ustring.Empty; + _ckbIsTopLevel.Checked = IsTopLevel (menuItem); + _ckbSubMenu.Checked = HasSubMenus (menuItem); + _txtHelp.CanFocus = !_ckbSubMenu.Checked; + _txtAction.CanFocus = !_ckbSubMenu.Checked; + _rbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck); + _txtShortCut.Text = menuItem?.ShortCutTag ?? ""; + _txtShortCut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; + } + + ustring GetTargetAction (Action action) + { + var me = action.Target; + + if (me == null) { + throw new ArgumentException (); + } + object v = new object (); + foreach (var field in me.GetType ().GetFields ()) { + if (field.Name == "item") { + v = field.GetValue (me); + } + } + return v == null || !(v is DynamicMenuItem item) ? ustring.Empty : item.action; + } + + bool IsTopLevel (MenuItem menuItem) + { + var topLevel = menuItem as MenuBarItem; + if (topLevel != null && topLevel.Parent == null && (topLevel.Children == null || topLevel.Children.Length == 0) && topLevel.Action != null) { + return true; + } else { + return false; + } + } + + bool HasSubMenus (MenuItem menuItem) + { + var menuBarItem = menuItem as MenuBarItem; + if (menuBarItem != null && menuBarItem.Children != null && (menuBarItem.Children.Length > 0 || menuBarItem.Action == null)) { + return true; + } else { + return false; + } + } + + public Action CreateAction (MenuItem menuItem, DynamicMenuItem item) + { + switch (menuItem.CheckType) { + case MenuItemCheckStyle.NoCheck: + return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok")); + case MenuItemCheckStyle.Checked: + return new Action (() => menuItem.Checked = !menuItem.Checked); + case MenuItemCheckStyle.Radio: + break; + } + return new Action (() => { + menuItem.Checked = true; + var parent = menuItem?.Parent as MenuBarItem; + if (parent != null) { + var childrens = parent.Children; + for (int i = 0; i < childrens.Length; i++) { + var child = childrens [i]; + if (child != menuItem) { + child.Checked = false; + } + } + } + }); + } + + public void UpdateParent (ref MenuItem menuItem) + { + var parent = menuItem.Parent as MenuBarItem; + var idx = parent.GetChildrenIndex (menuItem); + if (!(menuItem is MenuBarItem)) { + menuItem = new MenuBarItem (menuItem.Title, new MenuItem [] { }, menuItem.Parent); + if (idx > -1) { + parent.Children [idx] = menuItem; + } + } else { + menuItem = new MenuItem (menuItem.Title, menuItem.Help, CreateAction (menuItem, new DynamicMenuItem ()), null, menuItem.Parent); + if (idx > -1) { + parent.Children [idx] = menuItem; + } } } } - class DynamicMenuItemModel : INotifyPropertyChanged { + public class DynamicMenuItemModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private ustring menuBar; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ff61af42f..2fcac5be2 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using Terminal.Gui; using Rune = System.Rune; @@ -156,11 +157,15 @@ namespace UICatalog { _menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Quit", "", () => Application.RequestStop() ) + new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.ControlQ) }), new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), - new MenuBarItem ("_Diagostics", CreateDiagnosticMenuItems()), - new MenuBarItem ("_About...", "About this app", () => MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok")), + new MenuBarItem ("Diag_ostics", CreateDiagnosticMenuItems()), + new MenuBarItem ("_Help", new MenuItem [] { + new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://migueldeicaza.github.io/gui.cs/articles/overview.html"), null, null, Key.F1), + new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/migueldeicaza/gui.cs"), null, null, Key.F2), + new MenuItem ("_About...", "About this app", () => MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.ControlA), + }) }); _leftPane = new FrameView ("Categories") { @@ -261,10 +266,14 @@ namespace UICatalog { static MenuItem [] CreateDiagnosticMenuItems () { + var index = 0; + MenuItem CheckedMenuMenuItem (ustring menuItem, Action action, Func checkFunction) { var mi = new MenuItem (); mi.Title = menuItem; + mi.ShortCut = Key.AltMask + index.ToString () [0]; + index++; mi.CheckType |= MenuItemCheckStyle.Checked; mi.Checked = checkFunction (); mi.Action = () => { @@ -309,14 +318,15 @@ namespace UICatalog { List menuItems = new List (); foreach (var sc in Colors.ColorSchemes) { var item = new MenuItem (); - item.Title = sc.Key; + item.Title = $"_{sc.Key}"; + item.ShortCut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0]; item.CheckType |= MenuItemCheckStyle.Radio; item.Checked = sc.Value == _baseColorScheme; item.Action += () => { _baseColorScheme = sc.Value; SetColorScheme (); foreach (var menuItem in menuItems) { - menuItem.Checked = menuItem.Title.Equals (sc.Key) && sc.Value == _baseColorScheme; + menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _baseColorScheme; } }; menuItems.Add (item); @@ -440,5 +450,24 @@ namespace UICatalog { _scenarioListView.SelectedItem = _scenarioListViewItem; } + + private static void OpenUrl (string url) + { + try { + Process.Start (url); + } catch { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { + url = url.Replace ("&", "^&"); + Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true }); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { + Process.Start ("xdg-open", url); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Process.Start ("open", url); + } else { + throw; + } + } + } } }