Menu.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. //
  2. // Menu.cs: application menus and submenus
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. // TODO:
  8. // Add accelerator support, but should also support chords (ShortCut in MenuItem)
  9. // Add mouse support
  10. // Allow menus inside menus
  11. using System;
  12. namespace Terminal {
  13. /// <summary>
  14. /// A menu item has a title, an associated help text, and an action to execute on activation.
  15. /// </summary>
  16. public class MenuItem {
  17. public MenuItem (string title, string help, Action action)
  18. {
  19. Title = title ?? "";
  20. Help = help ?? "";
  21. Action = action;
  22. bool nextIsHot = false;
  23. foreach (var x in title) {
  24. if (x == '_')
  25. nextIsHot = true;
  26. else {
  27. if (nextIsHot) {
  28. HotKey = x;
  29. break;
  30. }
  31. nextIsHot = false;
  32. }
  33. }
  34. }
  35. // The hotkey is used when the menu is active, the shortcut can be triggered
  36. // when the menu is not active.
  37. // For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
  38. // if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
  39. public char HotKey;
  40. public Key ShortCut;
  41. public string Title { get; set; }
  42. public string Help { get; set; }
  43. public Action Action { get; set; }
  44. internal int Width => Title.Length + Help.Length + 1 + 2;
  45. }
  46. /// <summary>
  47. /// A menu bar item contains other menu items.
  48. /// </summary>
  49. public class MenuBarItem {
  50. public MenuBarItem (string title, MenuItem [] children)
  51. {
  52. SetTitle (title ?? "");
  53. Children = children;
  54. }
  55. void SetTitle (string title)
  56. {
  57. if (title == null)
  58. title = "";
  59. Title = title;
  60. int len = 0;
  61. foreach (var ch in Title) {
  62. if (ch == '_')
  63. continue;
  64. len++;
  65. }
  66. TitleLength = len;
  67. }
  68. public string Title { get; set; }
  69. public MenuItem [] Children { get; set; }
  70. internal int TitleLength { get; private set; }
  71. }
  72. class Menu : View {
  73. MenuBarItem barItems;
  74. MenuBar host;
  75. int current;
  76. static Rect MakeFrame (int x, int y, MenuItem [] items)
  77. {
  78. int maxW = 0;
  79. foreach (var item in items) {
  80. var l = item.Width;
  81. maxW = Math.Max (l, maxW);
  82. }
  83. return new Rect (x, y, maxW + 2, items.Length + 2);
  84. }
  85. public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
  86. {
  87. this.barItems = barItems;
  88. this.host = host;
  89. CanFocus = true;
  90. }
  91. public override void Redraw (Rect region)
  92. {
  93. Driver.SetAttribute (Colors.Menu.Normal);
  94. DrawFrame (region, true);
  95. for (int i = 0; i < barItems.Children.Length; i++){
  96. var item = barItems.Children [i];
  97. Move (1, i+1);
  98. Driver.SetAttribute (item == null ? Colors.Base.Focus : i == current ? Colors.Menu.Focus : Colors.Menu.Normal);
  99. for (int p = 0; p < Frame.Width-2; p++)
  100. if (item == null)
  101. Driver.AddSpecial (SpecialChar.HLine);
  102. else
  103. Driver.AddCh (' ');
  104. if (item == null)
  105. continue;
  106. Move (2, i + 1);
  107. DrawHotString (item.Title,
  108. i == current? Colors.Menu.HotFocus : Colors.Menu.HotNormal,
  109. i == current ? Colors.Menu.Focus : Colors.Menu.Normal);
  110. // The help string
  111. var l = item.Help.Length;
  112. Move (Frame.Width - l - 2, 1 + i);
  113. Driver.AddStr (item.Help);
  114. }
  115. }
  116. public override void PositionCursor ()
  117. {
  118. Move (2, 1 + current);
  119. }
  120. void Run (Action action)
  121. {
  122. if (action == null)
  123. return;
  124. Application.MainLoop.AddIdle (() => {
  125. action ();
  126. return false;
  127. });
  128. }
  129. public override bool ProcessKey (KeyEvent kb)
  130. {
  131. switch (kb.Key) {
  132. case Key.CursorUp:
  133. current--;
  134. if (current < 0)
  135. current = barItems.Children.Length - 1;
  136. SetNeedsDisplay ();
  137. break;
  138. case Key.CursorDown:
  139. current++;
  140. if (current== barItems.Children.Length)
  141. current = 0;
  142. SetNeedsDisplay ();
  143. break;
  144. case Key.CursorLeft:
  145. host.PreviousMenu ();
  146. break;
  147. case Key.CursorRight:
  148. host.NextMenu ();
  149. break;
  150. case Key.Esc:
  151. host.CloseMenu ();
  152. break;
  153. case Key.Enter:
  154. host.CloseMenu ();
  155. Run (barItems.Children [current].Action);
  156. break;
  157. default:
  158. // TODO: rune-ify
  159. if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
  160. var x = Char.ToUpper ((char)kb.KeyValue);
  161. foreach (var item in barItems.Children) {
  162. if (item.HotKey == x) {
  163. host.CloseMenu ();
  164. Run (item.Action);
  165. return true;
  166. }
  167. }
  168. }
  169. break;
  170. }
  171. return true;
  172. }
  173. }
  174. /// <summary>
  175. /// A menu bar for your application.
  176. /// </summary>
  177. public class MenuBar : View {
  178. public MenuBarItem [] Menus { get; set; }
  179. int selected;
  180. Action action;
  181. public MenuBar (MenuBarItem [] menus) : base (new Rect (0, 0, Application.Driver.Cols, 1))
  182. {
  183. Menus = menus;
  184. CanFocus = false;
  185. selected = -1;
  186. }
  187. public override void Redraw (Rect region)
  188. {
  189. Move (0, 0);
  190. Driver.SetAttribute (Colors.Base.Focus);
  191. for (int i = 0; i < Frame.Width; i++)
  192. Driver.AddCh (' ');
  193. Move (1, 0);
  194. int pos = 1;
  195. for (int i = 0; i < Menus.Length; i++) {
  196. var menu = Menus [i];
  197. Move (pos, 0);
  198. Attribute hotColor, normalColor;
  199. if (i == selected){
  200. hotColor = i == selected ? Colors.Menu.HotFocus : Colors.Menu.HotNormal;
  201. normalColor = i == selected ? Colors.Menu.Focus : Colors.Menu.Normal;
  202. } else {
  203. hotColor = Colors.Base.Focus;
  204. normalColor = Colors.Base.Focus;
  205. }
  206. DrawHotString (" " + menu.Title + " " + " ", hotColor, normalColor);
  207. pos += menu.TitleLength+ 3;
  208. }
  209. PositionCursor ();
  210. }
  211. public override void PositionCursor ()
  212. {
  213. int pos = 0;
  214. for (int i = 0; i < Menus.Length; i++) {
  215. if (i == selected) {
  216. pos++;
  217. Move (pos, 0);
  218. return;
  219. } else {
  220. pos += Menus [i].TitleLength + 4;
  221. }
  222. }
  223. Move (0, 0);
  224. }
  225. void Selected (MenuItem item)
  226. {
  227. // TODO: Running = false;
  228. action = item.Action;
  229. }
  230. Menu openMenu;
  231. View previousFocused;
  232. void OpenMenu (int index)
  233. {
  234. if (openMenu != null)
  235. SuperView.Remove (openMenu);
  236. int pos = 0;
  237. for (int i = 0; i < index; i++)
  238. pos += Menus [i].Title.Length + 3;
  239. openMenu = new Menu (this, pos, 1, Menus [index]);
  240. SuperView.Add (openMenu);
  241. SuperView.SetFocus (openMenu);
  242. }
  243. void StartMenu ()
  244. {
  245. if (openMenu != null)
  246. return;
  247. selected = 0;
  248. SetNeedsDisplay ();
  249. previousFocused = SuperView.Focused;
  250. OpenMenu (selected);
  251. }
  252. internal void CloseMenu ()
  253. {
  254. selected = -1;
  255. SetNeedsDisplay ();
  256. SuperView.Remove (openMenu);
  257. previousFocused.SuperView.SetFocus (previousFocused);
  258. openMenu = null;
  259. }
  260. internal void PreviousMenu ()
  261. {
  262. if (selected <= 0)
  263. selected = Menus.Length - 1;
  264. else
  265. selected--;
  266. OpenMenu (selected);
  267. }
  268. internal void NextMenu ()
  269. {
  270. if (selected == -1)
  271. selected = 0;
  272. else if (selected + 1 == Menus.Length)
  273. selected = 0;
  274. else
  275. selected++;
  276. OpenMenu (selected);
  277. }
  278. public override bool ProcessHotKey (KeyEvent kb)
  279. {
  280. if (kb.Key == Key.F9) {
  281. StartMenu ();
  282. return true;
  283. }
  284. var kc = kb.KeyValue;
  285. return base.ProcessHotKey (kb);
  286. }
  287. public override bool ProcessKey (KeyEvent kb)
  288. {
  289. switch (kb.Key) {
  290. case Key.CursorLeft:
  291. selected--;
  292. if (selected < 0)
  293. selected = Menus.Length - 1;
  294. break;
  295. case Key.CursorRight:
  296. selected = (selected + 1) % Menus.Length;
  297. break;
  298. case Key.Esc:
  299. case Key.ControlC:
  300. //TODO: Running = false;
  301. break;
  302. default:
  303. var key = kb.KeyValue;
  304. if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
  305. char c = Char.ToUpper ((char)key);
  306. if (Menus [selected].Children == null)
  307. return false;
  308. foreach (var mi in Menus [selected].Children) {
  309. int p = mi.Title.IndexOf ('_');
  310. if (p != -1 && p + 1 < mi.Title.Length) {
  311. if (mi.Title [p + 1] == c) {
  312. Selected (mi);
  313. return true;
  314. }
  315. }
  316. }
  317. }
  318. return false;
  319. }
  320. SetNeedsDisplay ();
  321. return true;
  322. }
  323. }
  324. }