// // Button.cs: Button control // // Authors: // Miguel de Icaza (miguel@gnome.org) // using System; using NStack; namespace Terminal.Gui { /// /// Button is a that provides an item that invokes an when activated by the user. /// /// /// /// Provides a button showing text invokes an when clicked on with a mouse /// or when the user presses SPACE, ENTER, or hotkey. The hotkey is the first letter or digit following the first underscore ('_') /// in the button text. /// /// /// Use to change the hotkey specifier from the default of ('_'). /// /// /// If no hotkey specifier is found, the first uppercase letter encountered will be used as the hotkey. /// /// /// When the button is configured as the default () and the user presses /// the ENTER key, if no other processes the , the 's /// will be invoked. /// /// public class Button : View { ustring text; bool is_default; /// /// Initializes a new instance of using layout. /// /// /// The width of the is computed based on the /// text length. The height will always be 1. /// public Button () : this (text: string.Empty, is_default: false) { } /// /// Initializes a new instance of using layout. /// /// /// The width of the is computed based on the /// text length. The height will always be 1. /// /// The button's text /// /// If true, a special decoration is used, and the user pressing the enter key /// in a will implicitly activate this button. /// public Button (ustring text, bool is_default = false) : base (text) { Initialize (text, is_default); } /// /// Initializes a new instance of using layout, based on the given text /// /// /// The width of the is computed based on the /// text length. The height will always be 1. /// /// X position where the button will be shown. /// Y position where the button will be shown. /// The button's text public Button (int x, int y, ustring text) : this (x, y, text, false) { } /// /// Initializes a new instance of using layout, based on the given text. /// /// /// The width of the is computed based on the /// text length. The height will always be 1. /// /// X position where the button will be shown. /// Y position where the button will be shown. /// The button's text /// /// If true, a special decoration is used, and the user pressing the enter key /// in a will implicitly activate this button. /// public Button (int x, int y, ustring text, bool is_default) : base (new Rect (x, y, text.RuneCount + 4 + (is_default ? 2 : 0), 1), text) { Initialize (text, is_default); } Rune _leftBracket; Rune _rightBracket; Rune _leftDefault; Rune _rightDefault; private Key hotKey = Key.Null; private Rune hotKeySpecifier; void Initialize (ustring text, bool is_default) { TextAlignment = TextAlignment.Centered; HotKeySpecifier = new Rune ('_'); _leftBracket = new Rune (Driver != null ? Driver.LeftBracket : '['); _rightBracket = new Rune (Driver != null ? Driver.RightBracket : ']'); _leftDefault = new Rune (Driver != null ? Driver.LeftDefaultIndicator : '<'); _rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>'); CanFocus = true; AutoSize = true; this.is_default = is_default; this.text = text ?? string.Empty; Update (); // Things this view knows how to do AddCommand (Command.Accept, () => AcceptKey ()); // Default keybindings for this view AddKeyBinding (Key.Enter, Command.Accept); AddKeyBinding (Key.Space, Command.Accept); if (HotKey != Key.Null) { AddKeyBinding (Key.Space | HotKey, Command.Accept); } } /// > public override ustring Text { get { return text; } set { text = value; TextFormatter.FindHotKey (text, HotKeySpecifier, true, out _, out Key hk); if (hotKey != hk) { HotKey = hk; } Update (); } } /// /// Gets or sets whether the is the default action to activate in a dialog. /// /// true if is default; otherwise, false. public bool IsDefault { get => is_default; set { is_default = value; Update (); } } /// public override Key HotKey { get => hotKey; set { if (hotKey != value) { var v = value == Key.Unknown ? Key.Null : value; if (hotKey != Key.Null && ContainsKeyBinding (Key.Space | hotKey)) { if (v == Key.Null) { ClearKeybinding (Key.Space | hotKey); } else { ReplaceKeyBinding (Key.Space | hotKey, Key.Space | v); } } else if (v != Key.Null) { AddKeyBinding (Key.Space | v, Command.Accept); } hotKey = v; } } } /// public override Rune HotKeySpecifier { get => hotKeySpecifier; set { hotKeySpecifier = TextFormatter.HotKeySpecifier = value; } } /// public override bool AutoSize { get => base.AutoSize; set { base.AutoSize = value; Update (); } } internal void Update () { if (IsDefault) TextFormatter.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket); else TextFormatter.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket); int w = TextFormatter.Size.Width - GetHotKeySpecifierLength (); GetCurrentWidth (out int cWidth); var canSetWidth = SetWidth (w, out int rWidth); if (canSetWidth && (cWidth < rWidth || AutoSize)) { Width = rWidth; w = rWidth; } else if (!canSetWidth || !AutoSize) { w = cWidth; } var layout = LayoutStyle; bool layoutChanged = false; if (!(Height is Dim.DimAbsolute)) { // The height is always equal to 1 and must be Dim.DimAbsolute. layoutChanged = true; LayoutStyle = LayoutStyle.Absolute; } Height = 1; if (layoutChanged) { LayoutStyle = layout; } Frame = new Rect (Frame.Location, new Size (w, 1)); SetNeedsDisplay (); } /// public override bool ProcessHotKey (KeyEvent kb) { if (!Enabled) { return false; } return ExecuteHotKey (kb); } /// public override bool ProcessColdKey (KeyEvent kb) { if (!Enabled) { return false; } return ExecuteColdKey (kb); } /// public override bool ProcessKey (KeyEvent kb) { if (!Enabled) { return false; } var result = InvokeKeybindings (kb); if (result != null) return (bool)result; return base.ProcessKey (kb); } bool ExecuteHotKey (KeyEvent ke) { if (ke.Key == (Key.AltMask | HotKey)) { return AcceptKey (); } return false; } bool ExecuteColdKey (KeyEvent ke) { if (IsDefault && ke.KeyValue == '\n') { return AcceptKey (); } return ExecuteHotKey (ke); } bool AcceptKey () { if (!HasFocus) { SetFocus (); } OnClicked (); return true; } /// /// Virtual method to invoke the event. /// public virtual void OnClicked () { Clicked?.Invoke (); } /// /// Clicked , raised when the user clicks the primary mouse button within the Bounds of this /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// /// Client code can hook up to this event, it is /// raised when the button is activated either with /// the mouse or the keyboard. /// public event Action Clicked; /// public override bool MouseEvent (MouseEvent me) { if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) { if (CanFocus && Enabled) { if (!HasFocus) { SetFocus (); SetNeedsDisplay (); Redraw (Bounds); } OnClicked (); } return true; } return false; } /// public override void PositionCursor () { if (HotKey == Key.Unknown && text != "") { for (int i = 0; i < TextFormatter.Text.RuneCount; i++) { if (TextFormatter.Text [i] == text [0]) { Move (i, 0); return; } } } base.PositionCursor (); } /// public override bool OnEnter (View view) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); return base.OnEnter (view); } } }