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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004-2005 Novell, Inc.
23 // Jordi Mas i Hernandez, jordi@ximian.com
29 using System.Collections;
31 namespace System.Windows.Forms
35 This class mimics the Win32 API Menu functionality
37 When writing this code the Wine project was of great help to
38 understand the logic behind some Win32 issues. Thanks to them. Jordi,
40 internal class MenuAPI
42 static ArrayList menu_list = new ArrayList ();
46 public MF Flags; // Menu flags (MF_POPUP, MF_SYSMENU)
47 public int X; // Used in MenuBar only
48 public int Y; // Used in MenuBar only
49 public int Width; // Width of the whole menu
50 public int Height; // Height of the whole menu
51 public Control Wnd; // In a Popup menu is the PopupWindow and in a MenuBar the Form
52 public ArrayList items; // Array of menu items
53 public int FocusedItem; // Currently focused item
54 public IntPtr hParent;
55 public MENUITEM SelectedItem; // Currently selected item
57 public bool bTracking;
58 public Menu menu; // SWF.Menu
60 public MENU (Menu menu_obj)
63 hParent = IntPtr.Zero;
64 items = new ArrayList ();
66 Width = Height = FocusedItem = X = Y = 0;
76 public Rectangle rect;
78 public IntPtr hSubMenu;
79 public int pos; /* Position in the menuitems array*/
85 rect = new Rectangle ();
92 public IntPtr hCurrentMenu;
93 public IntPtr hTopMenu;
97 hCurrentMenu = hTopMenu = IntPtr.Zero;
101 public enum MenuMouseEvent
107 internal enum ItemNavigation
122 MF_BYPOSITION = 0x400,
123 MF_SEPARATOR = 0x800,
129 MF_USECHECKBITMAPS = 0x200,
132 MF_OWNERDRAW = 0x100,
134 MF_MENUBARBREAK = 0x20,
141 MF_RIGHTJUSTIFY = 0x4000,
142 MF_MENUBAR = 0x8000 // Internal
150 static public IntPtr StoreMenuID (MENU menu)
152 int id = menu_list.Add (menu);
153 return (IntPtr)(id + 1);
156 static public MENU GetMenuFromID (IntPtr ptr)
161 if (menu_list[id] == null) // It has been delete it
164 return (MENU) menu_list[id];
167 static public IntPtr CreateMenu (Menu menu_obj)
169 MENU menu = new MENU (menu_obj);
170 return StoreMenuID (menu);
173 static public IntPtr CreatePopupMenu (Menu menu_obj)
175 MENU popMenu = new MENU (menu_obj);
176 popMenu.Flags |= MF.MF_POPUP;
177 return StoreMenuID (popMenu);
180 static public int InsertMenuItem (IntPtr hMenu, int uItem, bool fByPosition, MenuItem item, ref IntPtr hSubMenu)
184 if (fByPosition == false)
185 throw new NotImplementedException ();
187 MENU menu = GetMenuFromID (hMenu);
188 if ((uint)uItem > menu.items.Count)
189 uItem = menu.items.Count;
191 MENUITEM menu_item = new MENUITEM ();
192 menu_item.item = item;
195 menu_item.hSubMenu = CreatePopupMenu (menu_item.item);
196 MENU submenu = GetMenuFromID (menu_item.hSubMenu);
197 submenu.hParent = hMenu;
200 menu_item.hSubMenu = IntPtr.Zero;
202 hSubMenu = menu_item.hSubMenu;
203 id = menu.items.Count;
204 menu_item.pos = menu.items.Count;
205 menu.items.Insert (uItem, menu_item);
210 // The Point object contains screen coordinates
211 static public bool TrackPopupMenu (IntPtr hTopMenu, IntPtr hMenu, Point pnt, bool bMenubar, Control Wnd)
213 TRACKER tracker = new TRACKER ();
218 if (hMenu == IntPtr.Zero) // No submenus to track
221 menu = GetMenuFromID (hMenu);
223 menu.Wnd = new PopUpWindow (hMenu, tracker);
224 tracker.hCurrentMenu = hMenu;
225 tracker.hTopMenu = hTopMenu;
227 MENUITEM select_item = GetNextItem (hMenu, ItemNavigation.First);
229 if (select_item != null) {
230 MenuAPI.SelectItem (hMenu, select_item, false, tracker);
233 menu.Wnd.Location = menu.Wnd.PointToClient (pnt);
235 if (menu.menu.IsDirty) {
237 menu.menu.CreateItems ();
238 ((PopUpWindow)menu.Wnd).RefreshItems ();
239 menu.menu.IsDirty = false;
241 ((PopUpWindow)menu.Wnd).ShowWindow ();
243 while ((menu.Wnd != null) && menu.Wnd.Visible && no_quit) {
244 no_quit = XplatUI.GetMessage(ref msg, IntPtr.Zero, 0, 0);
245 XplatUI.TranslateMessage(ref msg);
246 XplatUI.DispatchMessage(ref msg);
253 if (menu.Wnd == null) {
265 static public Point ClientAreaPointToScreen (MENU menu, Point pnt)
276 XplatUI.MenuToScreen(menu.Wnd.window.Handle, ref x, ref y);
278 rslt = new Point(x, y);
283 static public Point ClientAreaPointToClient (MENU menu, Point pnt)
292 XplatUI.ScreenToMenu(menu.Wnd.window.Handle, ref x, ref y);
294 rslt = new Point(x, y);
301 static public MENUITEM FindItemByCoords (IntPtr hMenu, Point pt)
303 MENU menu = GetMenuFromID (hMenu);
305 for (int i = 0; i < menu.items.Count; i++) {
306 MENUITEM item = (MENUITEM) menu.items[i];
307 if (item.rect.Contains (pt)) {
315 // Get the current selected item
316 static public MENUITEM GetSelected (IntPtr hMenu)
318 MENU menu = GetMenuFromID (hMenu);
322 for (int i = 0; i < menu.items.Count; i++) {
323 it = (MENUITEM) menu.items[i];
324 if ((it.item.Status & DrawItemState.Selected) == DrawItemState.Selected) {
332 static internal void DrawMenuBar (IntPtr hMenu)
334 MENU menu = GetMenuFromID (hMenu);
335 DrawMenuBar (hMenu, new Rectangle (menu.X, menu.Y, menu.Width, menu.Height));
339 static internal void DrawMenuBar (IntPtr hMenu, Rectangle rect)
342 MENU menu = GetMenuFromID (hMenu);
346 rect.Height = menu.Height;
348 g = XplatUI.GetMenuDC(menu.Wnd.window.Handle, IntPtr.Zero);
349 ThemeEngine.Current.DrawMenuBar (g, hMenu, rect);
350 XplatUI.ReleaseMenuDC(menu.Wnd.window.Handle, g);
353 static public void UnSelectItem (IntPtr hMenu, MENUITEM item)
355 MENU menu = GetMenuFromID (hMenu);
360 item.item.Status = item.item.Status &~ DrawItemState.Selected;
365 menu.Wnd.Invalidate (item.rect);
369 // Select the item and unselect the previous selecte item
370 static public void SelectItem (IntPtr hMenu, MENUITEM item, bool execute, TRACKER tracker)
372 MENU menu = GetMenuFromID (hMenu);
373 MENUITEM previous_selitem = GetSelected (hMenu);
375 /* Already selected */
376 if (previous_selitem != null && item.rect == previous_selitem.rect) {
380 UnSelectItem (hMenu, previous_selitem);
382 // If the previous item had subitems, hide them
383 if (previous_selitem != null && previous_selitem.item.IsPopup)
384 HideSubPopups (hMenu);
386 if (tracker.hCurrentMenu != hMenu) {
387 menu.Wnd.Capture = true;
388 tracker.hCurrentMenu = hMenu;
391 menu.SelectedItem = item;
392 item.item.Status |= DrawItemState.Selected;
397 menu.Wnd.Invalidate (item.rect);
400 item.item.PerformSelect ();
403 ExecFocusedItem (hMenu, item, tracker);
407 // Used when the user executes the action of an item (press enter, shortcut)
408 // or a sub-popup menu has to be shown
409 static public void ExecFocusedItem (IntPtr hMenu, MENUITEM item, TRACKER tracker)
411 if (item.item.Enabled == false)
414 if (item.item.IsPopup) {
415 ShowSubPopup (hMenu, item.hSubMenu, item, tracker);
422 // Create a popup window and show it or only show it if it is already created
423 static public void ShowSubPopup (IntPtr hParent, IntPtr hMenu, MENUITEM item, TRACKER tracker)
425 MENU menu = GetMenuFromID (hMenu);
426 Point pnt = new Point ();
428 if (item.item.Enabled == false)
431 MENU menu_parent = GetMenuFromID (hParent);
432 ((PopUpWindow)menu_parent.Wnd).LostFocus ();
433 tracker.hCurrentMenu = hMenu;
435 if (menu.Wnd != null)
438 menu.Wnd = new PopUpWindow (hMenu, tracker);
440 pnt.X = item.rect.X + item.rect.Width;
441 pnt.Y = item.rect.Y + 1;
442 pnt = menu_parent.Wnd.PointToScreen (pnt);
443 menu.Wnd.Location = pnt;
445 MENUITEM select_item = GetNextItem (hMenu, ItemNavigation.First);
447 if (select_item != null)
448 MenuAPI.SelectItem (hMenu, select_item, false, tracker);
450 ((PopUpWindow)menu.Wnd).ShowWindow ();
453 /* Hides all the submenus open in a menu */
454 static public void HideSubPopups (IntPtr hMenu)
456 MENU menu = GetMenuFromID (hMenu);
459 for (int i = 0; i < menu.items.Count; i++) {
460 item = (MENUITEM) menu.items[i];
461 if (!item.item.IsPopup)
464 MENU sub_menu = GetMenuFromID (item.hSubMenu);
466 if (sub_menu.Wnd != null) {
467 HideSubPopups (item.hSubMenu);
468 ((PopUpWindow)sub_menu.Wnd).Hide ();
473 static public void DestroyMenu (IntPtr hMenu)
475 if (hMenu == IntPtr.Zero)
478 MENU menu = GetMenuFromID (hMenu);
481 for (int i = 0; i < menu.items.Count; i++) {
482 item = (MENUITEM) menu.items[i];
483 if (item.item.IsPopup) {
484 MENU sub_menu = GetMenuFromID (item.hSubMenu);
485 if (sub_menu != null && sub_menu.Wnd != null)
486 HideSubPopups (item.hSubMenu);
488 DestroyMenu (item.hSubMenu);
492 // Do not destroy the window of a Menubar
493 if (menu.Wnd != null && menu.bMenubar == false) {
494 ((PopUpWindow)menu.Wnd).Dispose ();
498 /* Unreference from the array list */
499 menu_list[((int)hMenu)-1] = null;
502 // Find item by screen coordinates
503 static public bool FindSubItemByCoord (IntPtr hMenu, Point pnt, ref IntPtr hMenuItem, ref MENUITEM itemfound)
507 MENU menu = GetMenuFromID (hMenu);
510 for (int i = 0; i < menu.items.Count; i++) {
511 item = (MENUITEM) menu.items[i];
513 if (item.item.IsPopup)
514 if (FindSubItemByCoord (item.hSubMenu, pnt, ref hMenuItem, ref itemfound) == true)
517 if (menu.Wnd == null) // Menu has not been created yet
521 pnt_client = menu.Wnd.PointToScreen (new Point (item.rect.X, item.rect.Y));
522 rect.X = pnt_client.X;
523 rect.Y = pnt_client.Y;
525 if (rect.Contains (pnt) == true) {
535 static public void SetMenuBarWindow (IntPtr hMenu, Control wnd)
537 MENU menu = GetMenuFromID (hMenu);
539 menu.bMenubar = true;
542 static private void MenuBarMove (IntPtr hMenu, MENUITEM item, TRACKER tracker)
544 MENU menu = GetMenuFromID (hMenu);
545 Point pnt = new Point (item.rect.X, item.rect.Y + item.rect.Height + 1);
546 pnt = ClientAreaPointToScreen (menu, pnt);
547 MenuAPI.SelectItem (hMenu, item, false, tracker);
548 HideSubPopups (tracker.hCurrentMenu);
549 tracker.hCurrentMenu = hMenu;
550 MenuAPI.TrackPopupMenu (hMenu, item.hSubMenu, pnt, false, null);
553 // Function that process all menubar mouse events. Coordinates in screen position
554 static public void TrackBarMouseEvent (IntPtr hMenu, Control wnd, MouseEventArgs e, MenuMouseEvent eventype, TRACKER tracker)
556 MENU menu = GetMenuFromID (hMenu);
559 case MenuMouseEvent.Down: {
560 Point pnt = new Point (e.X, e.Y);
561 pnt = ClientAreaPointToClient (menu, pnt);
563 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, pnt);
566 MENU top_menu = GetMenuFromID (tracker.hTopMenu);
568 top_menu.bTracking = true;
569 MenuBarMove (hMenu, item, tracker);
572 item.item.PerformClick ();
579 case MenuMouseEvent.Move: {
581 if (tracker.hTopMenu != IntPtr.Zero && tracker.hCurrentMenu != IntPtr.Zero) {
583 MENU top_menu = GetMenuFromID (tracker.hTopMenu);
585 if (top_menu.bTracking == false)
588 Point pnt = new Point (e.X, e.Y);
589 //pnt = menu.Wnd.PointToClient (pnt);
590 pnt = ClientAreaPointToClient (menu, pnt);
592 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, pnt);
594 if (item != null && menu.SelectedItem != item)
595 MenuBarMove (hMenu, item, tracker);
605 static public MENUITEM FindItemByKey (IntPtr hMenu, IntPtr key)
607 MENU menu = GetMenuFromID (hMenu);
610 char key_char = (char ) (key.ToInt32() & 0xff);
611 key_char = Char.ToUpper (key_char);
613 for (int i = 0; i < menu.items.Count; i++) {
614 item = (MENUITEM) menu.items[i];
616 if (item.item.Mnemonic == '\0')
619 if (item.item.Mnemonic == key_char)
626 // Get the next or previous selectable item on a menu
627 static public MENUITEM GetNextItem (IntPtr hMenu, ItemNavigation navigation)
629 MENU menu = GetMenuFromID (hMenu);
631 bool selectable_items = false;
634 // Check if there is at least a selectable item
635 for (int i = 0; i < menu.items.Count; i++) {
636 item = (MENUITEM)menu.items[i];
637 if (item.item.Separator == false && item.item.Visible == true) {
638 selectable_items = true;
643 if (selectable_items == false)
646 switch (navigation) {
647 case ItemNavigation.First: {
650 /* Next item that is not separator and it is visible*/
651 for (; pos < menu.items.Count; pos++) {
652 item = (MENUITEM)menu.items[pos];
653 if (item.item.Separator == false && item.item.Visible == true)
657 if (pos >= menu.items.Count) { /* Jump at the start of the menu */
659 /* Next item that is not separator and it is visible*/
660 for (; pos < menu.items.Count; pos++) {
661 item = (MENUITEM)menu.items[pos];
662 if (item.item.Separator == false && item.item.Visible == true)
670 case ItemNavigation.Last: { // Not used
674 case ItemNavigation.Next: {
676 if (menu.SelectedItem != null)
677 pos = menu.SelectedItem.pos;
679 /* Next item that is not separator and it is visible*/
680 for (pos++; pos < menu.items.Count; pos++) {
681 item = (MENUITEM)menu.items[pos];
682 if (item.item.Separator == false && item.item.Visible == true)
686 if (pos >= menu.items.Count) { /* Jump at the start of the menu */
688 /* Next item that is not separator and it is visible*/
689 for (; pos < menu.items.Count; pos++) {
690 item = (MENUITEM)menu.items[pos];
691 if (item.item.Separator == false && item.item.Visible == true)
698 case ItemNavigation.Previous: {
700 if (menu.SelectedItem != null)
701 pos = menu.SelectedItem.pos;
703 /* Previous item that is not separator and it is visible*/
704 for (pos--; pos >= 0; pos--) {
705 item = (MENUITEM)menu.items[pos];
706 if (item.item.Separator == false && item.item.Visible == true)
710 if (pos < 0 ) { /* Jump at the end of the menu*/
711 pos = menu.items.Count - 1;
712 /* Previous item that is not separator and it is visible*/
713 for (; pos >= 0; pos--) {
714 item = (MENUITEM)menu.items[pos];
715 if (item.item.Separator == false && item.item.Visible == true)
727 return (MENUITEM)menu.items[pos];
730 static public bool ProcessKeys (IntPtr hMenu, ref Message msg, Keys keyData, TRACKER tracker)
732 MENU menu = GetMenuFromID (hMenu);
737 item = GetNextItem (hMenu, ItemNavigation.Previous);
739 MenuAPI.SelectItem (hMenu, item, false, tracker);
745 item = GetNextItem (hMenu, ItemNavigation.Next);
748 MenuAPI.SelectItem (hMenu, item, false, tracker);
752 /* Menubar selects and opens next. Popups next or open*/
755 // Try to Expand popup first
756 if (menu.SelectedItem.item.IsPopup) {
757 ShowSubPopup (hMenu, menu.SelectedItem.hSubMenu, menu.SelectedItem, tracker);
761 if (menu.hParent != IntPtr.Zero)
762 parent = GetMenuFromID (menu.hParent);
764 if (parent != null && parent.bMenubar == true) {
765 MENUITEM select_item = GetNextItem (menu.hParent, ItemNavigation.Next);
766 MenuBarMove (menu.hParent, select_item, tracker);
775 // Try to Collapse popup first
776 if (menu.SelectedItem.item.IsPopup) {
781 if (menu.hParent != IntPtr.Zero)
782 parent = GetMenuFromID (menu.hParent);
784 if (parent != null && parent.bMenubar == true) {
785 MENUITEM select_item = GetNextItem (menu.hParent, ItemNavigation.Previous);
786 MenuBarMove (menu.hParent, select_item, tracker);
794 MenuAPI.ExecFocusedItem (hMenu, menu.SelectedItem, tracker);
802 /* Try if it is a menu hot key */
803 item = MenuAPI.FindItemByKey (hMenu, msg.WParam);
806 MenuAPI.SelectItem (hMenu, item, false, tracker);
819 internal class PopUpWindow : Control
821 private IntPtr hMenu;
822 private MenuAPI.TRACKER tracker;
824 public PopUpWindow (IntPtr hMenu, MenuAPI.TRACKER tracker): base ()
827 this.tracker = tracker;
828 MouseDown += new MouseEventHandler (OnMouseDownPUW);
829 MouseMove += new MouseEventHandler (OnMouseMovePUW);
830 MouseUp += new MouseEventHandler (OnMouseUpPUW);
831 Paint += new PaintEventHandler (OnPaintPUW);
832 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
833 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
837 protected override CreateParams CreateParams
840 CreateParams cp = base.CreateParams;
841 cp.Caption = "Menu PopUp";
842 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP));
843 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
848 public void ShowWindow ()
856 public new void LostFocus ()
861 protected override void OnResize (EventArgs e)
866 private void OnPaintPUW (Object o, PaintEventArgs pevent)
868 ThemeEngine.Current.DrawPopupMenu (pevent.Graphics, hMenu, pevent.ClipRectangle, ClientRectangle);
871 public void HideWindow ()
875 MenuAPI.MENU top_menu = MenuAPI.GetMenuFromID (tracker.hTopMenu);
876 top_menu.bTracking = false;
878 MenuAPI.HideSubPopups (tracker.hTopMenu);
880 if (top_menu.bMenubar) {
881 MenuAPI.MENUITEM item = MenuAPI.GetSelected (tracker.hTopMenu);
884 MenuAPI.UnSelectItem (tracker.hTopMenu, item);
886 } else { // Context Menu
887 ((PopUpWindow)top_menu.Wnd).Hide ();
891 private void OnMouseDownPUW (object sender, MouseEventArgs e)
893 /* Click outside the client area*/
894 if (ClientRectangle.Contains (e.X, e.Y) == false) {
899 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
902 MenuAPI.ExecFocusedItem (hMenu, item, tracker);
906 private void OnMouseUpPUW (object sender, MouseEventArgs e)
908 /* Click in an item area*/
909 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
912 if (item.item.Enabled) {
915 item.item.PerformClick ();
919 private void OnMouseMovePUW (object sender, MouseEventArgs e)
921 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
924 MenuAPI.SelectItem (hMenu, item, false, tracker);
927 MenuAPI.MENU menu_parent = null;
929 if (tracker.hTopMenu != IntPtr.Zero)
930 menu_parent = MenuAPI.GetMenuFromID (tracker.hTopMenu);
932 if (menu_parent == null)
935 if (menu_parent.bMenubar) {
936 MenuAPI.TrackBarMouseEvent (tracker.hTopMenu,
937 this, new MouseEventArgs(e.Button, e.Clicks, MousePosition.X, MousePosition.Y, e.Delta),
938 MenuAPI.MenuMouseEvent.Move, tracker);
941 IntPtr hMenuItem = IntPtr.Zero;
942 MenuAPI.MENUITEM item_found = null;
944 if (MenuAPI.FindSubItemByCoord (tracker.hTopMenu, MousePosition, ref hMenuItem, ref item_found) == false)
947 MenuAPI.SelectItem (hMenuItem, item_found, false, tracker);
951 protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
953 return MenuAPI.ProcessKeys (hMenu, ref msg, keyData, tracker);
956 protected override void CreateHandle ()
958 base.CreateHandle ();
962 // Called when the number of items has changed
963 internal void RefreshItems ()
965 MenuAPI.MENU menu = MenuAPI.GetMenuFromID (hMenu);
966 ThemeEngine.Current.CalcPopupMenuSize (DeviceContext, hMenu);
968 if ((Location.X + menu.Width) > SystemInformation.WorkingArea.Width) {
969 Location = new Point (Location.X - menu.Width, Location.Y);
971 if ((Location.Y + menu.Height) > SystemInformation.WorkingArea.Height) {
972 Location = new Point (Location.X, Location.Y - menu.Height);
976 Height = menu.Height;