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