// // StatusBar.cs: a statusbar for an application // // Authors: // Miguel de Icaza (miguel@gnome.org) // // TODO: // Add mouse support using System; using System.Collections.Generic; using System.Text; namespace Terminal.Gui { /// /// objects are contained by s. /// Each has a title, a shortcut (hotkey), and an that will be invoked when the /// is pressed. /// The will be a global hotkey for the application in the current context of the screen. /// The colour of the will be changed after each ~. /// A set to `~F1~ Help` will render as *F1* using and /// *Help* as . /// public class StatusItem { /// /// Initializes a new . /// /// Shortcut to activate the . /// Title for the . /// Action to invoke when the is activated. /// Function to determine if the action can currently be executed. public StatusItem (Key shortcut, string title, Action action, Func canExecute = null) { Title = title ?? ""; Shortcut = shortcut; Action = action; CanExecute = canExecute; } /// /// Gets the global shortcut to invoke the action on the menu. /// public Key Shortcut { get; set; } /// /// Gets or sets the title. /// /// The title. /// /// The colour of the will be changed after each ~. /// A set to `~F1~ Help` will render as *F1* using and /// *Help* as . /// public string Title { get; set; } /// /// Gets or sets the action to be invoked when the statusbar item is triggered /// /// Action to invoke. public Action Action { get; set; } /// /// Gets or sets the action to be invoked to determine if the can be triggered. /// If returns the status item will be enabled. Otherwise, it will be disabled. /// /// Function to determine if the action is can be executed or not. public Func CanExecute { get; set; } /// /// Returns if the status item is enabled. This method is a wrapper around . /// public bool IsEnabled () { return CanExecute == null ? true : CanExecute (); } /// /// Gets or sets arbitrary data for the status item. /// /// This property is not used internally. public object Data { get; set; } }; /// /// A status bar is a that snaps to the bottom of a displaying set of s. /// The should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will /// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. /// So for each context must be a new instance of a statusbar. /// public class StatusBar : View { /// /// The items that compose the /// public StatusItem [] Items { get; set; } /// /// Initializes a new instance of the class. /// public StatusBar () : this (items: new StatusItem [] { }) { } /// /// Initializes a new instance of the class with the specified set of s. /// The will be drawn on the lowest line of the terminal or (if not null). /// /// A list of statusbar items. public StatusBar (StatusItem [] items) : base () { Items = items; CanFocus = false; ColorScheme = Colors.Menu; X = 0; Y = Pos.AnchorEnd (1); Width = Dim.Fill (); Height = 1; } static string shortcutDelimiter = "-"; /// /// Used for change the shortcut delimiter separator. /// public static string ShortcutDelimiter { get => shortcutDelimiter; set { if (shortcutDelimiter != value) { shortcutDelimiter = value == string.Empty ? " " : value; } } } Attribute ToggleScheme (Attribute scheme) { var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal; Driver.SetAttribute (result); return result; } Attribute DetermineColorSchemeFor (StatusItem item) { if (item != null) { if (item.IsEnabled ()) { return GetNormalColor (); } return ColorScheme.Disabled; } return GetNormalColor (); } /// public override void OnDrawContent (Rect contentArea) { Move (0, 0); Driver.SetAttribute (GetNormalColor ()); for (int i = 0; i < Frame.Width; i++) { Driver.AddRune ((Rune)' '); } Move (1, 0); var scheme = GetNormalColor (); Driver.SetAttribute (scheme); for (int i = 0; i < Items.Length; i++) { var title = Items [i].Title; Driver.SetAttribute (DetermineColorSchemeFor (Items [i])); for (int n = 0; n < Items [i].Title.GetRuneCount (); n++) { if (title [n] == '~') { if (Items [i].IsEnabled ()) { scheme = ToggleScheme (scheme); } continue; } Driver.AddRune ((Rune)title [n]); } if (i + 1 < Items.Length) { Driver.AddRune ((Rune)' '); Driver.AddRune (CM.Glyphs.VLine); Driver.AddRune ((Rune)' '); } } } /// public override bool ProcessHotKey (KeyEvent kb) { foreach (var item in Items) { if (kb.Key == item.Shortcut) { if (item.IsEnabled ()) { Run (item.Action); } return true; } } return false; } /// public override bool MouseEvent (MouseEvent me) { if (me.Flags != MouseFlags.Button1Clicked) return false; int pos = 1; for (int i = 0; i < Items.Length; i++) { if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) { var item = Items [i]; if (item.IsEnabled ()) { Run (item.Action); } break; } pos += GetItemTitleLength (Items [i].Title) + 3; } return true; } int GetItemTitleLength (string title) { int len = 0; foreach (var ch in title) { if (ch == '~') continue; len++; } return len; } void Run (Action action) { if (action == null) return; Application.MainLoop.AddIdle (() => { action (); return false; }); } /// public override bool OnEnter (View view) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); return base.OnEnter (view); } /// /// Inserts a in the specified index of . /// /// The zero-based index at which item should be inserted. /// The item to insert. public void AddItemAt (int index, StatusItem item) { var itemsList = new List (Items); itemsList.Insert (index, item); Items = itemsList.ToArray (); SetNeedsDisplay (); } /// /// Removes a at specified index of . /// /// The zero-based index of the item to remove. /// The removed. public StatusItem RemoveItem (int index) { var itemsList = new List (Items); var item = itemsList [index]; itemsList.RemoveAt (index); Items = itemsList.ToArray (); SetNeedsDisplay (); return item; } } }