2006-07-14 Jonathan Pobst <monkey@ipobst.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 // 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 value;
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 (value > 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 (value < 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 value;
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
264                                 this.value = value;
265                                 OnValueChanged(EventArgs.Empty);
266                                 UpdateEditText();
267                         } 
268                 }
269                 #endregion      // Public Instance Properties
270
271                 #region Public Instance Methods
272                 public void BeginInit() {
273                         suppress_validation++;
274                 }
275
276                 public override void DownButton() {
277                         if (UserEdit) {
278                                 ParseEditText();
279                         }
280
281                         Value = Math.Max(minimum, unchecked(value - increment));
282                 }
283
284                 public void EndInit() {
285                         suppress_validation--;
286                         if (suppress_validation == 0)
287                                 UpdateEditText ();
288                 }
289
290                 public override string ToString() {
291                         return string.Format("{0}: value {1} in range [{2}, {3}]", base.ToString(), value, minimum, maximum);
292                 }
293
294                 public override void UpButton() {
295                         if (UserEdit)
296                                 ParseEditText();
297
298                         Value = Math.Min(maximum, unchecked(value + increment));
299                 }
300                 #endregion      // Public Instance Methods
301
302                 #region Protected Instance Methods
303                 protected override AccessibleObject CreateAccessibilityInstance() {
304                         AccessibleObject        acc;
305
306                         acc = new AccessibleObject(this);
307                         acc.role = AccessibleRole.SpinButton;
308
309                         return acc;
310                 }
311
312                 protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
313                         if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
314                                 return;
315                         }
316
317                         string acceptable = hexadecimal ? "\b-.,0123456789ABCDEF" : "\b-.,0123456789";
318
319                         if (acceptable.IndexOf(e.KeyChar) < 0) {
320                                 // prevent the key from reaching the text box
321                                 e.Handled = true;
322                         }
323
324                         base.OnTextBoxKeyPress(source, e);
325                 }
326
327                 protected virtual void OnValueChanged(EventArgs e) {
328                         if (ValueChanged != null) {
329                                 ValueChanged(this, e);
330                         }
331                 }
332
333                 protected void ParseEditText() {
334                         UserEdit = false;
335
336                         try {
337                                 string user_edit_text = Text.Replace(",", "").Trim();
338
339                                 if (!hexadecimal) {
340                                         value = decimal.Parse(user_edit_text);
341                                 } else {
342                                         value = 0M;
343
344                                         for (int i=0; i < user_edit_text.Length; i++) {
345                                                 int hex_digit = Convert.ToInt32(user_edit_text.Substring(i, 1), 16);
346
347                                                 value = unchecked(value * 16M + (decimal)hex_digit);
348                                         }
349                                 }
350
351                                 if (value < minimum) {
352                                         value = minimum;
353                                 }
354
355                                 if (value > maximum) {
356                                         value = maximum;
357                                 }
358
359                                 OnValueChanged(EventArgs.Empty);
360                         }
361                         catch {}
362                 }
363
364                 protected override void UpdateEditText() {
365                         if (suppress_validation > 0)
366                                 return;
367
368                         if (UserEdit)
369                                 ParseEditText(); // validate user input
370
371                         if (!hexadecimal) {
372                                 // "N" and "F" differ only in that "N" includes commas
373                                 // every 3 digits to the left of the decimal and "F"
374                                 // does not.
375
376                                 string format_string;
377
378                                 if (thousands_separator) {
379                                         format_string = "N";
380                                 } else {
381                                         format_string = "F";
382                                 }
383
384                                 format_string += decimal_places;
385
386                                 ChangingText = true;
387                                 Text = value.ToString(format_string);
388                         }
389                         else {
390                                 // Decimal.ToString doesn't know the "X" formatter, and
391                                 // converting it to an int is narrowing, so do it
392                                 // manually...
393
394                                 int[] bits = decimal.GetBits(value);
395
396                                 bool negative = (bits[3] < 0);
397
398                                 int scale = (bits[3] >> 16) & 0x1F;
399
400                                 bits[3] = 0;
401
402                                 int[] radix = new int[4];
403
404                                 radix[0] = 1;
405
406                                 for (int i=0; i < scale; i++)
407                                         wide_number_multiply_by_10(radix);
408
409                                 int num_chars = 0;
410
411                                 while (!wide_number_less_than(bits, radix)) {
412                                         num_chars++;
413                                         wide_number_multiply_by_16(radix);
414                                 }
415
416                                 if (num_chars == 0) {
417                                         ChangingText = true;
418                                         Text = "0";
419                                         return;
420                                 }
421
422                                 StringBuilder chars = new StringBuilder();
423
424                                 if (negative)
425                                         chars.Append('-');
426
427                                 for (int i=0; i < num_chars; i++) {
428                                         int digit = 0;
429
430                                         wide_number_divide_by_16(radix);
431
432                                         while (!wide_number_less_than(bits, radix)) { // greater than or equals
433                                                 digit++;
434                                                 wide_number_subtract(bits, radix);
435                                         }
436
437                                         if (digit < 10) {
438                                                 chars.Append((char)('0' + digit));
439                                         } else {
440                                                 chars.Append((char)('A' + digit - 10));
441                                         }
442                                 }
443
444                                 ChangingText = true;
445                                 Text = chars.ToString();
446                         }
447                 }
448
449                 protected override void ValidateEditText() {
450                         ParseEditText();
451                         UpdateEditText();
452                 }
453                 #endregion      // Protected Instance Methods
454
455                 #region Events
456                 public event EventHandler ValueChanged;
457
458                 [Browsable(false)]
459                 [EditorBrowsable(EditorBrowsableState.Never)]
460                 public event EventHandler TextChanged;
461                 #endregion      // Events
462         }
463 }