New test.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / DomainUpDown.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.Reflection;
36 using System.Runtime.InteropServices;
37 using System.Windows.Forms;
38
39 namespace System.Windows.Forms
40 {
41         [DefaultProperty("Items")]
42         [DefaultEvent("SelectedItemChanged")]
43         public class DomainUpDown : UpDownBase {
44                 #region Local Variables
45                 private DomainUpDownItemCollection      items;
46                 private int                             selected_index = -1;
47                 private bool                            sorted;
48                 private bool                            wrap;
49                 private int                             typed_to_index = -1;
50                 #endregion      // Local Variables
51
52                 #region DomainUpDownAccessibleObject sub-class
53                 [ComVisible(true)]
54                 public class DomainItemAccessibleObject : AccessibleObject {
55                         #region DomainItemAccessibleObject Local Variables
56                         private AccessibleObject parent;
57                         #endregion      // DomainItemAccessibleObject Local Variables
58
59                         #region DomainItemAccessibleObject Constructors
60                         public DomainItemAccessibleObject(string name, AccessibleObject parent) {
61                                 this.name = name;
62                                 this.parent = parent;
63                         }
64                         #endregion      // DomainItemAccessibleObject Constructors
65
66                         #region DomainItemAccessibleObject Properties
67                         public override string Name {
68                                 get {
69                                         return base.Name;
70                                 }
71
72                                 set {
73                                         base.Name = value;
74                                 }
75                         }
76
77                         public override AccessibleObject Parent {
78                                 get {
79                                         return parent;
80                                 }
81                         }
82
83                         public override AccessibleRole Role {
84                                 get {
85                                         return base.Role;
86                                 }
87                         }
88
89                         public override AccessibleStates State {
90                                 get {
91                                         return base.State;
92                                 }
93                         }
94
95                         public override string Value {
96                                 get {
97                                         return base.Value;
98                                 }
99                         }
100                         #endregion      // DomainItemAccessibleObject Properties
101                 }
102                 #endregion      // DomainItemAccessibleObject sub-class
103
104                 #region DomainUpDownAccessibleObject sub-class
105                 [ComVisible(true)]
106                 public class DomainUpDownAccessibleObject : ControlAccessibleObject {
107                         #region DomainUpDownAccessibleObject Local Variables
108                         private Control owner;
109                         #endregion      // DomainUpDownAccessibleObject Local Variables
110
111                         #region DomainUpDownAccessibleObject Constructors
112                         public DomainUpDownAccessibleObject(Control owner) : base(owner) {
113                                 this.owner = owner;
114                         }
115                         #endregion      // DomainUpDownAccessibleObject Constructors
116
117                         #region DomainUpDownAccessibleObject Properties
118                         public override AccessibleRole Role {
119                                 get {
120                                         return base.Role;
121                                 }
122                         }
123                         #endregion      // DomainUpDownAccessibleObject Properties
124
125                         #region DomainUpDownAccessibleObject Methods
126                         public override AccessibleObject GetChild(int index) {
127                                 return base.GetChild (index);
128                         }
129
130                         public override int GetChildCount() {
131                                 return base.GetChildCount ();
132                         }
133                         #endregion      // DomainUpDownAccessibleObject Methods
134                 }
135                 #endregion      // DomainUpDownAccessibleObject sub-class
136
137                 #region DomainUpDownItemCollection sub-class
138                 public class DomainUpDownItemCollection : ArrayList {
139                         internal ArrayList string_cache = new ArrayList();
140
141                         #region Local Variables
142                         #endregion      // Local Variables
143
144                         #region Constructors
145                         internal DomainUpDownItemCollection() {}
146                         #endregion      // Constructors
147
148                         #region Public Instance Properties
149                         [Browsable(false)]
150                         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
151                         public override object this[int index] {
152                                 get {
153                                         return base[index];
154                                 }
155
156                                 set {
157                                         if (value == null) {
158                                                 throw new ArgumentNullException("value", "Cannot add null values to a DomainUpDownItemCollection");
159                                         }
160
161                                         base[index] = value;
162                                         string_cache[index] = value.ToString();
163                                         OnCollectionChanged(index, 0);
164                                 }
165                         }
166                         #endregion      // Public Instance Properties
167
168                         #region Public Instance Methods
169                         public override int Add(object value) {
170                                 if (value == null)
171                                         throw new ArgumentNullException("value", "Cannot add null values to a DomainUpDownItemCollection");
172
173                                 int ret = base.Add(value);
174                                 string_cache.Add(value.ToString());
175                                 OnCollectionChanged(Count - 1, +1);
176                                 return ret;
177                         }
178
179                         public override void Insert(int index, object value) {
180                                 if (value == null)
181                                         throw new ArgumentNullException("value", "Cannot add null values to a DomainUpDownItemCollection");
182
183                                 base.Insert(index, value);
184                                 string_cache.Insert(index, value.ToString());
185                                 OnCollectionChanged(index, +1);
186                         }
187
188                         public override void Remove(object obj) {
189                                 int index = IndexOf(obj);
190
191                                 if (index >= 0)
192                                         RemoveAt(index);
193                         }
194
195                         public override void RemoveAt(int index) {
196                                 base.RemoveAt(index);
197                                 string_cache.RemoveAt(index);
198                                 OnCollectionChanged(index, -1);
199                         }
200                         #endregion      // Public Instance Methods
201
202                         #region Internal Methods and Events
203                         internal void OnCollectionChanged(int index, int size_delta) {
204                                 CollectionChangedEventHandler handler = CollectionChanged;
205
206                                 if (handler != null) {
207                                         handler(index, size_delta);
208                                 }
209                         }
210
211                         internal void PrivSort() {
212                                 PrivSort(0, Count, Comparer.Default);
213                         }
214
215                         internal void PrivSort(int index, int count, IComparer comparer) {
216                                 object[] base_items = null; // this will refer to the base ArrayList private _items member
217                                 object[] string_cache_items = null; // and this will refer to that of the string_cache
218
219                                 FieldInfo items_field = null;
220         
221                                 try {
222                                         items_field = typeof(ArrayList).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance);
223                                 }
224                                 catch {} // security exceptions, perhaps...
225
226                                 if (items_field != null) {
227                                         base_items = items_field.GetValue(this) as object[];
228                                         string_cache_items = items_field.GetValue(string_cache) as object[];
229                                 }
230
231                                 if ((base_items == null) || (string_cache_items == null)) {
232                                         // oh poop =/ guess we have to completely repopulate the string cache
233                                         base.Sort(index, count, comparer);
234
235                                         for (int i=0; i < count; i++)
236                                                 string_cache[i + index] = base[i + index].ToString();
237                                 }
238                                 else {
239                                         // yay, this will be much faster than creating a whole bunch more items
240                                         Array.Sort(string_cache_items, base_items, index, count, comparer);
241
242                                         OnCollectionChanged(-1, 0);
243                                 }
244                         }
245
246                         internal void PrivSort(IComparer comparer) {
247                                 PrivSort(0, Count, comparer);
248                         }
249
250                         internal event CollectionChangedEventHandler CollectionChanged;
251                         #endregion      // Internal Methods and Events
252                 }
253                 #endregion      // DomainUpDownItemCollection sub-class
254
255                 #region Private Methods
256                 // normally I'd use an EventArgs class, but I don't want to create spurious objects here
257                 internal delegate void  CollectionChangedEventHandler(int index, int size_delta);
258
259                 internal void items_CollectionChanged(int index, int size_delta) {
260                         bool new_item = false;
261
262                         if ((index == selected_index) && (size_delta <= 0))
263                                 new_item = true;
264                         else if (index <= selected_index)
265                                 selected_index += size_delta;
266
267                         if (sorted && (index >= 0)) // index < 0 means it is already sorting
268                                 items.PrivSort();
269
270                         UpdateEditText();
271
272                         if (new_item) {
273                                 OnSelectedItemChanged(this, EventArgs.Empty);
274                         }
275                 }
276
277                 void go_to_user_input() {
278                         UserEdit = false;
279
280                         if (typed_to_index >= 0) {
281                                 selected_index = typed_to_index;
282                                 OnSelectedItemChanged(this, EventArgs.Empty);
283                         }
284                 }
285
286                 private void TextBoxLostFocus(object source, EventArgs e) {
287                         Select(base.txtView.SelectionStart + base.txtView.SelectionLength, 0);
288                 }
289
290                 private void TextBoxKeyDown(object source, KeyPressEventArgs e) {
291                         if (!UserEdit) {
292                                 base.txtView.SelectionLength = 0;
293                                 typed_to_index = -1;
294                         }
295
296                         if (base.txtView.SelectionLength == 0) {
297                                 base.txtView.SelectionStart = 0;
298                         }
299
300                         if (base.txtView.SelectionStart != 0) {
301                                 return;
302                         }
303
304                         if (e.KeyChar == '\b') { // backspace
305                                 if (base.txtView.SelectionLength > 0) {
306                                         string prefix = base.txtView.SelectedText.Substring(0, base.txtView.SelectionLength - 1);
307
308                                         bool found = false;
309
310                                         if (typed_to_index < 0) {
311                                                 typed_to_index = 0;
312                                         }
313
314                                         if (sorted) {
315                                                 for (int i=typed_to_index; i >= 0; i--) {
316                                                         int difference = string.Compare(prefix, 0, items.string_cache[i].ToString(), 0, prefix.Length, true);
317
318                                                         if (difference == 0) {
319                                                                 found = true;
320                                                                 typed_to_index = i;
321                                                         }
322
323                                                         if (difference > 0) { // since it is sorted, no strings after this point will match
324                                                                 break;
325                                                         }
326                                                 }
327                                         } else {
328                                                 for (int i=0; i < items.Count; i++) {
329                                                         if (0 == string.Compare(prefix, 0, items.string_cache[i].ToString(), 0, prefix.Length, true)) {
330                                                                 found = true;
331                                                                 typed_to_index = i;
332                                                                 break;
333                                                         }
334                                                 }
335                                         }
336
337                                         ChangingText = true;
338
339                                         if (found)
340                                                 Text = items.string_cache[typed_to_index].ToString();
341                                         else
342                                                 Text = prefix;
343
344                                         Select(0, prefix.Length);
345
346                                         UserEdit = true;
347
348                                         e.Handled = true;
349                                 }
350                         }
351                         else {
352                                 char key_char = e.KeyChar;
353
354                                 if (char.IsLetterOrDigit(key_char)
355                                         || char.IsNumber(key_char)
356                                         || char.IsPunctuation(key_char)
357                                         || char.IsSymbol(key_char)
358                                         || char.IsWhiteSpace(key_char)) {
359                                         string prefix = base.txtView.SelectedText + key_char;
360
361                                         bool found = false;
362
363                                         if (typed_to_index < 0) {
364                                                 typed_to_index = 0;
365                                         }
366
367                                         if (sorted) {
368                                                 for (int i=typed_to_index; i < items.Count; i++) {
369                                                         int difference = string.Compare(prefix, 0, items.string_cache[i].ToString(), 0, prefix.Length, true);
370
371                                                         if (difference == 0) {
372                                                                 found = true;
373                                                                 typed_to_index = i;
374                                                         }
375
376                                                         if (difference <= 0) { // since it is sorted, no strings after this point will match
377                                                                 break;
378                                                         }
379                                                 }
380                                         } else {
381                                                 for (int i=0; i < items.Count; i++) {
382                                                         if (0 == string.Compare(prefix, 0, items.string_cache[i].ToString(), 0, prefix.Length, true)) {
383                                                                 found = true;
384                                                                 typed_to_index = i;
385                                                                 break;
386                                                         }
387                                                 }
388                                         }
389
390                                         ChangingText = true;
391
392                                         if (found) {
393                                                 Text = items.string_cache[typed_to_index].ToString();
394                                         } else {
395                                                 Text = prefix;
396                                         }
397
398                                         Select(0, prefix.Length);
399
400                                         UserEdit = true;
401
402                                         e.Handled = true;
403                                 }
404                         }
405                 }
406                 #endregion      // Private Methods
407
408                 #region Public Constructors
409                 public DomainUpDown() {
410                         selected_index = -1;
411                         sorted = false;
412                         wrap = false;
413                         typed_to_index = -1;
414
415                         items = new DomainUpDownItemCollection();
416                         items.CollectionChanged += new CollectionChangedEventHandler(items_CollectionChanged);
417
418                         this.txtView.LostFocus +=new EventHandler(TextBoxLostFocus);
419                         this.txtView.KeyPress += new KeyPressEventHandler(TextBoxKeyDown);
420                 }
421                 #endregion      // Public Constructors
422
423                 #region Public Instance Properties
424                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
425                 [Editor("System.Windows.Forms.Design.StringCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
426                 [Localizable(true)]
427                 public DomainUpDownItemCollection Items {
428                         get {
429                                 return items;
430                         }
431                 }
432
433                 [Browsable(false)]
434                 [DefaultValue(-1)]
435                 public int SelectedIndex {
436                         get {
437                                 return selected_index;
438                         }
439                         set {
440                                 object before = (selected_index >= 0) ? items[selected_index] : null;
441
442                                 selected_index = value;
443                                 UpdateEditText();
444
445                                 object after = (selected_index >= 0) ? items[selected_index] : null;
446
447                                 if (!ReferenceEquals(before, after)) {
448                                         OnSelectedItemChanged(this, EventArgs.Empty);
449                                 }
450                         }
451                 }
452
453                 [Browsable(false)]
454                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
455                 public object SelectedItem {
456                         get {
457                                 if (selected_index >= 0) {
458                                         return items[selected_index];
459                                 } else {
460                                         return null;
461                                 }
462                         }
463
464                         set {
465                                 SelectedIndex = items.IndexOf(value);
466                         }
467                 }
468
469                 [DefaultValue(false)]
470                 public bool Sorted {
471                         get {
472                                 return sorted;
473                         }
474                         set {
475                                 sorted = value;
476
477                                 if (sorted)
478                                         items.PrivSort();
479                         }
480                 }
481
482                 [DefaultValue(false)]
483                 [Localizable(true)]
484                 public bool Wrap {
485                         get {
486                                 return wrap;
487                         }
488                         set {
489                                 wrap = value;
490                         }
491                 }
492                 #endregion      // Public Instance Properties
493
494                 #region Public Instance Methods
495                 public override void DownButton() {
496                         if (UserEdit)
497                                 go_to_user_input();
498
499                         int new_index = selected_index + 1;
500
501                         if (new_index >= items.Count) {
502                                 if (!wrap)
503                                         return;
504
505                                 new_index = 0;
506                         }
507
508                         SelectedIndex = new_index;
509                 }
510
511                 public override string ToString() {
512                         return base.ToString() + ", Items.Count: " + items.Count + ", SelectedIndex: " + selected_index;
513                 }
514
515                 public override void UpButton() {
516                         if (UserEdit)
517                                 go_to_user_input();
518
519                         int new_index = selected_index - 1;
520
521                         if (new_index < 0) {
522                                 if (!wrap) {
523                                         return;
524                                 }
525
526                                 new_index = items.Count - 1;
527                         }
528
529                         SelectedIndex = new_index;
530                 }
531                 #endregion      // Public Instance Methods
532
533                 #region Protected Instance Methods
534                 protected override AccessibleObject CreateAccessibilityInstance() {
535                         AccessibleObject        acc;
536
537                         acc = new AccessibleObject(this);
538                         acc.role = AccessibleRole.SpinButton;
539
540                         return acc;
541                 }
542
543                 protected override void OnChanged(object source, EventArgs e) {
544                         base.OnChanged (source, e);
545                 }
546
547                 protected void OnSelectedItemChanged(object source, EventArgs e) {
548                         EventHandler handler = SelectedItemChanged;
549
550                         if (handler != null)
551                                 handler(source, e);
552                 }
553
554                 protected override void UpdateEditText() {
555                         if ((selected_index >= 0) && (selected_index < items.Count)) {
556                                 ChangingText = true;
557                                 Text = items.string_cache[selected_index].ToString();
558                         }
559                 }
560
561                 protected override void OnTextBoxKeyDown(object source, KeyEventArgs e) {
562                         base.OnTextBoxKeyDown (source, e);
563                 }
564
565                 #endregion      // Protected Instance Methods
566
567                 #region Events
568                 public event EventHandler SelectedItemChanged;
569                 #endregion      // Events
570         }
571 }