diff --git a/Example/demo.cs b/Example/demo.cs
index 3cc63d77c..c44b23ae7 100644
--- a/Example/demo.cs
+++ b/Example/demo.cs
@@ -93,10 +93,10 @@ static class Demo {
int i = 0;
string txt = "Hello world, how are you doing today";
container.Add (
- new Label(new Rect(0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
- new Label(new Rect(0, 3, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
- new Label(new Rect(0, 5, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
- new Label(new Rect(0, 7, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
+ new Label (new Rect (0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
+ new Label (new Rect (0, 3, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
+ new Label (new Rect (0, 5, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
+ new Label (new Rect (0, 7, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
);
Application.Run (container);
@@ -408,6 +408,34 @@ static class Demo {
#endregion
+ #region OnKeyDown / OnKeyUp Demo
+ private static void OnKeyDownUpDemo ()
+ {
+ var container = new Dialog (
+ "OnKeyDown & OnKeyUp demo", 50, 20,
+ new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
+ new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
+
+ var kl = new Label (new Rect (3, 3, 40, 1), "Keyboard: ");
+ container.OnKeyDown += (KeyEvent keyEvent) => KeyUpDown (keyEvent, kl, "Down");
+ container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, kl, "Up");
+ container.Add (kl);
+ Application.Run (container);
+ }
+
+ private static void KeyUpDown (KeyEvent keyEvent, Label kl, string updown)
+ {
+ kl.TextColor = Colors.TopLevel.Normal;
+ if ((keyEvent.Key & Key.CtrlMask) != 0) {
+ kl.Text = $"Keyboard: Ctrl Key{updown}";
+ } else if ((keyEvent.Key & Key.AltMask) != 0) {
+ kl.Text = $"Keyboard: Alt Key{updown}";
+ } else {
+ kl.Text = $"Keyboard: {(char)keyEvent.KeyValue} Key{updown}";
+ }
+ }
+#endregion
+
public static Label ml;
public static MenuBar menu;
public static CheckBox menuKeysStyle;
@@ -430,7 +458,7 @@ static class Demo {
X = 1,
Y = 1,
Width = Dim.Fill (),
- Height = Dim.Fill ()-1
+ Height = Dim.Fill () - 1
};
#else
var tframe = top.Frame;
@@ -451,7 +479,7 @@ static class Demo {
menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
- new MenuItem ("Text Editor Demo", "", () => { Editor (top); }),
+ new MenuItem ("Text _Editor Demo", "", () => { Editor (top); }),
new MenuItem ("_New", "Creates new file", NewFile),
new MenuItem ("_Open", "", Open),
new MenuItem ("_Hex", "", () => ShowHex (top)),
@@ -469,18 +497,19 @@ static class Demo {
menuItems[3]
}),
new MenuBarItem ("_List Demos", new MenuItem [] {
- new MenuItem ("Select Multiple Items", "", () => ListSelectionDemo (true)),
- new MenuItem ("Select Single Item", "", () => ListSelectionDemo (false)),
+ new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)),
+ new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)),
}),
- new MenuBarItem ("Assorted", new MenuItem [] {
- new MenuItem ("Show text alignments", "", () => ShowTextAlignments ())
+ new MenuBarItem ("A_ssorted", new MenuItem [] {
+ new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()),
+ new MenuItem ("_OnKeyDown/Up", "", () => OnKeyDownUpDemo ())
}),
- new MenuBarItem ("Test Menu and SubMenus", new MenuItem [] {
- new MenuItem ("SubMenu1Item1",
+ new MenuBarItem ("_Test Menu and SubMenus", new MenuItem [] {
+ new MenuItem ("SubMenu1Item_1",
new MenuBarItem (new MenuItem[] {
- new MenuItem ("SubMenu2Item1",
+ new MenuItem ("SubMenu2Item_1",
new MenuBarItem (new MenuItem [] {
- new MenuItem ("SubMenu3Item1",
+ new MenuItem ("SubMenu3Item_1",
new MenuBarItem (new MenuItem [] { menuItems [2] })
)
})
@@ -488,6 +517,7 @@ static class Demo {
})
)
}),
+ new MenuBarItem ("_About...", "Demonstrates top-level menu item", () => MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok")),
});
menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true);
@@ -535,6 +565,7 @@ static class Demo {
};
#endif
+
top.Add (win);
//top.Add (menu);
top.Add (menu, statusBar, ml);
diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index 2546abd65..c93af8908 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -121,6 +121,27 @@ namespace Terminal.Gui {
return false;
}
+ ///
+ /// Method invoked when a key is pressed.
+ ///
+ /// Contains the details about the key that produced the event.
+ /// true if the event was handled
+ public virtual bool KeyDown (KeyEvent keyEvent)
+ {
+ return false;
+ }
+
+ ///
+ /// Method invoked when a key is released.
+ ///
+ /// Contains the details about the key that produced the event.
+ /// true if the event was handled
+ public virtual bool KeyUp (KeyEvent keyEvent)
+ {
+ return false;
+ }
+
+
///
/// Method invoked when a mouse event is generated
///
@@ -1011,6 +1032,41 @@ namespace Terminal.Gui {
return false;
}
+ ///
+ /// Invoked when a key is pressed
+ ///
+ public Action OnKeyDown;
+
+ /// Contains the details about the key that produced the event.
+ public override bool KeyDown (KeyEvent keyEvent)
+ {
+ OnKeyDown?.Invoke (keyEvent);
+ if (subviews == null || subviews.Count == 0)
+ return false;
+ foreach (var view in subviews)
+ if (view.KeyDown (keyEvent))
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Invoked when a key is released
+ ///
+ public Action OnKeyUp;
+
+ /// Contains the details about the key that produced the event.
+ public override bool KeyUp (KeyEvent keyEvent)
+ {
+ OnKeyUp?.Invoke (keyEvent);
+ if (subviews == null || subviews.Count == 0)
+ return false;
+ foreach (var view in subviews)
+ if (view.KeyUp (keyEvent))
+ return true;
+
+ return false;
+ }
///
/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
///
@@ -1943,6 +1999,7 @@ namespace Terminal.Gui {
static void ProcessKeyEvent (KeyEvent ke)
{
+
var chain = toplevels.ToList();
foreach (var topLevel in chain) {
if (topLevel.ProcessHotKey (ke))
@@ -1967,6 +2024,29 @@ namespace Terminal.Gui {
}
}
+ static void ProcessKeyDownEvent (KeyEvent ke)
+ {
+ var chain = toplevels.ToList ();
+ foreach (var topLevel in chain) {
+ if (topLevel.KeyDown (ke))
+ return;
+ if (topLevel.Modal)
+ break;
+ }
+ }
+
+
+ static void ProcessKeyUpEvent (KeyEvent ke)
+ {
+ var chain = toplevels.ToList ();
+ foreach (var topLevel in chain) {
+ if (topLevel.KeyUp (ke))
+ return;
+ if (topLevel.Modal)
+ break;
+ }
+ }
+
static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
{
var startFrame = start.Frame;
@@ -2092,7 +2172,7 @@ namespace Terminal.Gui {
}
toplevels.Push (toplevel);
Current = toplevel;
- Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
+ Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent);
if (toplevel.LayoutStyle == LayoutStyle.Computed)
toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
toplevel.LayoutSubviews ();
diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs
index 19cdc4e48..9e9511e85 100644
--- a/Terminal.Gui/Drivers/ConsoleDriver.cs
+++ b/Terminal.Gui/Drivers/ConsoleDriver.cs
@@ -469,7 +469,7 @@ namespace Terminal.Gui {
///
///
///
- public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler);
+ public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler);
///
/// Updates the screen to reflect all the changes that have been done to the display buffer
diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs
index 2508051db..c51b37b6f 100644
--- a/Terminal.Gui/Drivers/CursesDriver.cs
+++ b/Terminal.Gui/Drivers/CursesDriver.cs
@@ -203,8 +203,9 @@ namespace Terminal.Gui {
keyHandler (new KeyEvent ((Key)wch));
}
- public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler)
+ public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler)
{
+ // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
Curses.timeout (-1);
(mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => {
diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs
index 0b5217910..188b7b30c 100644
--- a/Terminal.Gui/Drivers/NetDriver.cs
+++ b/Terminal.Gui/Drivers/NetDriver.cs
@@ -320,8 +320,9 @@ namespace Terminal.Gui {
return (Key)(0xffffffff);
}
- public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler)
+ public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler)
{
+ // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
(mainLoop.Driver as NetMainLoop).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) {
var map = MapKey (consoleKey);
if (map == (Key)0xffffffff)
diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs
index f48eb6418..1dcaf34fc 100644
--- a/Terminal.Gui/Drivers/WindowsDriver.cs
+++ b/Terminal.Gui/Drivers/WindowsDriver.cs
@@ -26,6 +26,7 @@
// SOFTWARE.
//
using System;
+using System.CodeDom;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
@@ -102,7 +103,7 @@ namespace Terminal.Gui {
}
if (ScreenBuffer != IntPtr.Zero)
- CloseHandle(ScreenBuffer);
+ CloseHandle (ScreenBuffer);
ScreenBuffer = IntPtr.Zero;
}
@@ -358,8 +359,8 @@ namespace Terminal.Gui {
[DllImport ("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle (int nStdHandle);
- [DllImport("kernel32.dll", SetLastError = true)]
- static extern bool CloseHandle(IntPtr handle);
+ [DllImport ("kernel32.dll", SetLastError = true)]
+ static extern bool CloseHandle (IntPtr handle);
[DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
public static extern bool ReadConsoleInput (
@@ -424,8 +425,8 @@ namespace Terminal.Gui {
internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver {
static bool sync;
- ManualResetEventSlim eventReady = new ManualResetEventSlim(false);
- ManualResetEventSlim waitForProbe = new ManualResetEventSlim(false);
+ ManualResetEventSlim eventReady = new ManualResetEventSlim (false);
+ ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
MainLoop mainLoop;
WindowsConsole.CharInfo [] OutputBuffer;
int cols, rows;
@@ -456,17 +457,17 @@ namespace Terminal.Gui {
Task.Run ((Action)WindowsInputHandler);
}
- [StructLayout(LayoutKind.Sequential)]
+ [StructLayout (LayoutKind.Sequential)]
public struct ConsoleKeyInfoEx {
public ConsoleKeyInfo consoleKeyInfo;
public bool CapsLock;
public bool NumLock;
- public ConsoleKeyInfoEx(ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
+ public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
{
- this.consoleKeyInfo = consoleKeyInfo;
- CapsLock = capslock;
- NumLock = numlock;
+ this.consoleKeyInfo = consoleKeyInfo;
+ CapsLock = capslock;
+ NumLock = numlock;
}
}
@@ -476,8 +477,8 @@ namespace Terminal.Gui {
void WindowsInputHandler ()
{
while (true) {
- waitForProbe.Wait();
- waitForProbe.Reset();
+ waitForProbe.Wait ();
+ waitForProbe.Reset ();
uint numberEventsRead = 0;
@@ -487,7 +488,7 @@ namespace Terminal.Gui {
else
result = records;
- eventReady.Set();
+ eventReady.Set ();
}
}
@@ -498,7 +499,7 @@ namespace Terminal.Gui {
void IMainLoopDriver.Wakeup ()
{
- tokenSource.Cancel();
+ tokenSource.Cancel ();
}
bool IMainLoopDriver.EventsPending (bool wait)
@@ -517,31 +518,35 @@ namespace Terminal.Gui {
waitTimeout = 0;
result = null;
- waitForProbe.Set();
+ waitForProbe.Set ();
try {
- if(!tokenSource.IsCancellationRequested)
- eventReady.Wait(waitTimeout, tokenSource.Token);
+ if (!tokenSource.IsCancellationRequested)
+ eventReady.Wait (waitTimeout, tokenSource.Token);
} catch (OperationCanceledException) {
return true;
} finally {
- eventReady.Reset();
+ eventReady.Reset ();
}
if (!tokenSource.IsCancellationRequested)
return result != null;
- tokenSource.Dispose();
- tokenSource = new CancellationTokenSource();
+ tokenSource.Dispose ();
+ tokenSource = new CancellationTokenSource ();
return true;
}
Action keyHandler;
+ Action keyDownHandler;
+ Action keyUpHandler;
Action mouseHandler;
- public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler)
+ public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler)
{
this.keyHandler = keyHandler;
+ this.keyDownHandler = keyDownHandler;
+ this.keyUpHandler = keyUpHandler;
this.mouseHandler = mouseHandler;
}
@@ -554,12 +559,37 @@ namespace Terminal.Gui {
var inputEvent = result [0];
switch (inputEvent.EventType) {
case WindowsConsole.EventType.Key:
- if (inputEvent.KeyEvent.bKeyDown == false)
- return;
var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
- if (inputEvent.KeyEvent.UnicodeChar == 0 && map == (Key)0xffffffff)
- return;
- keyHandler (new KeyEvent (map));
+ if (map == (Key)0xffffffff) {
+ KeyEvent key;
+ // Shift = VK_SHIFT = 0x10
+ // Ctrl = VK_CONTROL = 0x11
+ // Alt = VK_MENU = 0x12
+ switch (inputEvent.KeyEvent.wVirtualKeyCode) {
+ case 0x11:
+ key = new KeyEvent (Key.CtrlMask);
+ break;
+ case 0x12:
+ key = new KeyEvent (Key.AltMask);
+ break;
+ default:
+ key = new KeyEvent (Key.Unknown);
+ break;
+ }
+
+ if (inputEvent.KeyEvent.bKeyDown)
+ keyDownHandler (key);
+ else
+ keyUpHandler (key);
+ } else {
+ if (inputEvent.KeyEvent.bKeyDown) {
+ // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
+ keyDownHandler (new KeyEvent (map));
+ keyHandler (new KeyEvent (map));
+ } else {
+ keyUpHandler (new KeyEvent (map));
+ }
+ }
break;
case WindowsConsole.EventType.Mouse:
@@ -571,7 +601,7 @@ namespace Terminal.Gui {
rows = inputEvent.WindowBufferSizeEvent.size.Y;
ResizeScreen ();
UpdateOffScreen ();
- TerminalResized?.Invoke();
+ TerminalResized?.Invoke ();
break;
}
result = null;
@@ -734,8 +764,8 @@ namespace Terminal.Gui {
bool capslock = (state & (WindowsConsole.ControlKeyState.CapslockOn)) != 0;
bool numlock = (state & (WindowsConsole.ControlKeyState.NumlockOn)) != 0;
- var ConsoleKeyInfo = new ConsoleKeyInfo(keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
- return new ConsoleKeyInfoEx(ConsoleKeyInfo, capslock, numlock);
+ var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
+ return new ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock);
}
public Key MapKey (ConsoleKeyInfoEx keyInfoEx)
@@ -837,6 +867,7 @@ namespace Terminal.Gui {
return (Key)((int)Key.F1 + delta);
}
+
return (Key)(0xffffffff);
}
@@ -871,7 +902,7 @@ namespace Terminal.Gui {
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.Disabled = MakeColor(ConsoleColor.DarkGray, 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);
@@ -925,10 +956,10 @@ namespace Terminal.Gui {
}
ccol++;
- var runeWidth = Rune.ColumnWidth(rune);
+ var runeWidth = Rune.ColumnWidth (rune);
if (runeWidth > 1) {
for (int i = 1; i < runeWidth; i++) {
- AddStr(" ");
+ AddStr (" ");
}
}
if (ccol == Cols) {
@@ -947,7 +978,7 @@ namespace Terminal.Gui {
}
int currentAttribute;
- CancellationTokenSource tokenSource = new CancellationTokenSource();
+ CancellationTokenSource tokenSource = new CancellationTokenSource ();
public override void SetAttribute (Attribute c)
{
@@ -995,39 +1026,39 @@ namespace Terminal.Gui {
if (damageRegion.Left == -1)
return;
- var bufferCoords = new WindowsConsole.Coord (){
+ var bufferCoords = new WindowsConsole.Coord () {
X = (short)Clip.Width,
Y = (short)Clip.Height
};
- var window = new WindowsConsole.SmallRect (){
+ var window = new WindowsConsole.SmallRect () {
Top = 0,
Left = 0,
Right = (short)Clip.Right,
Bottom = (short)Clip.Bottom
};
- UpdateCursor();
+ UpdateCursor ();
winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion);
-// System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
+ // System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
}
- public override void UpdateCursor()
+ public override void UpdateCursor ()
{
- var position = new WindowsConsole.Coord(){
+ var position = new WindowsConsole.Coord () {
X = (short)ccol,
Y = (short)crow
};
- winConsole.SetCursorPosition(position);
+ winConsole.SetCursorPosition (position);
}
public override void End ()
{
- winConsole.Cleanup();
+ winConsole.Cleanup ();
}
-#region Unused
+ #region Unused
public override void SetColors (ConsoleColor foreground, ConsoleColor background)
{
}
@@ -1055,7 +1086,7 @@ namespace Terminal.Gui {
public override void CookMouse ()
{
}
-#endregion
+ #endregion
}
diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs
index aea8d9780..f2c9c6f1b 100644
--- a/Terminal.Gui/Event.cs
+++ b/Terminal.Gui/Event.cs
@@ -178,6 +178,12 @@ namespace Terminal.Gui {
///
AltMask = 0x80000000,
+ ///
+ /// When this value is set, the Key encodes the sequence Ctrl-KeyValue.
+ /// And the actual value must be extracted by removing the CtrlMask.
+ ///
+ CtrlMask = 0x40000000,
+
///
/// Backspace key.
///
@@ -297,7 +303,7 @@ namespace Terminal.Gui {
public bool IsAlt => (Key & Key.AltMask) != 0;
///
- /// Determines whether the value is a control key
+ /// Determines whether the value is a control key (and NOT just the ctrl key)
///
/// true if is ctrl; otherwise, false.
public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index 95f44b45c..27bf8546b 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -1,998 +1,1096 @@
-//
-// 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;
- }
- }
- }
-
- ///
- /// Initializes a new .
- ///
- /// Title for the menu item.
- /// The menu sub-menu.
- 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 {
- ///
- /// Initializes a new .
- ///
- /// Title for the menu item.
- /// The items in the current menu.
- public MenuBarItem (ustring title, MenuItem [] children)
- {
- SetTitle (title ?? "");
- Children = children;
- }
-
- ///
- /// Initializes a new .
- ///
- /// The items in the current menu.
- public MenuBarItem (MenuItem[] children) : this (new string (' ', GetMaxTitleLength (children)), children)
- {
- }
-
- 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 {
- internal MenuBarItem barItems;
- MenuBar host;
- internal int current;
- internal View previousSubFocused;
-
- 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;
- WantMousePositionReports = host.WantMousePositionReports;
- }
-
- 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 (Frame.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 ()
- {
- if (!host.isMenuClosed)
- Move (2, 1 + current);
- else
- host.PositionCursor ();
- }
-
- void Run (Action action)
- {
- if (action == null)
- return;
-
- Application.UngrabMouse ();
- host.CloseAllMenus ();
- Application.Refresh ();
-
- 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 && host.selectedSub > -1) {
- current++;
- host.PreviousMenu (true);
- 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]?.SubMenu != null &&
- !disabled && !host.isMenuClosed) {
- CheckSubMenu ();
- break;
- }
- if (host.isMenuClosed)
- host.OpenMenu (host.selected);
- } while (barItems.Children [current] == null || disabled);
- SetNeedsDisplay ();
- break;
- case Key.CursorLeft:
- host.PreviousMenu (true);
- break;
- case Key.CursorRight:
- host.NextMenu (barItems.Children [current].IsFromSubMenu ? true : false);
- break;
- case Key.Esc:
- Application.UngrabMouse ();
- host.CloseAllMenus ();
- 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)
- {
- if (!host.handled && !host.HandleGrabView (me, this)) {
- return false;
- }
- host.handled = false;
- 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;
- } else 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;
- }
-
- internal void CheckSubMenu ()
- {
- if (barItems.Children [current] == null)
- return;
- var subMenu = barItems.Children [current].SubMenu;
- if (subMenu != null) {
- int pos = -1;
- if (host.openSubMenu != null)
- pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
- host.Activate (host.selected, pos, subMenu);
- } else if (host.openSubMenu != null && !barItems.Children [current].IsFromSubMenu)
- host.CloseMenu (false, true);
- }
-
- int GetSubMenuIndex (MenuBarItem subMenu)
- {
- int pos = -1;
- if (this != null && Subviews.Count > 0) {
- Menu v = null;
- foreach (var menu in Subviews) {
- if (((Menu)menu).barItems == subMenu)
- v = (Menu)menu;
- }
- if (v != null)
- pos = Subviews.IndexOf (v);
- }
-
- return pos;
- }
- }
-
-
-
- ///
- /// 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; }
- internal int selected;
- internal int selectedSub;
- Action action;
-
- ///
- /// Used for change the navigation key style.
- ///
- public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
-
- ///
- /// 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;
- selectedSub = -1;
- ColorScheme = Colors.Menu;
- WantMousePositionReports = true;
- isMenuClosed = true;
- }
-
- public override void Redraw (Rect region)
- {
- Move (0, 0);
- Driver.SetAttribute (Colors.Menu.Normal);
- 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++;
- if (!isMenuClosed)
- Move (pos, 0);
- else
- Move (pos + 1, 0);
- return;
- } else {
- if (!isMenuClosed)
- pos += Menus [i].TitleLength + 4;
- else
- pos += 2 + Menus [i].TitleLength + 1;
- }
- }
- Move (0, 0);
- }
-
- void Selected (MenuItem item)
- {
- // TODO: Running = false;
- action = item.Action;
- }
-
- public event EventHandler OnOpenMenu;
- public event EventHandler OnCloseMenu;
- internal Menu openMenu;
- Menu openCurrentMenu;
- internal List