2007-03-12 Jonathan Pobst <monkey@jpobst.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / NumericUpDown.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2005 Novell, Inc.
21 //
22 // Authors:
23 //      Jonathan Gilbert        <logic@deltaq.org>
24 //
25 // Integration into MWF:
26 //      Peter Bartok            <pbartok@novell.com>
27 //
28
29 using System;
30 using System.Collections;
31 using System.ComponentModel;
32 using System.Drawing;
33 using System.Globalization;
34 using System.Runtime.InteropServices;
35 using System.Text;
36 using System.Windows.Forms;
37
38 namespace System.Windows.Forms {
39         [DefaultEvent("ValueChanged")]
40         [DefaultProperty("Value")]
41 #if NET_2_0
42         [DefaultBindingProperty ("Value")]
43         [ClassInterface (ClassInterfaceType.AutoDispatch)]
44         [ComVisible (true)]
45 #endif
46         public class NumericUpDown : UpDownBase, ISupportInitialize {
47                 #region Local Variables
48                 private int     suppress_validation;
49                 private int     decimal_places;
50                 private bool    hexadecimal;
51                 private decimal increment;
52                 private decimal maximum;
53                 private decimal minimum;
54                 private bool    thousands_separator;
55                 private decimal dvalue;
56                 #endregion      // Local Variables
57
58                 #region Public Constructors
59                 public NumericUpDown() {
60                         suppress_validation = 0;
61                         decimal_places = 0;
62                         hexadecimal = false;
63                         increment = 1M;
64                         maximum = 100M;
65                         minimum = 0.0M;
66                         thousands_separator = false;
67
68                         Text = "0";
69                 }
70                 #endregion      // Public Constructors
71
72                 #region Private Methods
73                 void wide_number_multiply_by_10(int[] number) {
74                         long carry = 0;
75
76                         for (int i=0; i < number.Length; i++) {
77                                 long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);
78
79                                 carry = multiplication >> 32;
80
81                                 number[i] = unchecked((int)multiplication);
82                         }
83                 }
84
85                 void wide_number_multiply_by_16(int[] number) {
86                         int carry = 0;
87
88                         for (int i=0; i < number.Length; i++) {
89                                 int multiplication = unchecked(carry | (number[i] << 4));
90
91                                 carry = (number[i] >> 28) & 0x0F;
92
93                                 number[i] = multiplication;
94                         }
95                 }
96
97                 void wide_number_divide_by_16(int[] number) {
98                         int carry = 0;
99
100                         for (int i=number.Length - 1; i >= 0; i--) {
101                                 int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));
102
103                                 carry = (number[i] << 28);
104
105                                 number[i] = division;
106                         }
107                 }
108
109                 bool wide_number_less_than(int[] left, int[] right) {
110                         unchecked {
111                                 for (int i=left.Length - 1; i >= 0; i--) {
112                                         uint leftvalue = (uint)left[i];
113                                         uint rightvalue = (uint)right[i];
114
115                                         if (leftvalue > rightvalue)
116                                                 return false;
117                                         if (leftvalue < rightvalue)
118                                                 return true;
119                                 }
120                         }
121
122                         // equal
123                         return false;
124                 }
125
126                 void wide_number_subtract(int[] subtrahend, int[] minuend) {
127                         long carry = 0;
128
129                         unchecked {
130                                 for (int i=0; i < subtrahend.Length; i++) {
131                                         long subtrahendvalue = (uint)subtrahend[i];
132                                         long minuendvalue = (uint)minuend[i];
133
134                                         long result = subtrahendvalue - minuendvalue + carry;
135
136                                         if (result < 0) {
137                                                 carry = -1;
138                                                 result -= int.MinValue;
139                                                 result -= int.MinValue;
140                                         }
141                                         else
142                                                 carry = 0;
143
144                                         subtrahend[i] = unchecked((int)result);
145                                 }
146                         }
147                 }
148                 #endregion      // Private Methods
149
150                 #region Public Instance Properties
151                 [DefaultValue(0)]
152                 public int DecimalPlaces {
153                         get {
154                                 return decimal_places;
155                         }
156
157                         set {
158                                 decimal_places = value;
159                                 UpdateEditText();
160                         }
161                 }
162
163                 [DefaultValue(false)]
164                 public bool Hexadecimal {
165                         get {
166                                 return hexadecimal;
167                         }
168
169                         set {
170                                 hexadecimal = value;
171                                 UpdateEditText();
172                         }
173                 }
174
175                 public decimal Increment {
176                         get {
177                                 return increment;
178                         }
179
180                         set {
181                                 if (value < 0) {
182                                         throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
183                                 }
184
185                                 increment = value;
186                         }
187                 }
188
189                 [RefreshProperties(RefreshProperties.All)]
190                 public decimal Maximum {
191                         get {
192                                 return maximum;
193                         }
194
195                         set {
196                                 maximum = value;
197
198                                 if (minimum > maximum)
199                                         minimum = maximum;
200
201                                 if (dvalue > maximum)
202                                         Value = maximum;
203                         }
204                 }
205
206                 [RefreshProperties(RefreshProperties.All)]
207                 public decimal Minimum {
208                         get {
209                                 return minimum;
210                         }
211
212                         set {
213                                 minimum = value;
214
215                                 if (maximum < minimum)
216                                         maximum = minimum;
217
218                                 if (dvalue < minimum)
219                                         Value = minimum;
220                         }
221                 }
222
223                 [Bindable(false)]
224                 [Browsable(false)]
225                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
226                 [EditorBrowsable(EditorBrowsableState.Never)]
227                 public override string Text {
228                         get {
229                                 return base.Text;
230                         }
231
232                         set {
233                                 base.Text = value;
234                         }
235                 }
236
237                 [DefaultValue(false)]
238                 [Localizable(true)]
239                 public bool ThousandsSeparator {
240                         get {
241                                 return thousands_separator;
242                         }
243
244                         set {
245                                 thousands_separator = value;
246                                 UpdateEditText();
247                         }
248                 }
249
250                 [Bindable(true)]
251                 public decimal Value {
252                         get {
253                                 if (UserEdit)
254                                         ParseEditText();
255                                 return dvalue;
256                         }
257
258                         set {
259                                 if (suppress_validation <= 0) {
260                                         if ((value < minimum) || (value > maximum)) {
261                                                 throw new ArgumentException("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");
262                                         }
263                                 }
264                                 if (value != dvalue) {
265                                         dvalue = value;
266                                         OnValueChanged(EventArgs.Empty);
267                                         UpdateEditText();
268                                 }
269                         }
270                 }
271                 #endregion      // Public Instance Properties
272
273                 #region Public Instance Methods
274                 public void BeginInit() {
275                         suppress_validation++;
276                 }
277
278                 public override void DownButton() {
279                         if (UserEdit) {
280                                 ParseEditText();
281                         }
282
283                         Value = Math.Max(minimum, unchecked(dvalue - increment));
284                 }
285
286                 public void EndInit() {
287                         suppress_validation--;
288                         if (suppress_validation == 0)
289                                 UpdateEditText ();
290                 }
291
292                 public override string ToString() {
293                         return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum);
294                 }
295
296                 public override void UpButton() {
297                         if (UserEdit)
298                                 ParseEditText();
299
300                         Value = Math.Min(maximum, unchecked(dvalue + increment));
301                 }
302                 #endregion      // Public Instance Methods
303
304                 #region Protected Instance Methods
305                 protected override AccessibleObject CreateAccessibilityInstance() {
306                         AccessibleObject        acc;
307
308                         acc = new AccessibleObject(this);
309                         acc.role = AccessibleRole.SpinButton;
310
311                         return acc;
312                 }
313
314                 protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
315                         if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
316                                 return;
317                         }
318
319                         NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
320                         string pressedKey = e.KeyChar.ToString ();
321
322                         if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) && 
323                                 (pressedKey != nfi.NumberGroupSeparator)) {
324                                 string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789";
325                                 if (acceptable.IndexOf (e.KeyChar) == -1) {
326                                         // FIXME: produce beep to signal that "invalid" key was pressed
327                                         // prevent the key from reaching the text box
328                                         e.Handled = true;
329                                 }
330                         }
331
332                         base.OnTextBoxKeyPress(source, e);
333                 }
334
335                 protected virtual void OnValueChanged(EventArgs e) {
336                         EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
337                         if (eh != null)
338                                 eh (this, e);
339                 }
340
341                 protected void ParseEditText() {
342                         UserEdit = false;
343
344                         try {
345                                 string user_edit_text = Text;
346
347                                 if (!hexadecimal) {
348                                         dvalue = decimal.Parse(user_edit_text, CultureInfo.CurrentCulture);
349                                 } else {
350 #if !NET_2_0
351                                         dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 16));
352 #else
353                                         dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 10));
354 #endif
355                                 }
356
357                                 if (dvalue < minimum) {
358                                         dvalue = minimum;
359                                 }
360
361                                 if (dvalue > maximum) {
362                                         dvalue = maximum;
363                                 }
364
365                                 OnValueChanged(EventArgs.Empty);
366                         }
367                         catch {}
368                 }
369
370                 protected override void UpdateEditText() {
371                         if (suppress_validation > 0)
372                                 return;
373
374                         if (UserEdit)
375                                 ParseEditText(); // validate user input
376
377                         if (!hexadecimal) {
378                                 // "N" and "F" differ only in that "N" includes commas
379                                 // every 3 digits to the left of the decimal and "F"
380                                 // does not.
381
382                                 string format_string;
383
384                                 if (thousands_separator) {
385                                         format_string = "N";
386                                 } else {
387                                         format_string = "F";
388                                 }
389
390                                 format_string += decimal_places;
391
392                                 ChangingText = true;
393                                 Text = dvalue.ToString(format_string, CultureInfo.CurrentCulture);
394                         }
395                         else {
396                                 // Decimal.ToString doesn't know the "X" formatter, and
397                                 // converting it to an int is narrowing, so do it
398                                 // manually...
399
400                                 int[] bits = decimal.GetBits(dvalue);
401
402                                 bool negative = (bits[3] < 0);
403
404                                 int scale = (bits[3] >> 16) & 0x1F;
405
406                                 bits[3] = 0;
407
408                                 int[] radix = new int[4];
409
410                                 radix[0] = 1;
411
412                                 for (int i=0; i < scale; i++)
413                                         wide_number_multiply_by_10(radix);
414
415                                 int num_chars = 0;
416
417                                 while (!wide_number_less_than(bits, radix)) {
418                                         num_chars++;
419                                         wide_number_multiply_by_16(radix);
420                                 }
421
422                                 if (num_chars == 0) {
423                                         ChangingText = true;
424                                         Text = "0";
425                                         return;
426                                 }
427
428                                 StringBuilder chars = new StringBuilder();
429
430                                 if (negative)
431                                         chars.Append('-');
432
433                                 for (int i=0; i < num_chars; i++) {
434                                         int digit = 0;
435
436                                         wide_number_divide_by_16(radix);
437
438                                         while (!wide_number_less_than(bits, radix)) { // greater than or equals
439                                                 digit++;
440                                                 wide_number_subtract(bits, radix);
441                                         }
442
443                                         if (digit < 10) {
444                                                 chars.Append((char)('0' + digit));
445                                         } else {
446                                                 chars.Append((char)('A' + digit - 10));
447                                         }
448                                 }
449
450                                 ChangingText = true;
451                                 Text = chars.ToString();
452                         }
453                 }
454
455                 protected override void ValidateEditText() {
456                         ParseEditText();
457                         UpdateEditText();
458                 }
459
460 #if NET_2_0
461                 protected override void OnLostFocus(EventArgs e) {
462                         base.OnLostFocus(e);
463                         if (this.UserEdit)
464                                 this.UpdateEditText();
465                 }
466 #endif
467                 #endregion      // Protected Instance Methods
468
469                 #region Events
470                 static object ValueChangedEvent = new object ();
471
472                 public event EventHandler ValueChanged {
473                         add { Events.AddHandler (ValueChangedEvent, value); }
474                         remove { Events.RemoveHandler (ValueChangedEvent, value); }
475                 }
476
477                 [Browsable(false)]
478                 [EditorBrowsable(EditorBrowsableState.Never)]
479                 public new event EventHandler TextChanged {
480                         add { base.TextChanged += value; }
481                         remove { base.TextChanged -= value; }
482                 }
483                 #endregion      // Events
484         }
485 }