New test.
[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 // COMPLETE
30
31 using System;
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Drawing;
35 using System.Runtime.InteropServices;
36 using System.Text;
37 using System.Windows.Forms;
38
39 namespace System.Windows.Forms {
40         [DefaultEvent("ValueChanged")]
41         [DefaultProperty("Value")]
42         public class NumericUpDown : UpDownBase, ISupportInitialize {
43                 #region Local Variables
44                 private int     suppress_validation;
45                 private int     decimal_places;
46                 private bool    hexadecimal;
47                 private decimal increment;
48                 private decimal maximum;
49                 private decimal minimum;
50                 private bool    thousands_separator;
51                 private decimal dvalue;
52                 #endregion      // Local Variables
53
54                 #region Public Constructors
55                 public NumericUpDown() {
56                         suppress_validation = 0;
57                         decimal_places = 0;
58                         hexadecimal = false;
59                         increment = 1M;
60                         maximum = 100.0M;
61                         minimum = 0.0M;
62                         thousands_separator = false;
63                 }
64                 #endregion      // Public Constructors
65
66                 #region Private Methods
67                 void wide_number_multiply_by_10(int[] number) {
68                         long carry = 0;
69
70                         for (int i=0; i < number.Length; i++) {
71                                 long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);
72
73                                 carry = multiplication >> 32;
74
75                                 number[i] = unchecked((int)multiplication);
76                         }
77                 }
78
79                 void wide_number_multiply_by_16(int[] number) {
80                         int carry = 0;
81
82                         for (int i=0; i < number.Length; i++) {
83                                 int multiplication = unchecked(carry | (number[i] << 4));
84
85                                 carry = (number[i] >> 28) & 0x0F;
86
87                                 number[i] = multiplication;
88                         }
89                 }
90
91                 void wide_number_divide_by_16(int[] number) {
92                         int carry = 0;
93
94                         for (int i=number.Length - 1; i >= 0; i--) {
95                                 int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));
96
97                                 carry = (number[i] << 28);
98
99                                 number[i] = division;
100                         }
101                 }
102
103                 bool wide_number_less_than(int[] left, int[] right) {
104                         unchecked {
105                                 for (int i=left.Length - 1; i >= 0; i--) {
106                                         uint leftvalue = (uint)left[i];
107                                         uint rightvalue = (uint)right[i];
108
109                                         if (leftvalue > rightvalue)
110                                                 return false;
111                                         if (leftvalue < rightvalue)
112                                                 return true;
113                                 }
114                         }
115
116                         // equal
117                         return false;
118                 }
119
120                 void wide_number_subtract(int[] subtrahend, int[] minuend) {
121                         long carry = 0;
122
123                         unchecked {
124                                 for (int i=0; i < subtrahend.Length; i++) {
125                                         long subtrahendvalue = (uint)subtrahend[i];
126                                         long minuendvalue = (uint)minuend[i];
127
128                                         long result = subtrahendvalue - minuendvalue + carry;
129
130                                         if (result < 0) {
131                                                 carry = -1;
132                                                 result -= int.MinValue;
133                                                 result -= int.MinValue;
134                                         }
135                                         else
136                                                 carry = 0;
137
138                                         subtrahend[i] = unchecked((int)result);
139                                 }
140                         }
141                 }
142                 #endregion      // Private Methods
143
144                 #region Public Instance Properties
145                 [DefaultValue(0)]
146                 public int DecimalPlaces {
147                         get {
148                                 return decimal_places;
149                         }
150
151                         set {
152                                 decimal_places = value;
153                                 if (!UserEdit) {
154                                         UpdateEditText();
155                                 }
156                         }
157                 }
158
159                 [DefaultValue(false)]
160                 public bool Hexadecimal {
161                         get {
162                                 return hexadecimal;
163                         }
164
165                         set {
166                                 hexadecimal = value;
167                                 if (!UserEdit) {
168                                         UpdateEditText();
169                                 }
170                         }
171                 }
172
173                 public decimal Increment {
174                         get {
175                                 return increment;
176                         }
177
178                         set {
179                                 if (value < 0) {
180                                         throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
181                                 }
182
183                                 increment = value;
184                         }
185                 }
186
187                 [RefreshProperties(RefreshProperties.All)]
188                 public decimal Maximum {
189                         get {
190                                 return maximum;
191                         }
192
193                         set {
194                                 maximum = value;
195
196                                 if (minimum > maximum)
197                                         minimum = maximum;
198
199                                 if (dvalue > maximum)
200                                         Value = maximum;
201                         }
202                 }
203
204                 [RefreshProperties(RefreshProperties.All)]
205                 public decimal Minimum {
206                         get {
207                                 return minimum;
208                         }
209
210                         set {
211                                 minimum = value;
212
213                                 if (maximum < minimum)
214                                         maximum = minimum;
215
216                                 if (dvalue < minimum)
217                                         Value = minimum;
218                         }
219                 }
220
221                 [Bindable(false)]
222                 [Browsable(false)]
223                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
224                 [EditorBrowsable(EditorBrowsableState.Never)]
225                 public override string Text {
226                         get {
227                                 return base.Text;
228                         }
229
230                         set {
231                                 base.Text = value;
232                         }
233                 }
234
235                 [DefaultValue(false)]
236                 [Localizable(true)]
237                 public bool ThousandsSeparator {
238                         get {
239                                 return thousands_separator;
240                         }
241
242                         set {
243                                 thousands_separator = value;
244
245                                 if (!UserEdit) {
246                                         UpdateEditText();
247                                 }
248                         }
249                 }
250
251                 [Bindable(true)]
252                 public decimal Value {
253                         get {
254                                 return dvalue;
255                         }
256
257                         set {
258                                 if (suppress_validation <= 0) {
259                                         if ((value < minimum) || (value > maximum)) {
260                                                 throw new ArgumentException("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");
261                                         }
262                                 }
263                                 if (value != dvalue) {
264                                         dvalue = value;
265                                         OnValueChanged(EventArgs.Empty);
266                                         UpdateEditText();
267                                 }
268                         }
269                 }
270                 #endregion      // Public Instance Properties
271
272                 #region Public Instance Methods
273                 public void BeginInit() {
274                         suppress_validation++;
275                 }
276
277                 public override void DownButton() {
278                         if (UserEdit) {
279                                 ParseEditText();
280                         }
281
282                         Value = Math.Max(minimum, unchecked(dvalue - increment));
283                 }
284
285                 public void EndInit() {
286                         suppress_validation--;
287                         if (suppress_validation == 0)
288                                 UpdateEditText ();
289                 }
290
291                 public override string ToString() {
292                         return string.Format("{0}: value {1} in range [{2}, {3}]", base.ToString(), dvalue, minimum, maximum);
293                 }
294
295                 public override void UpButton() {
296                         if (UserEdit)
297                                 ParseEditText();
298
299                         Value = Math.Min(maximum, unchecked(dvalue + increment));
300                 }
301                 #endregion      // Public Instance Methods
302
303                 #region Protected Instance Methods
304                 protected override AccessibleObject CreateAccessibilityInstance() {
305                         AccessibleObject        acc;
306
307                         acc = new AccessibleObject(this);
308                         acc.role = AccessibleRole.SpinButton;
309
310                         return acc;
311                 }
312
313                 protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
314                         if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
315                                 return;
316                         }
317
318                         string acceptable = hexadecimal ? "\b-.,0123456789ABCDEF" : "\b-.,0123456789";
319
320                         if (acceptable.IndexOf(e.KeyChar) < 0) {
321                                 // prevent the key from reaching the text box
322                                 e.Handled = true;
323                         }
324
325                         base.OnTextBoxKeyPress(source, e);
326                 }
327
328                 protected virtual void OnValueChanged(EventArgs e) {
329                         if (ValueChanged != null) {
330                                 ValueChanged(this, e);
331                         }
332                 }
333
334                 protected void ParseEditText() {
335                         UserEdit = false;
336
337                         try {
338                                 string user_edit_text = Text.Replace(",", "").Trim();
339
340                                 if (!hexadecimal) {
341                                         dvalue = decimal.Parse(user_edit_text);
342                                 } else {
343                                         dvalue = 0M;
344
345                                         for (int i=0; i < user_edit_text.Length; i++) {
346                                                 int hex_digit = Convert.ToInt32(user_edit_text.Substring(i, 1), 16);
347
348                                                 dvalue = unchecked(dvalue * 16M + (decimal)hex_digit);
349                                         }
350                                 }
351
352                                 if (dvalue < minimum) {
353                                         dvalue = minimum;
354                                 }
355
356                                 if (dvalue > maximum) {
357                                         dvalue = maximum;
358                                 }
359
360                                 OnValueChanged(EventArgs.Empty);
361                         }
362                         catch {}
363                 }
364
365                 protected override void UpdateEditText() {
366                         if (suppress_validation > 0)
367                                 return;
368
369                         if (UserEdit)
370                                 ParseEditText(); // validate user input
371
372                         if (!hexadecimal) {
373                                 // "N" and "F" differ only in that "N" includes commas
374                                 // every 3 digits to the left of the decimal and "F"
375                                 // does not.
376
377                                 string format_string;
378
379                                 if (thousands_separator) {
380                                         format_string = "N";
381                                 } else {
382                                         format_string = "F";
383                                 }
384
385                                 format_string += decimal_places;
386
387                                 ChangingText = true;
388                                 Text = dvalue.ToString(format_string);
389                         }
390                         else {
391                                 // Decimal.ToString doesn't know the "X" formatter, and
392                                 // converting it to an int is narrowing, so do it
393                                 // manually...
394
395                                 int[] bits = decimal.GetBits(dvalue);
396
397                                 bool negative = (bits[3] < 0);
398
399                                 int scale = (bits[3] >> 16) & 0x1F;
400
401                                 bits[3] = 0;
402
403                                 int[] radix = new int[4];
404
405                                 radix[0] = 1;
406
407                                 for (int i=0; i < scale; i++)
408                                         wide_number_multiply_by_10(radix);
409
410                                 int num_chars = 0;
411
412                                 while (!wide_number_less_than(bits, radix)) {
413                                         num_chars++;
414                                         wide_number_multiply_by_16(radix);
415                                 }
416
417                                 if (num_chars == 0) {
418                                         ChangingText = true;
419                                         Text = "0";
420                                         return;
421                                 }
422
423                                 StringBuilder chars = new StringBuilder();
424
425                                 if (negative)
426                                         chars.Append('-');
427
428                                 for (int i=0; i < num_chars; i++) {
429                                         int digit = 0;
430
431                                         wide_number_divide_by_16(radix);
432
433                                         while (!wide_number_less_than(bits, radix)) { // greater than or equals
434                                                 digit++;
435                                                 wide_number_subtract(bits, radix);
436                                         }
437
438                                         if (digit < 10) {
439                                                 chars.Append((char)('0' + digit));
440                                         } else {
441                                                 chars.Append((char)('A' + digit - 10));
442                                         }
443                                 }
444
445                                 ChangingText = true;
446                                 Text = chars.ToString();
447                         }
448                 }
449
450                 protected override void ValidateEditText() {
451                         ParseEditText();
452                         UpdateEditText();
453                 }
454                 #endregion      // Protected Instance Methods
455
456                 #region Events
457                 public event EventHandler ValueChanged;
458
459                 [Browsable(false)]
460                 [EditorBrowsable(EditorBrowsableState.Never)]
461                 public event EventHandler TextChanged;
462                 #endregion      // Events
463         }
464 }