Menu.cs 8.4 KB

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