* Test/System.Windows.Forms/DataGridViewCellTest.cs: Added
[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 bool    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                 
57
58 #if NET_2_0
59                 NumericUpDownAccelerationCollection accelerations;
60 //              private long buttonPressedTicks;
61                 //private bool isSpinning;
62 #endif
63                 #endregion      // Local Variables
64
65                 #region Public Constructors
66                 public NumericUpDown() {
67                         suppress_validation = false;
68                         decimal_places = 0;
69                         hexadecimal = false;
70                         increment = 1M;
71                         maximum = 100M;
72                         minimum = 0.0M;
73                         thousands_separator = false;
74
75                         Text = "0";
76                 }
77                 #endregion      // Public Constructors
78
79                 #region Private Methods
80                 void wide_number_multiply_by_10(int[] number) {
81                         long carry = 0;
82
83                         for (int i=0; i < number.Length; i++) {
84                                 long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);
85
86                                 carry = multiplication >> 32;
87
88                                 number[i] = unchecked((int)multiplication);
89                         }
90                 }
91
92                 void wide_number_multiply_by_16(int[] number) {
93                         int carry = 0;
94
95                         for (int i=0; i < number.Length; i++) {
96                                 int multiplication = unchecked(carry | (number[i] << 4));
97
98                                 carry = (number[i] >> 28) & 0x0F;
99
100                                 number[i] = multiplication;
101                         }
102                 }
103
104                 void wide_number_divide_by_16(int[] number) {
105                         int carry = 0;
106
107                         for (int i=number.Length - 1; i >= 0; i--) {
108                                 int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));
109
110                                 carry = (number[i] << 28);
111
112                                 number[i] = division;
113                         }
114                 }
115
116                 bool wide_number_less_than(int[] left, int[] right) {
117                         unchecked {
118                                 for (int i=left.Length - 1; i >= 0; i--) {
119                                         uint leftvalue = (uint)left[i];
120                                         uint rightvalue = (uint)right[i];
121
122                                         if (leftvalue > rightvalue)
123                                                 return false;
124                                         if (leftvalue < rightvalue)
125                                                 return true;
126                                 }
127                         }
128
129                         // equal
130                         return false;
131                 }
132
133                 void wide_number_subtract(int[] subtrahend, int[] minuend) {
134                         long carry = 0;
135
136                         unchecked {
137                                 for (int i=0; i < subtrahend.Length; i++) {
138                                         long subtrahendvalue = (uint)subtrahend[i];
139                                         long minuendvalue = (uint)minuend[i];
140
141                                         long result = subtrahendvalue - minuendvalue + carry;
142
143                                         if (result < 0) {
144                                                 carry = -1;
145                                                 result -= int.MinValue;
146                                                 result -= int.MinValue;
147                                         }
148                                         else
149                                                 carry = 0;
150
151                                         subtrahend[i] = unchecked((int)result);
152                                 }
153                         }
154                 }
155                 #endregion      // Private Methods
156
157                 #region Public Instance Properties
158
159 #if NET_2_0     
160                 [Browsable (false)]
161                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
162                 public NumericUpDownAccelerationCollection Accelerations {
163                         get {
164                                 if (accelerations == null)
165                                         accelerations = new NumericUpDownAccelerationCollection ();
166                                 return accelerations;
167                         }
168                 }
169 #endif
170
171                 [DefaultValue(0)]
172                 public int DecimalPlaces {
173                         get {
174                                 return decimal_places;
175                         }
176
177                         set {
178                                 decimal_places = value;
179                                 UpdateEditText();
180                         }
181                 }
182
183                 [DefaultValue(false)]
184                 public bool Hexadecimal {
185                         get {
186                                 return hexadecimal;
187                         }
188
189                         set {
190                                 hexadecimal = value;
191                                 UpdateEditText();
192                         }
193                 }
194
195                 public decimal Increment {
196                         get {
197
198                                 return increment;
199                         }
200
201                         set {
202                                 if (value < 0) {
203                                         throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
204                                 }
205
206                                 increment = value;
207                         }
208                 }
209
210                 [RefreshProperties(RefreshProperties.All)]
211                 public decimal Maximum {
212                         get {
213                                 return maximum;
214                         }
215
216                         set {
217                                 maximum = value;
218
219                                 if (minimum > maximum)
220                                         minimum = maximum;
221
222                                 if (dvalue > maximum)
223                                         Value = maximum;
224                         }
225                 }
226
227                 [RefreshProperties(RefreshProperties.All)]
228                 public decimal Minimum {
229                         get {
230                                 return minimum;
231                         }
232
233                         set {
234                                 minimum = value;
235
236                                 if (maximum < minimum)
237                                         maximum = minimum;
238
239                                 if (dvalue < minimum)
240                                         Value = minimum;
241                         }
242                 }
243
244 #if NET_2_0
245                 [Browsable (false)]
246                 [EditorBrowsable (EditorBrowsableState.Never)]
247                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
248                 public new Padding Padding {
249                         get { return Padding.Empty; }
250                         set { }
251                 }
252 #endif
253
254                 [Bindable(false)]
255                 [Browsable(false)]
256                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
257                 [EditorBrowsable(EditorBrowsableState.Never)]
258                 public override string Text {
259                         get {
260                                 return base.Text;
261                         }
262
263                         set {
264                                 base.Text = value;
265                         }
266                 }
267
268                 [DefaultValue(false)]
269                 [Localizable(true)]
270                 public bool ThousandsSeparator {
271                         get {
272                                 return thousands_separator;
273                         }
274
275                         set {
276                                 thousands_separator = value;
277                                 UpdateEditText();
278                         }
279                 }
280
281                 [Bindable(true)]
282                 public decimal Value {
283                         get {
284                                 if (UserEdit)
285                                         ValidateEditText ();
286                                 return dvalue;
287                         }
288
289                         set {
290                                 if (value != dvalue) {
291                                         if (!suppress_validation && ((value < minimum) || (value > maximum))) {
292 #if NET_2_0
293                                                 throw new ArgumentOutOfRangeException ("value", "NumericUpDown.Value must be within the specified Minimum and Maximum values");
294 #else
295                                                 throw new ArgumentException ("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");                                           
296 #endif
297                                         }
298
299                                         dvalue = value;
300                                         OnValueChanged (EventArgs.Empty);
301                                         UpdateEditText ();
302                                 }
303                         }
304                 }
305                 #endregion      // Public Instance Properties
306
307                 #region Public Instance Methods
308                 public void BeginInit() {
309                         suppress_validation = true;
310                 }
311
312                 public void EndInit() {
313                         suppress_validation = false;
314                         Value = Check (dvalue);
315                         UpdateEditText ();
316                 }
317
318                 public override string ToString() {
319                         return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum);
320                 }
321
322                 public override void DownButton() {
323                         if (UserEdit) {
324                                 ParseEditText ();
325                         }
326
327                         Value = Math.Max (minimum, unchecked (dvalue - increment));
328                 }
329
330                 public override void UpButton() {
331                         if (UserEdit) {
332                                 ParseEditText ();
333                         }
334
335                         Value = Math.Min (maximum, unchecked (dvalue + increment));
336                 }
337                 #endregion      // Public Instance Methods
338
339                 #region Protected Instance Methods
340                 protected override AccessibleObject CreateAccessibilityInstance() {
341                         AccessibleObject        acc;
342
343                         acc = new AccessibleObject(this);
344                         acc.role = AccessibleRole.SpinButton;
345
346                         return acc;
347                 }
348
349                 protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
350                         if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
351                                 return;
352                         }
353
354                         NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
355                         string pressedKey = e.KeyChar.ToString ();
356
357                         if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) && 
358                                 (pressedKey != nfi.NumberGroupSeparator)) {
359                                 string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789";
360                                 if (acceptable.IndexOf (e.KeyChar) == -1) {
361                                         // FIXME: produce beep to signal that "invalid" key was pressed
362                                         // prevent the key from reaching the text box
363                                         e.Handled = true;
364                                 }
365                         }
366
367                         base.OnTextBoxKeyPress(source, e);
368                 }
369
370                 protected virtual void OnValueChanged(EventArgs e) {
371                         EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
372                         if (eh != null)
373                                 eh (this, e);
374                 }
375
376                 protected void ParseEditText () {
377                         try {
378                                 if (!hexadecimal) {
379                                         Value = decimal.Parse (Text, CultureInfo.CurrentCulture);
380                                 } else {
381 #if !NET_2_0
382                                         Value = Check (Convert.ToDecimal (Convert.ToInt32 (Text, 16)));
383 #else
384                                         Value = Check (Convert.ToDecimal (Convert.ToInt32 (Text, 10)));
385 #endif
386                                 }
387                         }
388                         catch { }
389                         finally {
390                                 UserEdit = false;
391                         }
392                 }
393
394                 private decimal Check (decimal val)
395                 {
396                         decimal ret = val;
397                         if (ret < minimum) {
398                                 ret = minimum;
399                         }
400
401                         if (ret > maximum) {
402                                 ret = maximum;
403                         }
404
405                         return ret;
406                 }
407
408                 protected override void UpdateEditText () {
409                         if (suppress_validation)
410                                 return;
411
412                         if (UserEdit)
413                                 ParseEditText ();
414
415                         ChangingText = true;
416                         if (!hexadecimal) {
417                                 // "N" and "F" differ only in that "N" includes commas
418                                 // every 3 digits to the left of the decimal and "F"
419                                 // does not.
420
421                                 string format_string;
422
423                                 if (thousands_separator) {
424                                         format_string = "N";
425                                 } else {
426                                         format_string = "F";
427                                 }
428
429                                 format_string += decimal_places;
430
431                                 Text = dvalue.ToString (format_string, CultureInfo.CurrentCulture);
432
433                         } else {
434                                 // Decimal.ToString doesn't know the "X" formatter, and
435                                 // converting it to an int is narrowing, so do it
436                                 // manually...
437
438                                 int[] bits = decimal.GetBits (dvalue);
439
440                                 bool negative = (bits[3] < 0);
441
442                                 int scale = (bits[3] >> 16) & 0x1F;
443
444                                 bits[3] = 0;
445
446                                 int[] radix = new int[4];
447
448                                 radix[0] = 1;
449
450                                 for (int i = 0; i < scale; i++)
451                                         wide_number_multiply_by_10 (radix);
452
453                                 int num_chars = 0;
454
455                                 while (!wide_number_less_than (bits, radix)) {
456                                         num_chars++;
457                                         wide_number_multiply_by_16 (radix);
458                                 }
459
460                                 if (num_chars == 0) {
461                                         Text = "0";
462                                 }
463
464                                 StringBuilder chars = new StringBuilder ();
465
466                                 if (negative)
467                                         chars.Append ('-');
468
469                                 for (int i = 0; i < num_chars; i++) {
470                                         int digit = 0;
471
472                                         wide_number_divide_by_16 (radix);
473
474                                         while (!wide_number_less_than (bits, radix)) { // greater than or equals
475                                                 digit++;
476                                                 wide_number_subtract (bits, radix);
477                                         }
478
479                                         if (digit < 10) {
480                                                 chars.Append ((char) ('0' + digit));
481                                         } else {
482                                                 chars.Append ((char) ('A' + digit - 10));
483                                         }
484                                 }
485
486                                 Text = chars.ToString ();
487                         }
488                 }
489
490
491                 protected override void ValidateEditText() {
492                         ParseEditText ();
493                         UpdateEditText ();
494                 }
495
496 #if NET_2_0
497                 protected override void OnLostFocus(EventArgs e) {
498                         base.OnLostFocus(e);
499                         if (UserEdit)
500                                 UpdateEditText();
501                 }
502
503                 protected override void OnKeyUp (KeyEventArgs e)
504                 {
505 //                      isSpinning = false;
506                         base.OnKeyUp (e);
507                 }
508
509                 protected override void OnKeyDown (KeyEventArgs e)
510                 {
511 //                      buttonPressedTicks = DateTime.Now.Ticks;
512 //                      isSpinning = true;
513                         base.OnKeyDown (e);
514                 }
515 #endif
516                 #endregion      // Protected Instance Methods
517
518                 #region Events
519 #if NET_2_0
520                 [Browsable (false)]
521                 [EditorBrowsable (EditorBrowsableState.Never)]
522                 public new event EventHandler PaddingChanged {
523                         add { base.PaddingChanged += value; }
524                         remove { base.PaddingChanged -= value; }
525                 }
526 #endif
527
528                 static object ValueChangedEvent = new object ();
529
530                 public event EventHandler ValueChanged {
531                         add { Events.AddHandler (ValueChangedEvent, value); }
532                         remove { Events.RemoveHandler (ValueChangedEvent, value); }
533                 }
534
535                 [Browsable(false)]
536                 [EditorBrowsable(EditorBrowsableState.Never)]
537                 public new event EventHandler TextChanged {
538                         add { base.TextChanged += value; }
539                         remove { base.TextChanged -= value; }
540                 }
541                 #endregion      // Events
542         }
543 }