2007-03-12 Jonathan Pobst <monkey@jpobst.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / NumericUpDown.cs
index 64cbbd1553c4a5106d54c2d345f20dc16be787f5..c2b5bb3d0fc26e7e0c39ed39abda2c31f5251e30 100644 (file)
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-// Copyright (c) 2004 Novell, Inc.
+// Copyright (c) 2005 Novell, Inc.
 //
 // Authors:
-//     Miguel de Icaza (miguel@novell.com).
+//     Jonathan Gilbert        <logic@deltaq.org>
 //
+// Integration into MWF:
+//     Peter Bartok            <pbartok@novell.com>
 //
 
 using System;
+using System.Collections;
 using System.ComponentModel;
+using System.Drawing;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Windows.Forms;
 
 namespace System.Windows.Forms {
-       [DefaultEvent ("ValueChanged")]
-       [DefaultProperty ("Value")]
+       [DefaultEvent("ValueChanged")]
+       [DefaultProperty("Value")]
+#if NET_2_0
+       [DefaultBindingProperty ("Value")]
+       [ClassInterface (ClassInterfaceType.AutoDispatch)]
+       [ComVisible (true)]
+#endif
        public class NumericUpDown : UpDownBase, ISupportInitialize {
-               Decimal updown_value;
-               Decimal min = 0m;
-               Decimal max = 100m;
-               Decimal increment = 1m;
-               bool thousand = false;
-               bool on_init = false;
-               string format;
-               int decimal_places;
-               
-               public NumericUpDown () : base ()
-               {
-                       UpdateFormat ();
-                       UpdateEditText ();
-               }
-               
-#region ISupportInitialize methods
-
-               //
-               // These are used for batch updates of properties:
-               // checking of values should be disabled during this
-               // time, assuming that the caller will set us up
-               // correctly.
-               //
-               // See: http://www.vkarlsen.no/articles/02_isupportinitialize/ISupportInitialize.pdf
-               // for the strategy.
-               //
-               public void BeginInit ()
-               {
-                       on_init = true;
+               #region Local Variables
+               private int     suppress_validation;
+               private int     decimal_places;
+               private bool    hexadecimal;
+               private decimal increment;
+               private decimal maximum;
+               private decimal minimum;
+               private bool    thousands_separator;
+               private decimal dvalue;
+               #endregion      // Local Variables
+
+               #region Public Constructors
+               public NumericUpDown() {
+                       suppress_validation = 0;
+                       decimal_places = 0;
+                       hexadecimal = false;
+                       increment = 1M;
+                       maximum = 100M;
+                       minimum = 0.0M;
+                       thousands_separator = false;
+
+                       Text = "0";
                }
+               #endregion      // Public Constructors
+
+               #region Private Methods
+               void wide_number_multiply_by_10(int[] number) {
+                       long carry = 0;
+
+                       for (int i=0; i < number.Length; i++) {
+                               long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);
 
-               public void EndInit ()
-               {
-                       on_init = false;
-                       if (updown_value < min)
-                               updown_value = min;
-                       if (updown_value > max)
-                               updown_value = max;
-                       
-                       UpdateEditText ();
+                               carry = multiplication >> 32;
+
+                               number[i] = unchecked((int)multiplication);
+                       }
                }
-#endregion
 
-               public override void DownButton ()
-               {
-                       if (UserEdit)
-                               ParseEditText ();
+               void wide_number_multiply_by_16(int[] number) {
+                       int carry = 0;
 
-                       if (updown_value-increment >= min){
-                               updown_value -= increment;
-                               UpdateEditText ();
+                       for (int i=0; i < number.Length; i++) {
+                               int multiplication = unchecked(carry | (number[i] << 4));
+
+                               carry = (number[i] >> 28) & 0x0F;
+
+                               number[i] = multiplication;
                        }
                }
 
-               public override void UpButton ()
-               {
-                       if (UserEdit)
-                               ParseEditText ();
+               void wide_number_divide_by_16(int[] number) {
+                       int carry = 0;
 
-                       if (updown_value + increment <= max){
-                               updown_value += increment;
-                               UpdateEditText ();
+                       for (int i=number.Length - 1; i >= 0; i--) {
+                               int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));
+
+                               carry = (number[i] << 28);
+
+                               number[i] = division;
                        }
                }
 
-               public override void UpdateEditText ()
-               {
-                       if (on_init)
-                               return;
-                       
-                       ChangingText = true;
-                       Text = updown_value.ToString (format);
+               bool wide_number_less_than(int[] left, int[] right) {
+                       unchecked {
+                               for (int i=left.Length - 1; i >= 0; i--) {
+                                       uint leftvalue = (uint)left[i];
+                                       uint rightvalue = (uint)right[i];
+
+                                       if (leftvalue > rightvalue)
+                                               return false;
+                                       if (leftvalue < rightvalue)
+                                               return true;
+                               }
+                       }
+
+                       // equal
+                       return false;
                }
 
-               public void ParseEditText ()
-               {
-                       decimal res;
-                       
-                       try {
-                               res = decimal.Parse (Text);
-                       } catch {
-                               res = min;
+               void wide_number_subtract(int[] subtrahend, int[] minuend) {
+                       long carry = 0;
+
+                       unchecked {
+                               for (int i=0; i < subtrahend.Length; i++) {
+                                       long subtrahendvalue = (uint)subtrahend[i];
+                                       long minuendvalue = (uint)minuend[i];
+
+                                       long result = subtrahendvalue - minuendvalue + carry;
+
+                                       if (result < 0) {
+                                               carry = -1;
+                                               result -= int.MinValue;
+                                               result -= int.MinValue;
+                                       }
+                                       else
+                                               carry = 0;
+
+                                       subtrahend[i] = unchecked((int)result);
+                               }
                        }
-                       
-                       if (res < min)
-                               res = min;
-                       else if (res > max)
-                               res = max;
-                       updown_value = res;
                }
+               #endregion      // Private Methods
+
+               #region Public Instance Properties
+               [DefaultValue(0)]
+               public int DecimalPlaces {
+                       get {
+                               return decimal_places;
+                       }
 
-               protected override void ValidateEditText ()
-               {
-                       ParseEditText ();
-                       UpdateEditText ();
+                       set {
+                               decimal_places = value;
+                               UpdateEditText();
+                       }
                }
 
-               [Bindable(true)]
-               public decimal Value {
+               [DefaultValue(false)]
+               public bool Hexadecimal {
                        get {
-                               return updown_value;
+                               return hexadecimal;
                        }
 
                        set {
-                               if (on_init){
-                                       updown_value = value;
-                                       return;
-                               }
-                               
-                               if (value < min || value > max)
-                                       throw new ArgumentOutOfRangeException (
-                                               String.Format ("Value {0} not within boundaries [{1}, {2}]", value, min, max));
-                               
-                               updown_value = value;
-                               Text = updown_value.ToString (format);
+                               hexadecimal = value;
+                               UpdateEditText();
                        }
                }
 
@@ -155,46 +178,52 @@ namespace System.Windows.Forms {
                        }
 
                        set {
+                               if (value < 0) {
+                                       throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
+                               }
+
                                increment = value;
                        }
                }
-               
+
                [RefreshProperties(RefreshProperties.All)]
                public decimal Maximum {
                        get {
-                               return max;
+                               return maximum;
                        }
 
                        set {
-                               max = value;
-                               if (value > max)
-                                       value = max;
-                               if (min > max)
-                                       min = max;
-                               UpdateEditText ();
+                               maximum = value;
+
+                               if (minimum > maximum)
+                                       minimum = maximum;
+
+                               if (dvalue > maximum)
+                                       Value = maximum;
                        }
                }
 
                [RefreshProperties(RefreshProperties.All)]
                public decimal Minimum {
                        get {
-                               return min;
+                               return minimum;
                        }
 
                        set {
-                               min = value;
-                               if (value < min)
-                                       value = min;
-                               if (min > max)
-                                       max = min;
-                               UpdateEditText ();
+                               minimum = value;
+
+                               if (maximum < minimum)
+                                       maximum = minimum;
+
+                               if (dvalue < minimum)
+                                       Value = minimum;
                        }
                }
 
                [Bindable(false)]
+               [Browsable(false)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
                [EditorBrowsable(EditorBrowsableState.Never)]
-               [Browsable (false)]
                public override string Text {
                        get {
                                return base.Text;
@@ -205,71 +234,252 @@ namespace System.Windows.Forms {
                        }
                }
 
-               void UpdateFormat ()
-               {
-                       if (thousand)
-                               format = "N" + decimal_places.ToString ();
-                       else
-                               format = "F" + decimal_places.ToString ();
-               }
-               
+               [DefaultValue(false)]
+               [Localizable(true)]
                public bool ThousandsSeparator {
                        get {
-                               return thousand;
+                               return thousands_separator;
                        }
 
                        set {
-                               thousand = value;
-                               UpdateFormat ();
-                               UpdateEditText ();
+                               thousands_separator = value;
+                               UpdateEditText();
                        }
                }
 
-               public int DecimalPlaces {
+               [Bindable(true)]
+               public decimal Value {
                        get {
-                               return decimal_places;
+                               if (UserEdit)
+                                       ParseEditText();
+                               return dvalue;
                        }
 
                        set {
-                               decimal_places = value;
-                               UpdateFormat ();
-                               UpdateEditText ();
+                               if (suppress_validation <= 0) {
+                                       if ((value < minimum) || (value > maximum)) {
+                                               throw new ArgumentException("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");
+                                       }
+                               }
+                               if (value != dvalue) {
+                                       dvalue = value;
+                                       OnValueChanged(EventArgs.Empty);
+                                       UpdateEditText();
+                               }
+                       }
+               }
+               #endregion      // Public Instance Properties
+
+               #region Public Instance Methods
+               public void BeginInit() {
+                       suppress_validation++;
+               }
+
+               public override void DownButton() {
+                       if (UserEdit) {
+                               ParseEditText();
                        }
+
+                       Value = Math.Max(minimum, unchecked(dvalue - increment));
                }
-               
-#region Overrides for Control hooks
-
-               protected override void OnLostFocus (EventArgs e)
-               {
-                       base.OnLostFocus (e);
-                       if (UserEdit){
-                               ParseEditText ();
+
+               public void EndInit() {
+                       suppress_validation--;
+                       if (suppress_validation == 0)
                                UpdateEditText ();
+               }
+
+               public override string ToString() {
+                       return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum);
+               }
+
+               public override void UpButton() {
+                       if (UserEdit)
+                               ParseEditText();
+
+                       Value = Math.Min(maximum, unchecked(dvalue + increment));
+               }
+               #endregion      // Public Instance Methods
+
+               #region Protected Instance Methods
+               protected override AccessibleObject CreateAccessibilityInstance() {
+                       AccessibleObject        acc;
+
+                       acc = new AccessibleObject(this);
+                       acc.role = AccessibleRole.SpinButton;
+
+                       return acc;
+               }
+
+               protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
+                       if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
+                               return;
+                       }
+
+                       NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
+                       string pressedKey = e.KeyChar.ToString ();
+
+                       if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) && 
+                               (pressedKey != nfi.NumberGroupSeparator)) {
+                               string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789";
+                               if (acceptable.IndexOf (e.KeyChar) == -1) {
+                                       // FIXME: produce beep to signal that "invalid" key was pressed
+                                       // prevent the key from reaching the text box
+                                       e.Handled = true;
+                               }
                        }
+
+                       base.OnTextBoxKeyPress(source, e);
                }
 
-               protected override void OnMouseDown (MouseEventArgs e)
-               {
-                       // TODO: What to do?
+               protected virtual void OnValueChanged(EventArgs e) {
+                       EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
+                       if (eh != null)
+                               eh (this, e);
                }
 
-               protected override void OnMouseUp (MouseEventArgs e)
-               {
-                       // TODO: What to do?
+               protected void ParseEditText() {
+                       UserEdit = false;
+
+                       try {
+                               string user_edit_text = Text;
+
+                               if (!hexadecimal) {
+                                       dvalue = decimal.Parse(user_edit_text, CultureInfo.CurrentCulture);
+                               } else {
+#if !NET_2_0
+                                       dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 16));
+#else
+                                       dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 10));
+#endif
+                               }
+
+                               if (dvalue < minimum) {
+                                       dvalue = minimum;
+                               }
+
+                               if (dvalue > maximum) {
+                                       dvalue = maximum;
+                               }
+
+                               OnValueChanged(EventArgs.Empty);
+                       }
+                       catch {}
                }
 
-               protected override void OnTextBoxKeyPress (object source, KeyPressEventArgs e)
-               {
-                       Console.WriteLine ("OnTextBoxKeyPress: " + e);
+               protected override void UpdateEditText() {
+                       if (suppress_validation > 0)
+                               return;
+
+                       if (UserEdit)
+                               ParseEditText(); // validate user input
+
+                       if (!hexadecimal) {
+                               // "N" and "F" differ only in that "N" includes commas
+                               // every 3 digits to the left of the decimal and "F"
+                               // does not.
+
+                               string format_string;
+
+                               if (thousands_separator) {
+                                       format_string = "N";
+                               } else {
+                                       format_string = "F";
+                               }
 
-                       // TODO: Apparently we must validate digit input here
+                               format_string += decimal_places;
+
+                               ChangingText = true;
+                               Text = dvalue.ToString(format_string, CultureInfo.CurrentCulture);
+                       }
+                       else {
+                               // Decimal.ToString doesn't know the "X" formatter, and
+                               // converting it to an int is narrowing, so do it
+                               // manually...
+
+                               int[] bits = decimal.GetBits(dvalue);
+
+                               bool negative = (bits[3] < 0);
+
+                               int scale = (bits[3] >> 16) & 0x1F;
+
+                               bits[3] = 0;
+
+                               int[] radix = new int[4];
+
+                               radix[0] = 1;
+
+                               for (int i=0; i < scale; i++)
+                                       wide_number_multiply_by_10(radix);
+
+                               int num_chars = 0;
+
+                               while (!wide_number_less_than(bits, radix)) {
+                                       num_chars++;
+                                       wide_number_multiply_by_16(radix);
+                               }
+
+                               if (num_chars == 0) {
+                                       ChangingText = true;
+                                       Text = "0";
+                                       return;
+                               }
+
+                               StringBuilder chars = new StringBuilder();
+
+                               if (negative)
+                                       chars.Append('-');
+
+                               for (int i=0; i < num_chars; i++) {
+                                       int digit = 0;
+
+                                       wide_number_divide_by_16(radix);
+
+                                       while (!wide_number_less_than(bits, radix)) { // greater than or equals
+                                               digit++;
+                                               wide_number_subtract(bits, radix);
+                                       }
+
+                                       if (digit < 10) {
+                                               chars.Append((char)('0' + digit));
+                                       } else {
+                                               chars.Append((char)('A' + digit - 10));
+                                       }
+                               }
+
+                               ChangingText = true;
+                               Text = chars.ToString();
+                       }
                }
-#endregion
-               
 
-               public override string ToString ()
-               {
-                       return String.Format ("{0} Min={0} Max={1}", base.ToString (), min, max);
+               protected override void ValidateEditText() {
+                       ParseEditText();
+                       UpdateEditText();
+               }
+
+#if NET_2_0
+               protected override void OnLostFocus(EventArgs e) {
+                       base.OnLostFocus(e);
+                       if (this.UserEdit)
+                               this.UpdateEditText();
+               }
+#endif
+               #endregion      // Protected Instance Methods
+
+               #region Events
+               static object ValueChangedEvent = new object ();
+
+               public event EventHandler ValueChanged {
+                       add { Events.AddHandler (ValueChangedEvent, value); }
+                       remove { Events.RemoveHandler (ValueChangedEvent, value); }
+               }
+
+               [Browsable(false)]
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public new event EventHandler TextChanged {
+                       add { base.TextChanged += value; }
+                       remove { base.TextChanged -= value; }
                }
+               #endregion      // Events
        }
 }