Menu.cs 8.2 KB

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