Menu.cs 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727
  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. // Allow menus inside menus
  10. using System;
  11. using NStack;
  12. using System.Linq;
  13. using System.Collections.Generic;
  14. namespace Terminal.Gui {
  15. /// <summary>
  16. /// Specifies how a <see cref="MenuItem"/> shows selection state.
  17. /// </summary>
  18. [Flags]
  19. public enum MenuItemCheckStyle {
  20. /// <summary>
  21. /// The menu item will be shown normally, with no check indicator.
  22. /// </summary>
  23. NoCheck = 0b_0000_0000,
  24. /// <summary>
  25. /// The menu item will indicate checked/un-checked state (see <see cref="Checked"/>.
  26. /// </summary>
  27. Checked = 0b_0000_0001,
  28. /// <summary>
  29. /// The menu item is part of a menu radio group (see <see cref="Checked"/> and will indicate selected state.
  30. /// </summary>
  31. Radio = 0b_0000_0010,
  32. };
  33. /// <summary>
  34. /// A <see cref="MenuItem"/> has a title, an associated help text, and an action to execute on activation.
  35. /// </summary>
  36. public class MenuItem {
  37. ustring title;
  38. Key shortCut;
  39. /// <summary>
  40. /// Initializes a new instance of <see cref="MenuItem"/>
  41. /// </summary>
  42. public MenuItem (Key shortCut = Key.ControlSpace)
  43. {
  44. Title = "";
  45. Help = "";
  46. if (shortCut != Key.ControlSpace) {
  47. ShortCut = shortCut;
  48. }
  49. }
  50. /// <summary>
  51. /// Initializes a new instance of <see cref="MenuItem"/>.
  52. /// </summary>
  53. /// <param name="title">Title for the menu item.</param>
  54. /// <param name="help">Help text to display.</param>
  55. /// <param name="action">Action to invoke when the menu item is activated.</param>
  56. /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
  57. /// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
  58. /// <param name="shortCut">The <see cref="ShortCut"/> keystroke combination.</param>
  59. public MenuItem (ustring title, ustring help, Action action, Func<bool> canExecute = null, MenuItem parent = null, Key shortCut = Key.ControlSpace)
  60. {
  61. Title = title ?? "";
  62. Help = help ?? "";
  63. Action = action;
  64. CanExecute = canExecute;
  65. Parent = parent;
  66. if (shortCut != Key.ControlSpace) {
  67. ShortCut = shortCut;
  68. }
  69. }
  70. /// <summary>
  71. /// The HotKey is used when the menu is active, the shortcut can be triggered when the menu is not active.
  72. /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
  73. /// if the ShortCut is set to "Control-N", this would be a global hotkey that would trigger as well
  74. /// </summary>
  75. public Rune HotKey;
  76. /// <summary>
  77. /// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
  78. /// </summary>
  79. public Key ShortCut {
  80. get => shortCut;
  81. set {
  82. if (shortCut != value) {
  83. if (GetKeyToString (value).Contains ("Control")) {
  84. shortCut = Key.CtrlMask | value;
  85. } else {
  86. shortCut = value;
  87. }
  88. ShortCutTag = GetShortCutTag (shortCut);
  89. }
  90. }
  91. }
  92. /// <summary>
  93. /// The keystroke combination used in the <see cref="ShortCut"/> as string.
  94. /// </summary>
  95. public ustring ShortCutTag { get; private set; } = ustring.Empty;
  96. /// <summary>
  97. /// Gets or sets the title.
  98. /// </summary>
  99. /// <value>The title.</value>
  100. public ustring Title {
  101. get { return title; }
  102. set {
  103. if (title != value) {
  104. title = value;
  105. GetHotKey ();
  106. }
  107. }
  108. }
  109. /// <summary>
  110. /// Gets or sets the help text for the menu item.
  111. /// </summary>
  112. /// <value>The help text.</value>
  113. public ustring Help { get; set; }
  114. /// <summary>
  115. /// Gets or sets the action to be invoked when the menu is triggered
  116. /// </summary>
  117. /// <value>Method to invoke.</value>
  118. public Action Action { get; set; }
  119. /// <summary>
  120. /// Gets or sets the action to be invoked if the menu can be triggered
  121. /// </summary>
  122. /// <value>Function to determine if action is ready to be executed.</value>
  123. public Func<bool> CanExecute { get; set; }
  124. /// <summary>
  125. /// Shortcut to check if the menu item is enabled
  126. /// </summary>
  127. public bool IsEnabled ()
  128. {
  129. return CanExecute == null ? true : CanExecute ();
  130. }
  131. internal int Width => Title.RuneCount + Help.RuneCount + 1 + 2 +
  132. (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) +
  133. (ShortCutTag.RuneCount > 0 ? ShortCutTag.RuneCount + 2 : 0);
  134. /// <summary>
  135. /// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See <see cref="MenuItemCheckStyle"/>.
  136. /// </summary>
  137. public bool Checked { set; get; }
  138. /// <summary>
  139. /// Sets or gets the type selection indicator the menu item will be displayed with.
  140. /// </summary>
  141. public MenuItemCheckStyle CheckType { get; set; }
  142. /// <summary>
  143. /// Gets or sets the parent for this <see cref="MenuItem"/>.
  144. /// </summary>
  145. /// <value>The parent.</value>
  146. public MenuItem Parent { get; internal set; }
  147. /// <summary>
  148. /// Gets if this <see cref="MenuItem"/> is from a sub-menu.
  149. /// </summary>
  150. internal bool IsFromSubMenu { get {return Parent != null; } }
  151. /// <summary>
  152. /// Merely a debugging aid to see the interaction with main
  153. /// </summary>
  154. public MenuItem GetMenuItem ()
  155. {
  156. return this;
  157. }
  158. /// <summary>
  159. /// Merely a debugging aid to see the interaction with main
  160. /// </summary>
  161. public bool GetMenuBarItem ()
  162. {
  163. return IsFromSubMenu;
  164. }
  165. void GetHotKey ()
  166. {
  167. bool nextIsHot = false;
  168. foreach (var x in title) {
  169. if (x == '_') {
  170. nextIsHot = true;
  171. } else {
  172. if (nextIsHot) {
  173. HotKey = Char.ToUpper ((char)x);
  174. break;
  175. }
  176. nextIsHot = false;
  177. HotKey = default;
  178. }
  179. }
  180. }
  181. /// <summary>
  182. /// Get the <see cref="ShortCut"/> key as string.
  183. /// </summary>
  184. /// <param name="shortCut">The shortcut key.</param>
  185. /// <returns></returns>
  186. public ustring GetShortCutTag (Key shortCut)
  187. {
  188. if (shortCut == Key.ControlSpace) {
  189. return "";
  190. }
  191. var k = (uint)shortCut;
  192. var delimiter = MenuBar.ShortCutDelimiter;
  193. ustring tag = ustring.Empty;
  194. var sCut = GetKeyToString (shortCut).ToString ();
  195. if (sCut.Contains ("Control") || (shortCut & Key.CtrlMask) != 0) {
  196. tag = "Ctrl";
  197. }
  198. if ((shortCut & Key.ShiftMask) != 0) {
  199. if (!tag.IsEmpty) {
  200. tag += delimiter;
  201. }
  202. tag += "Shift";
  203. }
  204. if ((shortCut & Key.AltMask) != 0) {
  205. if (!tag.IsEmpty) {
  206. tag += delimiter;
  207. }
  208. tag += "Alt";
  209. }
  210. ustring [] keys = ustring.Make (sCut).Split (",");
  211. for (int i = 0; i < keys.Length; i++) {
  212. var key = keys [i].TrimSpace ();
  213. if (key == Key.AltMask.ToString () || key == Key.ShiftMask.ToString () || key == Key.CtrlMask.ToString ()) {
  214. continue;
  215. }
  216. if (!tag.IsEmpty) {
  217. tag += delimiter;
  218. }
  219. if (key.Contains ("Control")) {
  220. tag += ((char)key.ElementAt (7)).ToString ();
  221. } else if (!key.Contains ("F") && key.Length > 2 && keys.Length == 1) {
  222. k = (uint)Key.AltMask + k;
  223. tag += ((char)k).ToString ();
  224. } else if (key.Length == 2 && key.StartsWith ("D")) {
  225. tag += ((char)key.ElementAt (1)).ToString ();
  226. } else {
  227. tag += key;
  228. }
  229. }
  230. return tag;
  231. }
  232. ustring GetKeyToString (Key key)
  233. {
  234. if (key == Key.ControlSpace) {
  235. return "";
  236. }
  237. var mK = key & (Key.AltMask | Key.CtrlMask | Key.ShiftMask);
  238. for (uint i = (uint)Key.F1; i < (uint)Key.F12; i++) {
  239. if ((key | (Key)i) == key) {
  240. mK |= (Key)i;
  241. }
  242. }
  243. var k = key;
  244. k &= ~mK;
  245. int.TryParse (k.ToString (), out int c);
  246. var s = mK == Key.ControlSpace ? "" : mK.ToString ();
  247. if (s != "" && (k != Key.ControlSpace || c > 0)) {
  248. s += ",";
  249. }
  250. s += c == 0 ? k == Key.ControlSpace ? "" : k.ToString () : ((char)c).ToString ();
  251. return s;
  252. }
  253. /// <summary>
  254. /// Allows to generate a <see cref="Key"/> from a <see cref="ShortCutTag"/>
  255. /// </summary>
  256. /// <param name="tag">The key as string.</param>
  257. /// <returns></returns>
  258. public Key CreateShortCutFromTag (ustring tag)
  259. {
  260. var sCut = tag;
  261. if (sCut.IsEmpty) {
  262. return default;
  263. }
  264. Key key = Key.ControlSpace;
  265. var hasCtrl = false;
  266. var delimiter = MenuBar.ShortCutDelimiter;
  267. ustring [] keys = sCut.Split (MenuBar.ShortCutDelimiter);
  268. for (int i = 0; i < keys.Length; i++) {
  269. var k = keys [i];
  270. if (k == "Ctrl") {
  271. hasCtrl = true;
  272. key |= Key.CtrlMask;
  273. } else if (k == "Shift") {
  274. key |= Key.ShiftMask;
  275. } else if (k == "Alt") {
  276. key |= Key.AltMask;
  277. } else if (k.StartsWith ("F") && k.Length > 1) {
  278. int.TryParse (k.Substring (1).ToString (), out int n);
  279. for (uint j = (uint)Key.F1; j < (uint)Key.F12; j++) {
  280. int.TryParse (((Key)j).ToString ().Substring (1), out int f);
  281. if (f == n) {
  282. key |= (Key)j;
  283. }
  284. }
  285. } else if (k [0] >= 'A' && k [0] <= 'Z') {
  286. if (hasCtrl) {
  287. var n = k [0] - 'A' + 1;
  288. var d = n - (uint)Key.ControlA;
  289. key |= (Key)((uint)Key.ControlA + d);
  290. } else {
  291. key |= (Key)k [0];
  292. }
  293. } else if (k [0] >= '0' && k [0] <= '9') {
  294. //var n = k [0] - (uint)Key.D0 + 1;
  295. //var d = n - (uint)Key.D0;
  296. //key |= (Key)((uint)Key.D0 + d);
  297. key |= (Key)k [0];
  298. }
  299. }
  300. return key;
  301. }
  302. }
  303. /// <summary>
  304. /// A <see cref="MenuBarItem"/> contains <see cref="MenuBarItem"/>s or <see cref="MenuItem"/>s.
  305. /// </summary>
  306. public class MenuBarItem : MenuItem {
  307. /// <summary>
  308. /// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
  309. /// </summary>
  310. /// <param name="title">Title for the menu item.</param>
  311. /// <param name="help">Help text to display.</param>
  312. /// <param name="action">Action to invoke when the menu item is activated.</param>
  313. /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
  314. /// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
  315. public MenuBarItem (ustring title, ustring help, Action action, Func<bool> canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
  316. {
  317. SetTitle (title ?? "");
  318. Children = null;
  319. }
  320. /// <summary>
  321. /// Initializes a new <see cref="MenuBarItem"/>.
  322. /// </summary>
  323. /// <param name="title">Title for the menu item.</param>
  324. /// <param name="children">The items in the current menu.</param>
  325. /// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
  326. public MenuBarItem (ustring title, MenuItem [] children, MenuItem parent = null)
  327. {
  328. if (children == null) {
  329. throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
  330. }
  331. SetTitle (title ?? "");
  332. if (parent != null) {
  333. Parent = parent;
  334. }
  335. SetChildrensParent (children);
  336. Children = children;
  337. }
  338. /// <summary>
  339. /// Initializes a new <see cref="MenuBarItem"/>.
  340. /// </summary>
  341. /// <param name="children">The items in the current menu.</param>
  342. public MenuBarItem (MenuItem [] children) : this ("", children) { }
  343. /// <summary>
  344. /// Initializes a new <see cref="MenuBarItem"/>.
  345. /// </summary>
  346. public MenuBarItem () : this (children: new MenuItem [] { }) { }
  347. //static int GetMaxTitleLength (MenuItem [] children)
  348. //{
  349. // int maxLength = 0;
  350. // foreach (var item in children) {
  351. // int len = GetMenuBarItemLength (item.Title);
  352. // if (len > maxLength)
  353. // maxLength = len;
  354. // item.IsFromSubMenu = true;
  355. // }
  356. // return maxLength;
  357. //}
  358. void SetChildrensParent (MenuItem [] childrens)
  359. {
  360. foreach (var child in childrens) {
  361. if (child != null && child.Parent == null) {
  362. child.Parent = this;
  363. }
  364. }
  365. }
  366. /// <summary>
  367. /// Check if the children parameter is a <see cref="MenuBarItem"/>.
  368. /// </summary>
  369. /// <param name="children"></param>
  370. /// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
  371. public MenuBarItem SubMenu (MenuItem children)
  372. {
  373. return children as MenuBarItem;
  374. }
  375. /// <summary>
  376. /// Check if the <see cref="MenuItem"/> parameter is a child of this.
  377. /// </summary>
  378. /// <param name="menuItem"></param>
  379. /// <returns>Returns <c>true</c> if it is a child of this. <c>false</c> otherwise.</returns>
  380. public bool IsSubMenuOf (MenuItem menuItem)
  381. {
  382. foreach (var child in Children) {
  383. if (child == menuItem && child.Parent == menuItem.Parent) {
  384. return true;
  385. }
  386. }
  387. return false;
  388. }
  389. /// <summary>
  390. /// Get the index of the <see cref="MenuItem"/> parameter.
  391. /// </summary>
  392. /// <param name="children"></param>
  393. /// <returns>Returns a value bigger than -1 if the <see cref="MenuItem"/> is a child of this.</returns>
  394. public int GetChildrenIndex (MenuItem children)
  395. {
  396. if (Children?.Length == 0) {
  397. return -1;
  398. }
  399. int i = 0;
  400. foreach (var child in Children) {
  401. if (child == children) {
  402. return i;
  403. }
  404. i++;
  405. }
  406. return -1;
  407. }
  408. void SetTitle (ustring title)
  409. {
  410. if (title == null)
  411. title = "";
  412. Title = title;
  413. }
  414. int GetMenuBarItemLength (ustring title)
  415. {
  416. int len = 0;
  417. foreach (var ch in title) {
  418. if (ch == '_')
  419. continue;
  420. len++;
  421. }
  422. return len;
  423. }
  424. ///// <summary>
  425. ///// Gets or sets the title to display.
  426. ///// </summary>
  427. ///// <value>The title.</value>
  428. //public ustring Title { get; set; }
  429. /// <summary>
  430. /// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
  431. /// </summary>
  432. /// <value>The children.</value>
  433. public MenuItem [] Children { get; set; }
  434. internal int TitleLength => GetMenuBarItemLength (Title);
  435. internal bool IsTopLevel { get => Parent == null && (Children == null || Children.Length == 0) && Action != null; }
  436. }
  437. class Menu : View {
  438. internal MenuBarItem barItems;
  439. MenuBar host;
  440. internal int current;
  441. internal View previousSubFocused;
  442. static Rect MakeFrame (int x, int y, MenuItem [] items)
  443. {
  444. if (items == null || items.Length == 0) {
  445. return new Rect ();
  446. }
  447. int maxW = items.Max (z => z?.Width) ?? 0;
  448. return new Rect (x, y, maxW + 2, items.Length + 2);
  449. }
  450. public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
  451. {
  452. this.barItems = barItems;
  453. this.host = host;
  454. if (barItems.IsTopLevel) {
  455. // This is a standalone MenuItem on a MenuBar
  456. ColorScheme = Colors.Menu;
  457. CanFocus = true;
  458. } else {
  459. current = -1;
  460. for (int i = 0; i < barItems.Children.Length; i++) {
  461. if (barItems.Children [i] != null) {
  462. current = i;
  463. break;
  464. }
  465. }
  466. ColorScheme = Colors.Menu;
  467. CanFocus = true;
  468. WantMousePositionReports = host.WantMousePositionReports;
  469. }
  470. }
  471. internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
  472. {
  473. if (item != null) {
  474. if (index == current) return ColorScheme.Focus;
  475. if (!item.IsEnabled ()) return ColorScheme.Disabled;
  476. }
  477. return ColorScheme.Normal;
  478. }
  479. public override void Redraw (Rect bounds)
  480. {
  481. Driver.SetAttribute (ColorScheme.Normal);
  482. DrawFrame (bounds, padding: 0, fill: true);
  483. for (int i = 0; i < barItems.Children.Length; i++) {
  484. var item = barItems.Children [i];
  485. Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
  486. if (item == null) {
  487. Move (0, i + 1);
  488. Driver.AddRune (Driver.LeftTee);
  489. } else
  490. Move (1, i + 1);
  491. Driver.SetAttribute (DetermineColorSchemeFor (item, i));
  492. for (int p = 0; p < Frame.Width - 2; p++)
  493. if (item == null)
  494. Driver.AddRune (Driver.HLine);
  495. else if (p == Frame.Width - 3 && barItems.SubMenu(barItems.Children [i]) != null)
  496. Driver.AddRune (Driver.RightArrow);
  497. else
  498. Driver.AddRune (' ');
  499. if (item == null) {
  500. Move (Frame.Right - 1, i + 1);
  501. Driver.AddRune (Driver.RightTee);
  502. continue;
  503. }
  504. ustring textToDraw;
  505. var checkChar = Driver.Selected;
  506. var uncheckedChar = Driver.UnSelected;
  507. if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked)) {
  508. checkChar = Driver.Checked;
  509. uncheckedChar = Driver.UnChecked;
  510. }
  511. // Support Checked even though CheckType wasn't set
  512. if (item.Checked) {
  513. textToDraw = ustring.Make (new Rune [] { checkChar, ' ' }) + item.Title;
  514. } else if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked) || item.CheckType.HasFlag (MenuItemCheckStyle.Radio)) {
  515. textToDraw = ustring.Make (new Rune [] { uncheckedChar, ' ' }) + item.Title;
  516. } else {
  517. textToDraw = item.Title;
  518. }
  519. Move (2, i + 1);
  520. if (!item.IsEnabled ())
  521. DrawHotString (textToDraw, ColorScheme.Disabled, ColorScheme.Disabled);
  522. else
  523. DrawHotString (textToDraw,
  524. i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
  525. i == current ? ColorScheme.Focus : ColorScheme.Normal);
  526. // The help string
  527. var l = item.ShortCutTag.RuneCount == 0 ? item.Help.RuneCount : item.Help.RuneCount + item.ShortCutTag.RuneCount + 2;
  528. Move (Frame.Width - l - 2, 1 + i);
  529. Driver.AddStr (item.Help);
  530. // The shortcut tag string
  531. if (!item.ShortCutTag.IsEmpty) {
  532. l = item.ShortCutTag.RuneCount;
  533. Move (Frame.Width - l - 2, 1 + i);
  534. Driver.AddStr (item.ShortCutTag);
  535. }
  536. }
  537. PositionCursor ();
  538. }
  539. public override void PositionCursor ()
  540. {
  541. if (host == null || host.IsMenuOpen)
  542. if (barItems.IsTopLevel) {
  543. host.PositionCursor ();
  544. } else
  545. Move (2, 1 + current);
  546. else
  547. host.PositionCursor ();
  548. }
  549. public void Run (Action action)
  550. {
  551. if (action == null)
  552. return;
  553. Application.UngrabMouse ();
  554. host.CloseAllMenus ();
  555. Application.Refresh ();
  556. Application.MainLoop.AddIdle (() => {
  557. action ();
  558. return false;
  559. });
  560. }
  561. public override bool OnLeave (View view)
  562. {
  563. return host.OnLeave (view);
  564. }
  565. public override bool OnKeyDown (KeyEvent keyEvent)
  566. {
  567. if (keyEvent.IsAlt) {
  568. host.CloseAllMenus ();
  569. return true;
  570. }
  571. return false;
  572. }
  573. public override bool ProcessHotKey (KeyEvent keyEvent)
  574. {
  575. // To ncurses simulate a AltMask key pressing Alt+Space because
  576. // it can�t detect an alone special key down was pressed.
  577. if (keyEvent.IsAlt && keyEvent.Key == Key.AltMask) {
  578. OnKeyDown (keyEvent);
  579. return true;
  580. }
  581. return false;
  582. }
  583. public override bool ProcessKey (KeyEvent kb)
  584. {
  585. switch (kb.Key) {
  586. case Key.Tab:
  587. host.CleanUp ();
  588. return true;
  589. case Key.CursorUp:
  590. return MoveUp ();
  591. case Key.CursorDown:
  592. return MoveDown ();
  593. case Key.CursorLeft:
  594. host.PreviousMenu (true);
  595. return true;
  596. case Key.CursorRight:
  597. host.NextMenu (barItems.IsTopLevel || (barItems.Children != null && current > -1 && current < barItems.Children.Length && barItems.Children [current].IsFromSubMenu) ? true : false);
  598. return true;
  599. case Key.Esc:
  600. Application.UngrabMouse ();
  601. host.CloseAllMenus ();
  602. return true;
  603. case Key.Enter:
  604. if (barItems.IsTopLevel) {
  605. Run (barItems.Action);
  606. } else if (current > -1) {
  607. Run (barItems.Children [current].Action);
  608. }
  609. return true;
  610. default:
  611. // TODO: rune-ify
  612. if (barItems.Children != null && Char.IsLetterOrDigit ((char)kb.KeyValue)) {
  613. var x = Char.ToUpper ((char)kb.KeyValue);
  614. foreach (var item in barItems.Children) {
  615. if (item == null) continue;
  616. if (item.IsEnabled () && item.HotKey == x) {
  617. host.CloseMenu ();
  618. Run (item.Action);
  619. return true;
  620. }
  621. }
  622. }
  623. break;
  624. }
  625. return false;
  626. }
  627. bool MoveDown ()
  628. {
  629. if (barItems.IsTopLevel) {
  630. return true;
  631. }
  632. bool disabled;
  633. do {
  634. current++;
  635. if (current >= barItems.Children.Length) {
  636. current = 0;
  637. }
  638. if (this != host.openCurrentMenu && barItems.Children [current].IsFromSubMenu && host.selectedSub > -1) {
  639. host.PreviousMenu (true);
  640. host.SelectEnabledItem (barItems.Children, current, out current);
  641. host.openCurrentMenu = this;
  642. }
  643. var item = barItems.Children [current];
  644. if (item?.IsEnabled () != true) {
  645. disabled = true;
  646. } else {
  647. disabled = false;
  648. }
  649. if (host.UseKeysUpDownAsKeysLeftRight && barItems.SubMenu (barItems.Children [current]) != null &&
  650. !disabled && host.IsMenuOpen) {
  651. CheckSubMenu ();
  652. break;
  653. }
  654. if (!host.IsMenuOpen) {
  655. host.OpenMenu (host.selected);
  656. }
  657. } while (barItems.Children [current] == null || disabled);
  658. SetNeedsDisplay ();
  659. return true;
  660. }
  661. bool MoveUp ()
  662. {
  663. if (barItems.IsTopLevel || current == -1) {
  664. return true;
  665. }
  666. bool disabled;
  667. do {
  668. current--;
  669. if (host.UseKeysUpDownAsKeysLeftRight) {
  670. if ((current == -1 || this != host.openCurrentMenu) && barItems.Children [current + 1].IsFromSubMenu && host.selectedSub > -1) {
  671. current++;
  672. host.PreviousMenu (true);
  673. if (current > 0) {
  674. current--;
  675. host.openCurrentMenu = this;
  676. }
  677. break;
  678. }
  679. }
  680. if (current < 0)
  681. current = barItems.Children.Length - 1;
  682. if (!host.SelectEnabledItem (barItems.Children, current, out current, false)) {
  683. current = 0;
  684. if (!host.SelectEnabledItem (barItems.Children, current, out current)) {
  685. host.CloseMenu ();
  686. }
  687. break;
  688. }
  689. var item = barItems.Children [current];
  690. if (item?.IsEnabled () != true) {
  691. disabled = true;
  692. } else {
  693. disabled = false;
  694. }
  695. if (host.UseKeysUpDownAsKeysLeftRight && barItems.SubMenu (barItems.Children [current]) != null &&
  696. !disabled && host.IsMenuOpen) {
  697. CheckSubMenu ();
  698. break;
  699. }
  700. } while (barItems.Children [current] == null || disabled);
  701. SetNeedsDisplay ();
  702. return true;
  703. }
  704. public override bool MouseEvent (MouseEvent me)
  705. {
  706. if (!host.handled && !host.HandleGrabView (me, this)) {
  707. return false;
  708. }
  709. host.handled = false;
  710. bool disabled;
  711. if (me.Flags == MouseFlags.Button1Clicked) {
  712. disabled = false;
  713. if (me.Y < 1)
  714. return true;
  715. var meY = me.Y - 1;
  716. if (meY >= barItems.Children.Length)
  717. return true;
  718. var item = barItems.Children [meY];
  719. if (item == null || !item.IsEnabled ()) disabled = true;
  720. if (item != null && !disabled)
  721. Run (barItems.Children [meY].Action);
  722. return true;
  723. } else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked ||
  724. me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.ReportMousePosition ||
  725. me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
  726. disabled = false;
  727. if (me.Y < 1 || me.Y - 1 >= barItems.Children.Length) {
  728. return true;
  729. }
  730. var item = barItems.Children [me.Y - 1];
  731. if (item == null || !item.IsEnabled ()) disabled = true;
  732. if (item != null && !disabled)
  733. current = me.Y - 1;
  734. CheckSubMenu ();
  735. return true;
  736. }
  737. return false;
  738. }
  739. internal void CheckSubMenu ()
  740. {
  741. if (current == -1 || barItems.Children [current] == null) {
  742. return;
  743. }
  744. var subMenu = barItems.SubMenu (barItems.Children [current]);
  745. if (subMenu != null) {
  746. int pos = -1;
  747. if (host.openSubMenu != null) {
  748. pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
  749. }
  750. if (pos == -1 && this != host.openCurrentMenu && subMenu.Children != host.openCurrentMenu.barItems.Children) {
  751. host.CloseMenu (false, true);
  752. }
  753. host.Activate (host.selected, pos, subMenu);
  754. } else if (host.openSubMenu?.Last ().barItems.IsSubMenuOf (barItems.Children [current]) == false) {
  755. host.CloseMenu (false, true);
  756. } else {
  757. SetNeedsDisplay ();
  758. }
  759. }
  760. int GetSubMenuIndex (MenuBarItem subMenu)
  761. {
  762. int pos = -1;
  763. if (this != null && Subviews.Count > 0) {
  764. Menu v = null;
  765. foreach (var menu in Subviews) {
  766. if (((Menu)menu).barItems == subMenu)
  767. v = (Menu)menu;
  768. }
  769. if (v != null)
  770. pos = Subviews.IndexOf (v);
  771. }
  772. return pos;
  773. }
  774. }
  775. /// <summary>
  776. /// The MenuBar provides a menu for Terminal.Gui applications.
  777. /// </summary>
  778. /// <remarks>
  779. /// <para>
  780. /// The <see cref="MenuBar"/> appears on the first row of the terminal.
  781. /// </para>
  782. /// <para>
  783. /// The <see cref="MenuBar"/> provides global hotkeys for the application.
  784. /// </para>
  785. /// </remarks>
  786. public class MenuBar : View {
  787. /// <summary>
  788. /// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this when the <see cref="MenuBar"/> is vislble.
  789. /// </summary>
  790. /// <value>The menu array.</value>
  791. public MenuBarItem [] Menus { get; set; }
  792. internal int selected;
  793. internal int selectedSub;
  794. Action action;
  795. /// <summary>
  796. /// Used for change the navigation key style.
  797. /// </summary>
  798. public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
  799. /// <summary>
  800. /// Used for change the shortcut delimiter separator.
  801. /// </summary>
  802. public static ustring ShortCutDelimiter { get; set; } = "+";
  803. /// <summary>
  804. /// Initializes a new instance of the <see cref="MenuBar"/>.
  805. /// </summary>
  806. public MenuBar () : this (new MenuBarItem [] { }) { }
  807. /// <summary>
  808. /// Initializes a new instance of the <see cref="MenuBar"/> class with the specified set of toplevel menu items.
  809. /// </summary>
  810. /// <param name="menus">Individual menu items; a null item will result in a separator being drawn.</param>
  811. public MenuBar (MenuBarItem [] menus) : base ()
  812. {
  813. X = 0;
  814. Y = 0;
  815. Width = Dim.Fill ();
  816. Height = 1;
  817. Menus = menus;
  818. //CanFocus = true;
  819. selected = -1;
  820. selectedSub = -1;
  821. ColorScheme = Colors.Menu;
  822. WantMousePositionReports = true;
  823. IsMenuOpen = false;
  824. }
  825. bool openedByAltKey;
  826. bool isCleaning;
  827. ///<inheritdoc/>
  828. public override bool OnLeave (View view)
  829. {
  830. if ((!(view is MenuBar) && !(view is Menu) || !(view is MenuBar) && !(view is Menu) && openMenu != null) && !isCleaning && !reopen) {
  831. CleanUp ();
  832. return true;
  833. }
  834. return false;
  835. }
  836. ///<inheritdoc/>
  837. public override bool OnKeyDown (KeyEvent keyEvent)
  838. {
  839. if (keyEvent.IsAlt) {
  840. openedByAltKey = true;
  841. SetNeedsDisplay ();
  842. openedByHotKey = false;
  843. }
  844. return false;
  845. }
  846. ///<inheritdoc/>
  847. public override bool OnKeyUp (KeyEvent keyEvent)
  848. {
  849. if (keyEvent.IsAlt) {
  850. // User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F)
  851. if (!keyEvent.IsCtrl && openedByAltKey && !IsMenuOpen && openMenu == null && ((uint)keyEvent.Key & (uint)Key.CharMask) == 0) {
  852. // There's no open menu, the first menu item should be highlight.
  853. // The right way to do this is to SetFocus(MenuBar), but for some reason
  854. // that faults.
  855. //Activate (0);
  856. //StartMenu ();
  857. IsMenuOpen = true;
  858. selected = 0;
  859. CanFocus = true;
  860. lastFocused = SuperView.MostFocused;
  861. SetFocus ();
  862. SetNeedsDisplay ();
  863. Application.GrabMouse (this);
  864. } else if (!openedByHotKey) {
  865. // There's an open menu. If this Alt key-up is a pre-cursor to an accelerator
  866. // we don't want to close the menu because it'll flash.
  867. // How to deal with that?
  868. CleanUp ();
  869. }
  870. return true;
  871. }
  872. return false;
  873. }
  874. internal void CleanUp ()
  875. {
  876. isCleaning = true;
  877. if (openMenu != null) {
  878. CloseAllMenus ();
  879. }
  880. openedByAltKey = false;
  881. IsMenuOpen = false;
  882. selected = -1;
  883. CanFocus = false;
  884. if (lastFocused != null) {
  885. lastFocused.SetFocus ();
  886. }
  887. SetNeedsDisplay ();
  888. Application.UngrabMouse ();
  889. isCleaning = false;
  890. }
  891. ///<inheritdoc/>
  892. public override void Redraw (Rect bounds)
  893. {
  894. Move (0, 0);
  895. Driver.SetAttribute (Colors.Menu.Normal);
  896. for (int i = 0; i < Frame.Width; i++)
  897. Driver.AddRune (' ');
  898. Move (1, 0);
  899. int pos = 1;
  900. for (int i = 0; i < Menus.Length; i++) {
  901. var menu = Menus [i];
  902. Move (pos, 0);
  903. Attribute hotColor, normalColor;
  904. if (i == selected) {
  905. hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
  906. normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
  907. } else if (openedByAltKey) {
  908. hotColor = ColorScheme.HotNormal;
  909. normalColor = ColorScheme.Normal;
  910. } else {
  911. hotColor = ColorScheme.Normal;
  912. normalColor = ColorScheme.Normal;
  913. }
  914. DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} {menu.Help} ", hotColor, normalColor);
  915. pos += 1 + menu.TitleLength + (menu.Help.Length > 0 ? menu.Help.Length + 2 : 0) + 2;
  916. }
  917. PositionCursor ();
  918. }
  919. ///<inheritdoc/>
  920. public override void PositionCursor ()
  921. {
  922. int pos = 0;
  923. for (int i = 0; i < Menus.Length; i++) {
  924. if (i == selected) {
  925. pos++;
  926. if (IsMenuOpen)
  927. Move (pos + 1, 0);
  928. else {
  929. Move (pos + 1, 0);
  930. }
  931. return;
  932. } else if (IsMenuOpen) {
  933. pos += 1 + Menus [i].TitleLength + (Menus [i].Help.Length > 0 ? Menus [i].Help.Length + 2 : 0) + 2;
  934. } else {
  935. pos += 2 + Menus [i].TitleLength + (Menus [i].Help.Length > 0 ? Menus [i].Help.Length + 2 : 0) + 1;
  936. }
  937. }
  938. //Move (0, 0);
  939. }
  940. void Selected (MenuItem item)
  941. {
  942. // TODO: Running = false;
  943. action = item.Action;
  944. }
  945. /// <summary>
  946. /// Raised as a menu is opening.
  947. /// </summary>
  948. public event Action MenuOpening;
  949. /// <summary>
  950. /// Raised when a menu is closing.
  951. /// </summary>
  952. public event Action MenuClosing;
  953. internal Menu openMenu;
  954. internal Menu openCurrentMenu;
  955. internal List<Menu> openSubMenu;
  956. View previousFocused;
  957. internal bool isMenuOpening;
  958. internal bool isMenuClosing;
  959. /// <summary>
  960. /// True if the menu is open; otherwise false.
  961. /// </summary>
  962. public bool IsMenuOpen { get; protected set; }
  963. /// <summary>
  964. /// Virtual method that will invoke the <see cref="MenuOpening"/>
  965. /// </summary>
  966. public virtual void OnMenuOpening ()
  967. {
  968. MenuOpening?.Invoke ();
  969. }
  970. /// <summary>
  971. /// Virtual method that will invoke the <see cref="MenuClosing"/>
  972. /// </summary>
  973. public virtual void OnMenuClosing ()
  974. {
  975. MenuClosing?.Invoke ();
  976. }
  977. View lastFocused;
  978. /// <summary>
  979. /// Get the lasted focused view before open the menu.
  980. /// </summary>
  981. public View LastFocused { get; private set; }
  982. internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
  983. {
  984. isMenuOpening = true;
  985. OnMenuOpening ();
  986. int pos = 0;
  987. switch (subMenu) {
  988. case null:
  989. lastFocused = lastFocused ?? SuperView.MostFocused;
  990. if (openSubMenu != null)
  991. CloseMenu (false, true);
  992. if (openMenu != null) {
  993. SuperView.Remove (openMenu);
  994. openMenu.Dispose ();
  995. }
  996. for (int i = 0; i < index; i++)
  997. pos += Menus [i].Title.RuneCount + (Menus [i].Help.RuneCount > 0 ? Menus [i].Help.RuneCount + 2 : 0) + 2;
  998. openMenu = new Menu (this, pos, 1, Menus [index]);
  999. openCurrentMenu = openMenu;
  1000. openCurrentMenu.previousSubFocused = openMenu;
  1001. SuperView.Add (openMenu);
  1002. openMenu.SetFocus ();
  1003. break;
  1004. default:
  1005. if (openSubMenu == null)
  1006. openSubMenu = new List<Menu> ();
  1007. if (sIndex > -1) {
  1008. RemoveSubMenu (sIndex);
  1009. } else {
  1010. var last = openSubMenu.Count > 0 ? openSubMenu.Last () : openMenu;
  1011. openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width, last.Frame.Top + 1 + last.current, subMenu);
  1012. openCurrentMenu.previousSubFocused = last.previousSubFocused;
  1013. openSubMenu.Add (openCurrentMenu);
  1014. SuperView.Add (openCurrentMenu);
  1015. }
  1016. selectedSub = openSubMenu.Count - 1;
  1017. if (selectedSub > -1 && SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
  1018. openCurrentMenu.SetFocus ();
  1019. }
  1020. break;
  1021. }
  1022. isMenuOpening = false;
  1023. IsMenuOpen = true;
  1024. }
  1025. /// <summary>
  1026. /// Opens the current Menu programatically.
  1027. /// </summary>
  1028. public void OpenMenu ()
  1029. {
  1030. if (openMenu != null)
  1031. return;
  1032. selected = 0;
  1033. SetNeedsDisplay ();
  1034. previousFocused = SuperView.Focused;
  1035. OpenMenu (selected);
  1036. if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
  1037. CloseMenu ();
  1038. }
  1039. openCurrentMenu.CheckSubMenu ();
  1040. Application.GrabMouse (this);
  1041. }
  1042. // Activates the menu, handles either first focus, or activating an entry when it was already active
  1043. // For mouse events.
  1044. internal void Activate (int idx, int sIdx = -1, MenuBarItem subMenu = null)
  1045. {
  1046. selected = idx;
  1047. selectedSub = sIdx;
  1048. if (openMenu == null)
  1049. previousFocused = SuperView.Focused;
  1050. OpenMenu (idx, sIdx, subMenu);
  1051. if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
  1052. if (subMenu == null) {
  1053. CloseMenu ();
  1054. }
  1055. }
  1056. SetNeedsDisplay ();
  1057. }
  1058. internal bool SelectEnabledItem (IEnumerable<MenuItem> chldren, int current, out int newCurrent, bool forward = true)
  1059. {
  1060. if (chldren == null) {
  1061. newCurrent = -1;
  1062. return true;
  1063. }
  1064. IEnumerable<MenuItem> childrens;
  1065. if (forward) {
  1066. childrens = chldren;
  1067. } else {
  1068. childrens = chldren.Reverse ();
  1069. }
  1070. int count;
  1071. if (forward) {
  1072. count = -1;
  1073. } else {
  1074. count = childrens.Count ();
  1075. }
  1076. foreach (var child in childrens) {
  1077. if (forward) {
  1078. if (++count < current) {
  1079. continue;
  1080. }
  1081. } else {
  1082. if (--count > current) {
  1083. continue;
  1084. }
  1085. }
  1086. if (child == null || !child.IsEnabled ()) {
  1087. if (forward) {
  1088. current++;
  1089. } else {
  1090. current--;
  1091. }
  1092. } else {
  1093. newCurrent = current;
  1094. return true;
  1095. }
  1096. }
  1097. newCurrent = -1;
  1098. return false;
  1099. }
  1100. /// <summary>
  1101. /// Closes the current Menu programatically, if open.
  1102. /// </summary>
  1103. public void CloseMenu ()
  1104. {
  1105. CloseMenu (false, false);
  1106. }
  1107. bool reopen;
  1108. internal void CloseMenu (bool reopen = false, bool isSubMenu = false)
  1109. {
  1110. isMenuClosing = true;
  1111. this.reopen = reopen;
  1112. OnMenuClosing ();
  1113. switch (isSubMenu) {
  1114. case false:
  1115. if (openMenu != null) {
  1116. SuperView?.Remove (openMenu);
  1117. }
  1118. SetNeedsDisplay ();
  1119. if (previousFocused != null && previousFocused is Menu && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ())
  1120. previousFocused.SetFocus ();
  1121. openMenu?.Dispose ();
  1122. openMenu = null;
  1123. if (lastFocused is Menu || lastFocused is MenuBar) {
  1124. lastFocused = null;
  1125. }
  1126. LastFocused = lastFocused;
  1127. lastFocused = null;
  1128. if (LastFocused != null) {
  1129. if (!reopen) {
  1130. selected = -1;
  1131. }
  1132. LastFocused.SetFocus ();
  1133. } else {
  1134. SetFocus ();
  1135. PositionCursor ();
  1136. }
  1137. IsMenuOpen = false;
  1138. break;
  1139. case true:
  1140. selectedSub = -1;
  1141. SetNeedsDisplay ();
  1142. RemoveAllOpensSubMenus ();
  1143. openCurrentMenu.previousSubFocused.SetFocus ();
  1144. openSubMenu = null;
  1145. IsMenuOpen = true;
  1146. break;
  1147. }
  1148. this.reopen = false;
  1149. isMenuClosing = false;
  1150. }
  1151. void RemoveSubMenu (int index)
  1152. {
  1153. if (openSubMenu == null)
  1154. return;
  1155. for (int i = openSubMenu.Count - 1; i > index; i--) {
  1156. isMenuClosing = true;
  1157. if (openSubMenu.Count - 1 > 0)
  1158. openSubMenu [i - 1].SetFocus ();
  1159. else
  1160. openMenu.SetFocus ();
  1161. if (openSubMenu != null) {
  1162. var menu = openSubMenu [i];
  1163. SuperView.Remove (menu);
  1164. openSubMenu.Remove (menu);
  1165. menu.Dispose ();
  1166. }
  1167. RemoveSubMenu (i);
  1168. }
  1169. if (openSubMenu.Count > 0)
  1170. openCurrentMenu = openSubMenu.Last ();
  1171. //if (openMenu.Subviews.Count == 0)
  1172. // return;
  1173. //if (index == 0) {
  1174. // //SuperView.SetFocus (previousSubFocused);
  1175. // FocusPrev ();
  1176. // return;
  1177. //}
  1178. //for (int i = openMenu.Subviews.Count - 1; i > index; i--) {
  1179. // isMenuClosing = true;
  1180. // if (openMenu.Subviews.Count - 1 > 0)
  1181. // SuperView.SetFocus (openMenu.Subviews [i - 1]);
  1182. // else
  1183. // SuperView.SetFocus (openMenu);
  1184. // if (openMenu != null) {
  1185. // Remove (openMenu.Subviews [i]);
  1186. // openMenu.Remove (openMenu.Subviews [i]);
  1187. // }
  1188. // RemoveSubMenu (i);
  1189. //}
  1190. isMenuClosing = false;
  1191. }
  1192. internal void RemoveAllOpensSubMenus ()
  1193. {
  1194. if (openSubMenu != null) {
  1195. foreach (var item in openSubMenu) {
  1196. SuperView.Remove (item);
  1197. item.Dispose ();
  1198. }
  1199. }
  1200. }
  1201. internal void CloseAllMenus ()
  1202. {
  1203. if (!isMenuOpening && !isMenuClosing) {
  1204. if (openSubMenu != null)
  1205. CloseMenu (false, true);
  1206. CloseMenu ();
  1207. if (LastFocused != null && LastFocused != this)
  1208. selected = -1;
  1209. }
  1210. IsMenuOpen = false;
  1211. openedByHotKey = false;
  1212. openedByAltKey = false;
  1213. }
  1214. View FindDeepestMenu (View view, ref int count)
  1215. {
  1216. count = count > 0 ? count : 0;
  1217. foreach (var menu in view.Subviews) {
  1218. if (menu is Menu) {
  1219. count++;
  1220. return FindDeepestMenu ((Menu)menu, ref count);
  1221. }
  1222. }
  1223. return view;
  1224. }
  1225. internal void PreviousMenu (bool isSubMenu = false)
  1226. {
  1227. switch (isSubMenu) {
  1228. case false:
  1229. if (selected <= 0)
  1230. selected = Menus.Length - 1;
  1231. else
  1232. selected--;
  1233. if (selected > -1)
  1234. CloseMenu (true, false);
  1235. OpenMenu (selected);
  1236. if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current, false)) {
  1237. openCurrentMenu.current = 0;
  1238. if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
  1239. CloseMenu ();
  1240. }
  1241. break;
  1242. }
  1243. break;
  1244. case true:
  1245. if (selectedSub > -1) {
  1246. selectedSub--;
  1247. RemoveSubMenu (selectedSub);
  1248. SetNeedsDisplay ();
  1249. } else
  1250. PreviousMenu ();
  1251. break;
  1252. }
  1253. }
  1254. internal void NextMenu (bool isSubMenu = false)
  1255. {
  1256. switch (isSubMenu) {
  1257. case false:
  1258. if (selected == -1)
  1259. selected = 0;
  1260. else if (selected + 1 == Menus.Length)
  1261. selected = 0;
  1262. else
  1263. selected++;
  1264. if (selected > -1)
  1265. CloseMenu (true);
  1266. OpenMenu (selected);
  1267. SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current);
  1268. break;
  1269. case true:
  1270. if (UseKeysUpDownAsKeysLeftRight) {
  1271. CloseMenu (false, true);
  1272. NextMenu ();
  1273. } else {
  1274. var subMenu = openCurrentMenu.barItems.SubMenu (openCurrentMenu.barItems.Children [openCurrentMenu.current]);
  1275. if ((selectedSub == -1 || openSubMenu == null || openSubMenu?.Count == selectedSub) && subMenu == null) {
  1276. if (openSubMenu != null)
  1277. CloseMenu (false, true);
  1278. NextMenu ();
  1279. } else if (subMenu != null ||
  1280. !openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)
  1281. selectedSub++;
  1282. else
  1283. return;
  1284. SetNeedsDisplay ();
  1285. openCurrentMenu.CheckSubMenu ();
  1286. }
  1287. break;
  1288. }
  1289. }
  1290. bool openedByHotKey;
  1291. internal bool FindAndOpenMenuByHotkey (KeyEvent kb)
  1292. {
  1293. //int pos = 0;
  1294. var c = ((uint)kb.Key & (uint)Key.CharMask);
  1295. for (int i = 0; i < Menus.Length; i++) {
  1296. // TODO: this code is duplicated, hotkey should be part of the MenuBarItem
  1297. var mi = Menus [i];
  1298. int p = mi.Title.IndexOf ('_');
  1299. if (p != -1 && p + 1 < mi.Title.RuneCount) {
  1300. if (Char.ToUpperInvariant ((char)mi.Title [p + 1]) == c) {
  1301. ProcessMenu (i, mi);
  1302. return true;
  1303. }
  1304. }
  1305. }
  1306. return false;
  1307. }
  1308. internal bool FindAndOpenMenuByShortCut (KeyEvent kb, MenuItem [] children = null)
  1309. {
  1310. if (children == null) {
  1311. children = Menus;
  1312. }
  1313. var key = kb.KeyValue;
  1314. var keys = GetModifiersKey (kb);
  1315. key |= (int)keys;
  1316. //if (kb.IsShift) {
  1317. // key |= (int)Key.ShiftMask;
  1318. //}
  1319. //if (kb.IsAlt) {
  1320. // key |= unchecked((int)Key.AltMask);
  1321. //}
  1322. for (int i = 0; i < children.Length; i++) {
  1323. var mi = children [i];
  1324. if (mi == null) {
  1325. continue;
  1326. }
  1327. if ((!(mi is MenuBarItem mbiTopLevel) || mbiTopLevel.IsTopLevel) && mi.ShortCut != Key.ControlSpace && mi.ShortCut == (Key)key) {
  1328. var action = mi.Action;
  1329. if (action != null) {
  1330. Application.MainLoop.AddIdle (() => {
  1331. action ();
  1332. return false;
  1333. });
  1334. }
  1335. return true;
  1336. }
  1337. if (mi is MenuBarItem menuBarItem && !menuBarItem.IsTopLevel && FindAndOpenMenuByShortCut (kb, menuBarItem.Children)) {
  1338. return true;
  1339. }
  1340. }
  1341. return false;
  1342. }
  1343. private void ProcessMenu (int i, MenuBarItem mi)
  1344. {
  1345. if (mi.IsTopLevel) {
  1346. var menu = new Menu (this, i, 0, mi);
  1347. menu.Run (mi.Action);
  1348. menu.Dispose ();
  1349. } else {
  1350. openedByHotKey = true;
  1351. Application.GrabMouse (this);
  1352. selected = i;
  1353. OpenMenu (i);
  1354. if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
  1355. CloseMenu ();
  1356. }
  1357. openCurrentMenu.CheckSubMenu ();
  1358. }
  1359. }
  1360. ///<inheritdoc/>
  1361. public override bool ProcessHotKey (KeyEvent kb)
  1362. {
  1363. if (kb.Key == Key.F9) {
  1364. if (!IsMenuOpen)
  1365. OpenMenu ();
  1366. else
  1367. CloseAllMenus ();
  1368. return true;
  1369. }
  1370. // To ncurses simulate a AltMask key pressing Alt+Space because
  1371. // it can�t detect an alone special key down was pressed.
  1372. if (kb.IsAlt && kb.Key == Key.AltMask && openMenu == null) {
  1373. OnKeyDown (kb);
  1374. OnKeyUp (kb);
  1375. return true;
  1376. } else if (kb.IsAlt && !kb.IsCtrl && !kb.IsShift) {
  1377. if (FindAndOpenMenuByHotkey (kb)) return true;
  1378. }
  1379. //var kc = kb.KeyValue;
  1380. return base.ProcessHotKey (kb);
  1381. }
  1382. ///<inheritdoc/>
  1383. public override bool ProcessKey (KeyEvent kb)
  1384. {
  1385. switch (kb.Key) {
  1386. case Key.CursorLeft:
  1387. selected--;
  1388. if (selected < 0)
  1389. selected = Menus.Length - 1;
  1390. break;
  1391. case Key.CursorRight:
  1392. selected = (selected + 1) % Menus.Length;
  1393. break;
  1394. case Key.Esc:
  1395. case Key.ControlC:
  1396. //TODO: Running = false;
  1397. CloseMenu ();
  1398. if (openedByAltKey) {
  1399. openedByAltKey = false;
  1400. LastFocused.SetFocus ();
  1401. }
  1402. break;
  1403. case Key.CursorDown:
  1404. case Key.Enter:
  1405. if (selected > -1) {
  1406. ProcessMenu (selected, Menus [selected]);
  1407. }
  1408. break;
  1409. default:
  1410. var key = kb.KeyValue;
  1411. if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
  1412. char c = Char.ToUpper ((char)key);
  1413. if (selected == -1 || Menus [selected].IsTopLevel)
  1414. return false;
  1415. foreach (var mi in Menus [selected].Children) {
  1416. if (mi == null)
  1417. continue;
  1418. int p = mi.Title.IndexOf ('_');
  1419. if (p != -1 && p + 1 < mi.Title.RuneCount) {
  1420. if (mi.Title [p + 1] == c) {
  1421. Selected (mi);
  1422. return true;
  1423. }
  1424. }
  1425. }
  1426. }
  1427. return false;
  1428. }
  1429. SetNeedsDisplay ();
  1430. return true;
  1431. }
  1432. ///<inheritdoc/>
  1433. public override bool ProcessColdKey (KeyEvent kb)
  1434. {
  1435. return FindAndOpenMenuByShortCut (kb);
  1436. }
  1437. ///<inheritdoc/>
  1438. public override bool MouseEvent (MouseEvent me)
  1439. {
  1440. if (!handled && !HandleGrabView (me, this)) {
  1441. return false;
  1442. }
  1443. handled = false;
  1444. if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked ||
  1445. (me.Flags == MouseFlags.ReportMousePosition && selected > -1) ||
  1446. (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) {
  1447. int pos = 1;
  1448. int cx = me.X;
  1449. for (int i = 0; i < Menus.Length; i++) {
  1450. if (cx >= pos && cx < pos + 1 + Menus [i].TitleLength + Menus [i].Help.RuneCount + 2) {
  1451. if (me.Flags == MouseFlags.Button1Clicked) {
  1452. if (Menus [i].IsTopLevel) {
  1453. var menu = new Menu (this, i, 0, Menus [i]);
  1454. menu.Run (Menus [i].Action);
  1455. menu.Dispose ();
  1456. }
  1457. } else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) {
  1458. if (IsMenuOpen && !Menus [i].IsTopLevel) {
  1459. CloseAllMenus ();
  1460. } else if (!Menus [i].IsTopLevel) {
  1461. Activate (i);
  1462. }
  1463. } else if (selected != i && selected > -1 && (me.Flags == MouseFlags.ReportMousePosition ||
  1464. me.Flags == MouseFlags.Button1Pressed && me.Flags == MouseFlags.ReportMousePosition)) {
  1465. if (IsMenuOpen) {
  1466. CloseMenu (true, false);
  1467. Activate (i);
  1468. }
  1469. } else {
  1470. if (IsMenuOpen)
  1471. Activate (i);
  1472. }
  1473. return true;
  1474. }
  1475. pos += 1 + Menus [i].TitleLength + 2;
  1476. }
  1477. }
  1478. return false;
  1479. }
  1480. internal bool handled;
  1481. internal bool HandleGrabView (MouseEvent me, View current)
  1482. {
  1483. if (Application.mouseGrabView != null) {
  1484. if (me.View is MenuBar || me.View is Menu) {
  1485. if (me.View != current) {
  1486. Application.UngrabMouse ();
  1487. var v = me.View;
  1488. Application.GrabMouse (v);
  1489. var newxy = v.ScreenToView (me.X, me.Y);
  1490. var nme = new MouseEvent () {
  1491. X = newxy.X,
  1492. Y = newxy.Y,
  1493. Flags = me.Flags,
  1494. OfX = me.X - newxy.X,
  1495. OfY = me.Y - newxy.Y,
  1496. View = v
  1497. };
  1498. v.MouseEvent (nme);
  1499. return false;
  1500. }
  1501. } else if (!(me.View is MenuBar || me.View is Menu) && (me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
  1502. me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked)) {
  1503. Application.UngrabMouse ();
  1504. CloseAllMenus ();
  1505. handled = false;
  1506. return false;
  1507. } else {
  1508. handled = false;
  1509. return false;
  1510. }
  1511. } else if (!IsMenuOpen && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
  1512. Application.GrabMouse (current);
  1513. } else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu)) {
  1514. Application.GrabMouse (me.View);
  1515. } else {
  1516. handled = false;
  1517. return false;
  1518. }
  1519. //if (me.View != this && me.Flags != MouseFlags.Button1Pressed)
  1520. // return true;
  1521. //else if (me.View != this && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
  1522. // Application.UngrabMouse ();
  1523. // host.CloseAllMenus ();
  1524. // return true;
  1525. //}
  1526. //if (!(me.View is MenuBar) && !(me.View is Menu) && me.Flags != MouseFlags.Button1Pressed))
  1527. // return false;
  1528. //if (Application.mouseGrabView != null) {
  1529. // if (me.View is MenuBar || me.View is Menu) {
  1530. // me.X -= me.OfX;
  1531. // me.Y -= me.OfY;
  1532. // me.View.MouseEvent (me);
  1533. // return true;
  1534. // } else if (!(me.View is MenuBar || me.View is Menu) && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
  1535. // Application.UngrabMouse ();
  1536. // CloseAllMenus ();
  1537. // }
  1538. //} else if (!isMenuClosed && selected == -1 && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
  1539. // Application.GrabMouse (this);
  1540. // return true;
  1541. //}
  1542. //if (Application.mouseGrabView != null) {
  1543. // if (Application.mouseGrabView == me.View && me.View == current) {
  1544. // me.X -= me.OfX;
  1545. // me.Y -= me.OfY;
  1546. // } else if (me.View != current && me.View is MenuBar && me.View is Menu) {
  1547. // Application.UngrabMouse ();
  1548. // Application.GrabMouse (me.View);
  1549. // } else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
  1550. // Application.UngrabMouse ();
  1551. // CloseMenu ();
  1552. // }
  1553. //} else if ((!isMenuClosed && selected > -1)) {
  1554. // Application.GrabMouse (current);
  1555. //}
  1556. handled = true;
  1557. return true;
  1558. }
  1559. }
  1560. }