Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Menu.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) 2004-2005 Novell, Inc.
21 //
22 // Authors:
23 //      Jordi Mas i Hernandez, jordi@ximian.com
24 //
25 //      TODO:
26 //              - FindMenuItem
27 //              - MdiListItem
28 //
29
30 using System.Collections;
31 using System.ComponentModel;
32 using System.ComponentModel.Design;
33 using System.Reflection;
34 using System.Runtime.InteropServices;
35 using System.Collections.Generic;
36
37 namespace System.Windows.Forms
38 {
39         [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
40         [ListBindable(false)]
41         public abstract class Menu : Component
42         {
43                 internal MenuItemCollection menu_items;
44                 internal IntPtr menu_handle = IntPtr.Zero;
45                 internal Menu parent_menu = null;
46                 System.Drawing.Rectangle rect;
47                 // UIA Framework Note: Used to keep track of expanded menus
48                 internal Control Wnd;
49                 internal MenuTracker tracker;
50                 private string control_name;
51                 private object control_tag;
52                 public const int FindHandle = 0;
53                 public const int FindShortcut = 1;
54
55                 protected Menu (MenuItem[] items)
56                 {
57                         menu_items = new MenuItemCollection (this);
58
59                         if (items != null)
60                                 menu_items.AddRange (items);
61                 }
62
63                 #region Public Properties
64                 
65                 [BrowsableAttribute(false)]
66                 [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]
67                 [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
68                 public IntPtr Handle {
69                         get { return menu_handle; }
70                 }
71
72                 internal virtual void OnMenuChanged (EventArgs e)
73                 {
74                         EventHandler eh = (EventHandler)(Events [MenuChangedEvent]);
75                         if (eh != null)
76                                 eh (this, e);
77                 }
78
79                 [BrowsableAttribute(false)]
80                 [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
81                 public virtual bool IsParent {
82                         get {
83                                 if (menu_items != null && menu_items.Count > 0)
84                                         return true;
85                                 else
86                                         return false;
87                         }
88                 }
89
90                 [BrowsableAttribute(false)]
91                 [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
92                 public MenuItem MdiListItem {
93                         get {
94                                 throw new NotImplementedException ();
95                         }
96                 }
97
98                 [BrowsableAttribute(false)]
99                 [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Content)]
100                 [MergableProperty(false)]
101                 public MenuItemCollection MenuItems {
102                         get { return menu_items; }
103                 }
104                 
105                 [BrowsableAttribute(false)]
106                 [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
107                 public string Name { 
108                         get { return control_name; } 
109                         set { control_name = value; }
110                 }
111
112                 [Localizable(false)]
113                 [Bindable(true)]
114                 [TypeConverter(typeof(StringConverter))]
115                 [DefaultValue(null)]
116                 [MWFCategory("Data")]
117                 public object Tag {
118                         get { return control_tag; }
119                         set { control_tag = value; }
120                 }
121
122                 #endregion Public Properties
123
124                 #region Private Properties
125
126                 // UIA Framework Note: Used to obtain menu bounds
127                 internal System.Drawing.Rectangle Rect {
128                         get { return rect; }
129                 }
130
131                 internal MenuItem SelectedItem  {
132                         get {
133                                 foreach (MenuItem item in MenuItems)
134                                         if (item.Selected)
135                                                 return item;
136
137                                 return null;
138                         }
139                 }
140
141                 internal int Height {
142                         get { return rect.Height; }
143                         set { rect.Height = value; }
144                 }
145
146                 internal int Width {
147                         get { return rect.Width; }
148                         set { rect.Width = value; }
149                 }
150
151                 internal int X {
152                         get { return rect.X; }
153                         set { rect.X = value; }
154                 }
155
156                 internal int Y {
157                         get { return rect.Y; }
158                         set { rect.Y = value; }
159                 }
160
161                 internal MenuTracker Tracker {
162                         get {
163                                 Menu top = this;
164                                 while (top.parent_menu != null)
165                                         top = top.parent_menu;
166
167                                 return top.tracker;
168                         }
169                 }
170                 #endregion Private Properties
171
172                 #region Public Methods
173
174                 protected void CloneMenu (Menu menuSrc)
175                 {
176                         Dispose (true);
177
178                         menu_items = new MenuItemCollection (this);
179
180                         for (int i = 0; i < menuSrc.MenuItems.Count ; i++)
181                                 menu_items.Add (menuSrc.MenuItems [i].CloneMenu ());
182                 }
183
184                 protected virtual IntPtr CreateMenuHandle ()
185                 {
186                         return IntPtr.Zero;
187                 }
188
189                 protected override void Dispose (bool disposing)
190                 {
191                         if (disposing) {
192                                 if (menu_items != null) {
193                                         // MenuItem.Dispose removes the item from the list
194                                         while (menu_items.Count > 0) {
195                                                 menu_items [0].Dispose ();
196                                         }
197                                 }
198                                 if (menu_handle != IntPtr.Zero) {
199                                         menu_handle = IntPtr.Zero;
200                                 }
201                         }
202                 }
203
204                 // From Microsoft documentation is impossible to guess that 
205                 // this method is supossed to do
206                 //
207                 // update: according to MS documentation, first parameter is on of this
208                 // constant values FindHandle or FindShortcut, value depends from what
209                 // you what to search, by shortcut or handle. FindHandle and FindShortcut
210                 // is a constant fields and was defined for this class.  
211                 public MenuItem FindMenuItem (int type, IntPtr value)
212                 {
213                         return null;
214                 }
215
216                 protected int FindMergePosition (int mergeOrder)
217                 {
218                         int cnt = MenuItems.Count, cur, pos;
219                         
220                         for (pos = 0; pos < cnt; ) {
221                                 cur = (pos + cnt) /2;
222                                 if (MenuItems[cur].MergeOrder > mergeOrder) {
223                                         cnt = cur;
224                                 } else  {
225                                         pos = cur +1;
226                                 }
227                         }
228                         
229                         return pos;
230                 }
231
232                 public ContextMenu GetContextMenu ()
233                 {
234                         for (Menu item = this; item != null; item = item.parent_menu) {
235                                 if (item is ContextMenu) {
236                                         return (ContextMenu) item;
237                                 }
238                         }
239                         
240                         return null;
241                 }
242
243                 public MainMenu GetMainMenu ()
244                 {                               
245                         for (Menu item = this; item != null; item = item.parent_menu) {
246                                 if (item is MainMenu) {
247                                         return (MainMenu) item;
248                                 }                               
249                         }
250                         
251                         return null;
252                 }
253
254                 internal virtual void InvalidateItem (MenuItem item)
255                 {
256                         if (Wnd != null)
257                                 Wnd.Invalidate (item.bounds);
258                 }
259
260                 public virtual void MergeMenu (Menu menuSrc)
261                 {
262                         if (menuSrc == this)
263                                 throw new ArgumentException ("The menu cannot be merged with itself");
264                         
265                         if (menuSrc == null)
266                                 return;
267                                 
268                         for (int i = 0; i < menuSrc.MenuItems.Count; i++) {
269                                 
270                                 MenuItem sourceitem = menuSrc.MenuItems[i];
271                                 
272                                 switch (sourceitem.MergeType) {
273                                         case MenuMerge.Remove:  // Item not included
274                                                 break;
275                                                 
276                                         case MenuMerge.Add:
277                                         {
278                                                 int pos = FindMergePosition (sourceitem.MergeOrder);                                            
279                                                 MenuItems.Add (pos, sourceitem.CloneMenu ());
280                                                 break;                                  
281                                         }
282                                         
283                                         case MenuMerge.Replace:
284                                         case MenuMerge.MergeItems:
285                                         {
286                                                 for (int pos = FindMergePosition (sourceitem.MergeOrder-1); pos <= MenuItems.Count; pos++) {
287                                                         
288                                                         if  ((pos >= MenuItems.Count) || (MenuItems[pos].MergeOrder != sourceitem.MergeOrder)) {
289                                                                 MenuItems.Add (pos, sourceitem.CloneMenu ());
290                                                                 break;
291                                                         }
292                                                         
293                                                         MenuItem mergeitem = MenuItems[pos];
294                                                         
295                                                         if (mergeitem.MergeType != MenuMerge.Add) {
296                                                                 if ((sourceitem.MergeType == MenuMerge.MergeItems) && (mergeitem.MergeType == MenuMerge.MergeItems)) {
297                                                                         mergeitem.MergeMenu (sourceitem);
298                                                                 } else {
299                                                                         MenuItems.Remove (sourceitem);
300                                                                         MenuItems.Add (pos, sourceitem.CloneMenu ());
301                                                                 }
302                                                                 break;
303                                                         }
304                                                 }
305                                                 
306                                                 break;
307                                         }
308                                         
309                                         default:
310                                                 break;
311                                 }                       
312                         }               
313                 }
314
315                 protected internal virtual bool ProcessCmdKey (ref Message msg, Keys keyData)
316                 {
317                         if (tracker == null)
318                                 return false;
319                         return tracker.ProcessKeys (ref msg, keyData);
320                 }
321
322                 public override string ToString ()
323                 {
324                         return base.ToString () + ", Items.Count: " + MenuItems.Count;
325                 }
326
327                 #endregion Public Methods
328                 static object MenuChangedEvent = new object ();
329
330                 // UIA Framework Note: Used to track changes in MenuItemCollection
331                 internal event EventHandler MenuChanged {
332                         add { Events.AddHandler (MenuChangedEvent, value); }
333                         remove { Events.RemoveHandler (MenuChangedEvent, value); }
334                 }
335
336                 [ListBindable(false)]
337                 public class MenuItemCollection : IList, ICollection, IEnumerable
338                 {
339                         private Menu owner;
340                         private ArrayList items = new ArrayList ();
341
342                         public MenuItemCollection (Menu owner)
343                         {
344                                 this.owner = owner;
345                         }
346
347                         #region Public Properties
348
349                         public int Count {
350                                 get { return items.Count;}
351                         }
352
353                         public bool IsReadOnly {
354                                 get { return false; }
355                         }
356
357                         bool ICollection.IsSynchronized {
358                                 get { return false;}
359                         }
360
361                         object ICollection.SyncRoot {
362                                 get { return this;}
363                         }
364
365                         bool IList.IsFixedSize {
366                                 get { return false;}
367                         }
368
369                         public virtual MenuItem this [int index] {
370                                 get {
371                                         if (index < 0 || index >= Count)
372                                                 throw new ArgumentOutOfRangeException ("Index of out range");
373
374                                         return (MenuItem) items[index];
375                                 }
376                         }
377
378                         public virtual MenuItem this [string key] {
379                                 get {
380                                         if (string.IsNullOrEmpty (key))
381                                                 return null;
382                                                 
383                                         foreach (MenuItem m in items)
384                                                 if (string.Compare (m.Name, key, true) == 0)
385                                                         return m;
386                                                         
387                                         return null;
388                                 }
389                         }
390
391                         object IList.this[int index] {
392                                 get { return items[index]; }
393                                 set { throw new NotSupportedException (); }
394                         }
395
396                         #endregion Public Properties
397
398                         #region Public Methods
399
400                         public virtual int Add (MenuItem item)
401                         {
402                                 if (item.Parent != null)
403                                         item.Parent.MenuItems.Remove (item);
404                                 
405                                 items.Add (item);
406                                 item.Index = items.Count - 1;
407                                 UpdateItem (item);
408                                 
409                                 owner.OnMenuChanged (EventArgs.Empty);
410                                 if (owner.parent_menu != null)
411                                         owner.parent_menu.OnMenuChanged (EventArgs.Empty);
412                                 return items.Count - 1;
413                         }
414
415                         internal void AddNoEvents (MenuItem mi)
416                         {
417                                 if (mi.Parent != null)
418                                         mi.Parent.MenuItems.Remove (mi);
419                                 
420                                 items.Add (mi);
421                                 mi.Index = items.Count - 1;
422                                 mi.parent_menu = owner;
423                         }
424
425                         public virtual MenuItem Add (string caption)
426                         {
427                                 MenuItem item = new MenuItem (caption);
428                                 Add (item);
429                                 return item;
430                         }
431
432                         public virtual int Add (int index, MenuItem item)
433                         {
434                                 if (index < 0 || index > Count)
435                                         throw new ArgumentOutOfRangeException ("Index of out range");
436
437                                 ArrayList new_items = new ArrayList (Count + 1);
438
439                                 for (int i = 0; i < index; i++)
440                                         new_items.Add (items[i]);
441
442                                 new_items.Add (item);
443
444                                 for (int i = index; i < Count; i++)
445                                         new_items.Add (items[i]);
446
447                                 items = new_items;
448                                 UpdateItemsIndices ();                          
449                                 UpdateItem (item);
450
451                                 return index;
452                         }
453
454                         private void UpdateItem (MenuItem mi)
455                         {
456                                 mi.parent_menu = owner;
457                                 owner.OnMenuChanged (EventArgs.Empty);
458                                 if (owner.parent_menu != null)
459                                         owner.parent_menu.OnMenuChanged (EventArgs.Empty);
460                                 if (owner.Tracker != null)
461                                         owner.Tracker.AddShortcuts (mi);
462                         }
463
464                         internal void Insert (int index, MenuItem mi)
465                         {
466                                 if (index < 0 || index > Count)
467                                         throw new ArgumentOutOfRangeException ("Index of out range");
468                                 
469                                 items.Insert (index, mi);
470                                 
471                                 UpdateItemsIndices ();
472                                 UpdateItem (mi);
473                         }
474
475                         public virtual MenuItem Add (string caption, EventHandler onClick)
476                         {
477                                 MenuItem item = new MenuItem (caption, onClick);
478                                 Add (item);
479
480                                 return item;
481                         }
482
483                         public virtual MenuItem Add (string caption, MenuItem[] items)
484                         {
485                                 MenuItem item = new MenuItem (caption, items);
486                                 Add (item);
487
488                                 return item;
489                         }
490
491                         public virtual void AddRange (MenuItem[] items)
492                         {
493                                 if (items == null)
494                                         throw new ArgumentNullException ("items");
495
496                                 foreach (MenuItem mi in items)
497                                         Add (mi);
498                         }
499
500                         public virtual void Clear ()
501                         {
502                                 MenuTracker tracker = owner.Tracker;
503                                 foreach (MenuItem item in items) {
504                                         if (tracker != null)
505                                                 tracker.RemoveShortcuts (item);
506                                         item.parent_menu = null;
507                                 }
508                                 items.Clear ();
509                                 owner.OnMenuChanged (EventArgs.Empty);
510                         }
511
512                         public bool Contains (MenuItem value)
513                         {
514                                 return items.Contains (value);
515                         }
516
517                         public virtual bool ContainsKey (string key)
518                         {
519                                 return !(this[key] == null);
520                         }
521
522                         public void CopyTo (Array dest, int index)
523                         {
524                                 items.CopyTo (dest, index);
525                         }
526
527                         public MenuItem[] Find (string key, bool searchAllChildren)
528                         {
529                                 if (string.IsNullOrEmpty (key))
530                                         throw new ArgumentNullException ("key");
531                                         
532                                 List<MenuItem> list = new List<MenuItem> ();
533                                 
534                                 foreach (MenuItem m in items)
535                                         if (string.Compare (m.Name, key, true) == 0)
536                                                 list.Add (m);
537                                 
538                                 if (searchAllChildren)
539                                         foreach (MenuItem m in items)
540                                                 list.AddRange (m.MenuItems.Find (key, true));
541                                                 
542                                 return list.ToArray ();
543                         }
544
545                         public IEnumerator GetEnumerator ()
546                         {
547                                 return items.GetEnumerator ();
548                         }
549
550                         int IList.Add (object value)
551                         {
552                                 return Add ((MenuItem)value);
553                         }
554
555                         bool IList.Contains (object value)
556                         {
557                                 return Contains ((MenuItem)value);
558                         }
559
560                         int IList.IndexOf (object value)
561                         {
562                                 return IndexOf ((MenuItem)value);
563                         }
564
565                         void IList.Insert (int index, object value)
566                         {
567                                 Insert (index, (MenuItem) value);
568                         }
569
570                         void IList.Remove (object value)
571                         {
572                                 Remove ((MenuItem) value);
573                         }
574
575                         public int IndexOf (MenuItem value)
576                         {
577                                 return items.IndexOf (value);
578                         }
579
580                         public virtual int IndexOfKey (string key)
581                         {
582                                 if (string.IsNullOrEmpty (key))
583                                         return -1;
584                                         
585                                 return IndexOf (this[key]);
586                         }
587
588                         public virtual void Remove (MenuItem item)
589                         {
590                                 RemoveAt (item.Index);
591                         }
592
593                         public virtual void RemoveAt (int index)
594                         {
595                                 if (index < 0 || index >= Count)
596                                         throw new ArgumentOutOfRangeException ("Index of out range");
597
598                                 MenuItem item = (MenuItem) items [index];
599                                 MenuTracker tracker = owner.Tracker;
600                                 if (tracker != null)
601                                         tracker.RemoveShortcuts (item);
602                                 item.parent_menu = null;
603
604                                 items.RemoveAt (index);
605
606                                 UpdateItemsIndices ();
607                                 owner.OnMenuChanged (EventArgs.Empty);
608                         }
609
610                         public virtual void RemoveByKey (string key)
611                         {
612                                 Remove (this[key]);
613                         }
614
615                         #endregion Public Methods
616
617                         #region Private Methods
618
619                         private void UpdateItemsIndices ()
620                         {
621                                 for (int i = 0; i < Count; i++) // Recalculate indeces
622                                         ((MenuItem)items[i]).Index = i;
623                         }
624
625                         #endregion Private Methods
626                 }
627         }
628 }
629
630