Menu.cs 8.5 KB

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