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 ();
214 MENU top_menu = GetMenuFromID (hTopMenu);
217 if (hMenu == IntPtr.Zero) // No submenus to track
220 menu = GetMenuFromID (hMenu);
222 menu.Wnd = new PopUpWindow (hMenu, tracker);
223 tracker.hCurrentMenu = hMenu;
224 tracker.hTopMenu = hTopMenu;
226 MENUITEM select_item = GetNextItem (hMenu, ItemNavigation.First);
228 if (select_item != null) {
229 MenuAPI.SelectItem (hMenu, select_item, false, tracker);
232 // Make sure the menu is always visible and does not 'leave' the screen
233 // What is menu.Width/Height? It seemed to be 0/0
234 if ((pnt.X + menu.Wnd.Width) > SystemInformation.WorkingArea.Width) {
235 pnt.X -= menu.Wnd.Width;
238 if ((pnt.X + menu.Wnd.Height) > SystemInformation.WorkingArea.Height) {
239 pnt.Y -= menu.Wnd.Height;
242 menu.Wnd.Location = menu.Wnd.PointToClient (pnt);
244 if (menu.menu.IsDirty) {
246 menu.menu.CreateItems ();
247 ((PopUpWindow)menu.Wnd).RefreshItems ();
248 menu.menu.IsDirty = false;
251 ((PopUpWindow)menu.Wnd).ShowWindow ();
255 if (menu.Wnd == null) {
267 static public Point ClientAreaPointToScreen (MENU menu, Point pnt)
278 XplatUI.MenuToScreen(menu.Wnd.window.Handle, ref x, ref y);
280 rslt = new Point(x, y);
285 static public Point ClientAreaPointToClient (MENU menu, Point pnt)
294 XplatUI.ScreenToMenu(menu.Wnd.window.Handle, ref x, ref y);
296 rslt = new Point(x, y);
303 static public MENUITEM FindItemByCoords (IntPtr hMenu, Point pt)
305 MENU menu = GetMenuFromID (hMenu);
307 for (int i = 0; i < menu.items.Count; i++) {
308 MENUITEM item = (MENUITEM) menu.items[i];
309 if (item.rect.Contains (pt)) {
317 // Get the current selected item
318 static public MENUITEM GetSelected (IntPtr hMenu)
320 MENU menu = GetMenuFromID (hMenu);
324 for (int i = 0; i < menu.items.Count; i++) {
325 it = (MENUITEM) menu.items[i];
326 if ((it.item.Status & DrawItemState.Selected) == DrawItemState.Selected) {
334 static internal void DrawMenuBar (IntPtr hMenu)
336 MENU menu = GetMenuFromID (hMenu);
337 DrawMenuBar (hMenu, new Rectangle (menu.X, menu.Y, menu.Width, menu.Height));
341 static internal void DrawMenuBar (IntPtr hMenu, Rectangle rect)
344 MENU menu = GetMenuFromID (hMenu);
348 rect.Height = menu.Height;
350 g = XplatUI.GetMenuDC(menu.Wnd.window.Handle, IntPtr.Zero);
351 ThemeEngine.Current.DrawMenuBar (g, hMenu, rect);
352 XplatUI.ReleaseMenuDC(menu.Wnd.window.Handle, g);
355 static public void UnSelectItem (IntPtr hMenu, MENUITEM item)
357 MENU menu = GetMenuFromID (hMenu);
362 item.item.Status = item.item.Status &~ DrawItemState.Selected;
367 menu.Wnd.Invalidate (item.rect);
371 // Select the item and unselect the previous selecte item
372 static public void SelectItem (IntPtr hMenu, MENUITEM item, bool execute, TRACKER tracker)
374 MENU menu = GetMenuFromID (hMenu);
375 MENUITEM previous_selitem = GetSelected (hMenu);
377 /* Already selected */
378 if (previous_selitem != null && item.rect == previous_selitem.rect) {
382 UnSelectItem (hMenu, previous_selitem);
384 // If the previous item had subitems, hide them
385 if (previous_selitem != null && previous_selitem.item.IsPopup)
386 HideSubPopups (hMenu);
388 if (tracker.hCurrentMenu != hMenu) {
389 menu.Wnd.Capture = true;
390 tracker.hCurrentMenu = hMenu;
393 menu.SelectedItem = item;
394 item.item.Status |= DrawItemState.Selected;
399 menu.Wnd.Invalidate (item.rect);
402 item.item.PerformSelect ();
405 ExecFocusedItem (hMenu, item, tracker);
409 // Used when the user executes the action of an item (press enter, shortcut)
410 // or a sub-popup menu has to be shown
411 static public void ExecFocusedItem (IntPtr hMenu, MENUITEM item, TRACKER tracker)
413 if (item.item.Enabled == false)
417 if (item.item.IsPopup) {
418 ShowSubPopup (hMenu, item.hSubMenu, item, tracker);
425 // Create a popup window and show it or only show it if it is already created
426 static public void ShowSubPopup (IntPtr hParent, IntPtr hMenu, MENUITEM item, TRACKER tracker)
428 MENU menu = GetMenuFromID (hMenu);
429 Point pnt = new Point ();
431 if (item.item.Enabled == false)
434 MENU menu_parent = GetMenuFromID (hParent);
435 ((PopUpWindow)menu_parent.Wnd).LostFocus ();
436 tracker.hCurrentMenu = hMenu;
438 if (menu.Wnd == null)
439 menu.Wnd = new PopUpWindow (hMenu, tracker);
441 pnt.X = item.rect.X + item.rect.Width;
442 pnt.Y = item.rect.Y + 1;
443 pnt = menu_parent.Wnd.PointToScreen (pnt);
444 menu.Wnd.Location = pnt;
446 MENUITEM select_item = GetNextItem (hMenu, ItemNavigation.First);
448 if (select_item != null)
449 MenuAPI.SelectItem (hMenu, select_item, false, tracker);
451 ((PopUpWindow)menu.Wnd).ShowWindow ();
454 /* Hides all the submenus open in a menu */
455 static public void HideSubPopups (IntPtr hMenu)
457 MENU menu = GetMenuFromID (hMenu);
460 for (int i = 0; i < menu.items.Count; i++) {
461 item = (MENUITEM) menu.items[i];
462 if (!item.item.IsPopup)
465 MENU sub_menu = GetMenuFromID (item.hSubMenu);
467 if (sub_menu.Wnd != null) {
468 HideSubPopups (item.hSubMenu);
469 ((PopUpWindow)sub_menu.Wnd).Hide ();
474 static public void DestroyMenu (IntPtr hMenu)
476 if (hMenu == IntPtr.Zero)
479 MENU menu = GetMenuFromID (hMenu);
482 for (int i = 0; i < menu.items.Count; i++) {
483 item = (MENUITEM) menu.items[i];
484 if (item.item.IsPopup) {
485 MENU sub_menu = GetMenuFromID (item.hSubMenu);
486 if (sub_menu != null && sub_menu.Wnd != null)
487 HideSubPopups (item.hSubMenu);
489 DestroyMenu (item.hSubMenu);
493 // Do not destroy the window of a Menubar
494 if (menu.Wnd != null && menu.bMenubar == false) {
495 ((PopUpWindow)menu.Wnd).Dispose ();
499 /* Unreference from the array list */
500 menu_list[((int)hMenu)-1] = null;
503 // Find item by screen coordinates
504 static public bool FindSubItemByCoord (IntPtr hMenu, Point pnt, ref IntPtr hMenuItem, ref MENUITEM itemfound)
508 MENU menu = GetMenuFromID (hMenu);
511 for (int i = 0; i < menu.items.Count; i++) {
512 item = (MENUITEM) menu.items[i];
514 if (item.item.IsPopup)
515 if (FindSubItemByCoord (item.hSubMenu, pnt, ref hMenuItem, ref itemfound) == true)
518 if (menu.Wnd == null) // Menu has not been created yet
522 pnt_client = menu.Wnd.PointToScreen (new Point (item.rect.X, item.rect.Y));
523 rect.X = pnt_client.X;
524 rect.Y = pnt_client.Y;
526 if (rect.Contains (pnt) == true) {
536 static public void SetMenuBarWindow (IntPtr hMenu, Control wnd)
538 MENU menu = GetMenuFromID (hMenu);
540 menu.bMenubar = true;
543 static private void MenuBarMove (IntPtr hMenu, MENUITEM item, TRACKER tracker)
545 MENU menu = GetMenuFromID (hMenu);
546 Point pnt = new Point (item.rect.X, item.rect.Y + item.rect.Height + 1);
547 pnt = ClientAreaPointToScreen (menu, pnt);
548 MenuAPI.SelectItem (hMenu, item, false, tracker);
549 HideSubPopups (tracker.hCurrentMenu);
550 tracker.hCurrentMenu = hMenu;
551 MenuAPI.TrackPopupMenu (hMenu, item.hSubMenu, pnt, false, null);
554 // Function that process all menubar mouse events. Coordinates in screen position
555 static public void TrackBarMouseEvent (IntPtr hMenu, Control wnd, MouseEventArgs e, MenuMouseEvent eventype, TRACKER tracker)
557 MENU menu = GetMenuFromID (hMenu);
560 case MenuMouseEvent.Down: {
561 Point pnt = new Point (e.X, e.Y);
562 pnt = ClientAreaPointToClient (menu, pnt);
564 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, pnt);
567 MENU top_menu = GetMenuFromID (tracker.hTopMenu);
569 top_menu.bTracking = true;
570 MenuBarMove (hMenu, item, tracker);
573 item.item.PerformClick ();
580 case MenuMouseEvent.Move: {
582 if (tracker.hTopMenu != IntPtr.Zero && tracker.hCurrentMenu != IntPtr.Zero) {
584 MENU top_menu = GetMenuFromID (tracker.hTopMenu);
586 if (top_menu.bTracking == false)
589 Point pnt = new Point (e.X, e.Y);
590 //pnt = menu.Wnd.PointToClient (pnt);
591 pnt = ClientAreaPointToClient (menu, pnt);
593 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, pnt);
595 if (item != null && menu.SelectedItem != item)
596 MenuBarMove (hMenu, item, tracker);
606 static public MENUITEM FindItemByKey (IntPtr hMenu, IntPtr key)
608 MENU menu = GetMenuFromID (hMenu);
611 char key_char = (char ) (key.ToInt32() & 0xff);
612 key_char = Char.ToUpper (key_char);
614 for (int i = 0; i < menu.items.Count; i++) {
615 item = (MENUITEM) menu.items[i];
617 if (item.item.Mnemonic == '\0')
620 if (item.item.Mnemonic == key_char)
627 // Get the next or previous selectable item on a menu
628 static public MENUITEM GetNextItem (IntPtr hMenu, ItemNavigation navigation)
630 MENU menu = GetMenuFromID (hMenu);
632 bool selectable_items = false;
635 // Check if there is at least a selectable item
636 for (int i = 0; i < menu.items.Count; i++) {
637 item = (MENUITEM)menu.items[i];
638 if (item.item.Separator == false && item.item.Visible == true) {
639 selectable_items = true;
644 if (selectable_items == false)
647 switch (navigation) {
648 case ItemNavigation.First: {
651 /* Next item that is not separator and it is visible*/
652 for (; pos < menu.items.Count; pos++) {
653 item = (MENUITEM)menu.items[pos];
654 if (item.item.Separator == false && item.item.Visible == true)
658 if (pos >= menu.items.Count) { /* Jump at the start of the menu */
660 /* Next item that is not separator and it is visible*/
661 for (; pos < menu.items.Count; pos++) {
662 item = (MENUITEM)menu.items[pos];
663 if (item.item.Separator == false && item.item.Visible == true)
671 case ItemNavigation.Last: { // Not used
675 case ItemNavigation.Next: {
677 if (menu.SelectedItem != null)
678 pos = menu.SelectedItem.pos;
680 /* Next item that is not separator and it is visible*/
681 for (pos++; pos < menu.items.Count; pos++) {
682 item = (MENUITEM)menu.items[pos];
683 if (item.item.Separator == false && item.item.Visible == true)
687 if (pos >= menu.items.Count) { /* Jump at the start of the menu */
689 /* Next item that is not separator and it is visible*/
690 for (; pos < menu.items.Count; pos++) {
691 item = (MENUITEM)menu.items[pos];
692 if (item.item.Separator == false && item.item.Visible == true)
699 case ItemNavigation.Previous: {
701 if (menu.SelectedItem != null)
702 pos = menu.SelectedItem.pos;
704 /* Previous item that is not separator and it is visible*/
705 for (pos--; pos >= 0; pos--) {
706 item = (MENUITEM)menu.items[pos];
707 if (item.item.Separator == false && item.item.Visible == true)
711 if (pos < 0 ) { /* Jump at the end of the menu*/
712 pos = menu.items.Count - 1;
713 /* Previous item that is not separator and it is visible*/
714 for (; pos >= 0; pos--) {
715 item = (MENUITEM)menu.items[pos];
716 if (item.item.Separator == false && item.item.Visible == true)
728 return (MENUITEM)menu.items[pos];
731 static public bool ProcessKeys (IntPtr hMenu, ref Message msg, Keys keyData, TRACKER tracker)
733 MENU menu = GetMenuFromID (hMenu);
738 item = GetNextItem (hMenu, ItemNavigation.Previous);
740 MenuAPI.SelectItem (hMenu, item, false, tracker);
746 item = GetNextItem (hMenu, ItemNavigation.Next);
749 MenuAPI.SelectItem (hMenu, item, false, tracker);
753 /* Menubar selects and opens next. Popups next or open*/
756 // Try to Expand popup first
757 if (menu.SelectedItem.item.IsPopup) {
758 ShowSubPopup (hMenu, menu.SelectedItem.hSubMenu, menu.SelectedItem, tracker);
762 if (menu.hParent != IntPtr.Zero)
763 parent = GetMenuFromID (menu.hParent);
765 if (parent != null && parent.bMenubar == true) {
766 MENUITEM select_item = GetNextItem (menu.hParent, ItemNavigation.Next);
767 MenuBarMove (menu.hParent, select_item, tracker);
776 // Try to Collapse popup first
777 if (menu.SelectedItem.item.IsPopup) {
782 if (menu.hParent != IntPtr.Zero)
783 parent = GetMenuFromID (menu.hParent);
785 if (parent != null && parent.bMenubar == true) {
786 MENUITEM select_item = GetNextItem (menu.hParent, ItemNavigation.Previous);
787 MenuBarMove (menu.hParent, select_item, tracker);
795 MenuAPI.ExecFocusedItem (hMenu, menu.SelectedItem, tracker);
803 /* Try if it is a menu hot key */
804 item = MenuAPI.FindItemByKey (hMenu, msg.WParam);
807 MenuAPI.SelectItem (hMenu, item, false, tracker);
820 internal class PopUpWindow : Control
822 private IntPtr hMenu;
823 private MenuAPI.TRACKER tracker;
825 public PopUpWindow (IntPtr hMenu, MenuAPI.TRACKER tracker): base ()
828 this.tracker = tracker;
829 MouseDown += new MouseEventHandler (OnMouseDownPUW);
830 MouseMove += new MouseEventHandler (OnMouseMovePUW);
831 MouseUp += new MouseEventHandler (OnMouseUpPUW);
832 Paint += new PaintEventHandler (OnPaintPUW);
833 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
834 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
838 protected override CreateParams CreateParams
841 CreateParams cp = base.CreateParams;
842 cp.Caption = "Menu PopUp";
843 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP));
844 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
849 public void ShowWindow ()
856 public new void LostFocus ()
861 protected override void OnResize (EventArgs e)
866 private void OnPaintPUW (Object o, PaintEventArgs pevent)
868 if (Width <= 0 || Height <= 0 || Visible == false)
871 Draw (pevent.ClipRectangle);
872 pevent.Graphics.DrawImage (ImageBuffer, pevent.ClipRectangle, pevent.ClipRectangle, GraphicsUnit.Pixel);
875 public void HideWindow ()
879 MenuAPI.MENU top_menu = MenuAPI.GetMenuFromID (tracker.hTopMenu);
880 top_menu.bTracking = false;
882 MenuAPI.HideSubPopups (tracker.hTopMenu);
884 if (top_menu.bMenubar) {
885 MenuAPI.MENUITEM item = MenuAPI.GetSelected (tracker.hTopMenu);
888 MenuAPI.UnSelectItem (tracker.hTopMenu, item);
890 } else { // Context Menu
891 ((PopUpWindow)top_menu.Wnd).Hide ();
895 private void OnMouseDownPUW (object sender, MouseEventArgs e)
897 /* Click outside the client area*/
898 if (ClientRectangle.Contains (e.X, e.Y) == false) {
902 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
905 MenuAPI.ExecFocusedItem (hMenu, item, tracker);
909 private void OnMouseUpPUW (object sender, MouseEventArgs e)
911 /* Click in an item area*/
912 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
915 if (item.item.Enabled) {
918 item.item.PerformClick ();
922 private void OnMouseMovePUW (object sender, MouseEventArgs e)
924 MenuAPI.MENUITEM item = MenuAPI.FindItemByCoords (hMenu, new Point (e.X, e.Y));
927 MenuAPI.SelectItem (hMenu, item, false, tracker);
930 MenuAPI.MENU menu_parent = null;
932 if (tracker.hTopMenu != IntPtr.Zero)
933 menu_parent = MenuAPI.GetMenuFromID (tracker.hTopMenu);
935 if (menu_parent == null)
938 if (menu_parent.bMenubar) {
939 MenuAPI.TrackBarMouseEvent (tracker.hTopMenu,
940 this, new MouseEventArgs(e.Button, e.Clicks, MousePosition.X, MousePosition.Y, e.Delta),
941 MenuAPI.MenuMouseEvent.Move, tracker);
944 IntPtr hMenuItem = IntPtr.Zero;
945 MenuAPI.MENUITEM item_found = null;
947 if (MenuAPI.FindSubItemByCoord (tracker.hTopMenu, MousePosition, ref hMenuItem, ref item_found) == false)
950 MenuAPI.SelectItem (hMenuItem, item_found, false, tracker);
954 protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
956 return MenuAPI.ProcessKeys (hMenu, ref msg, keyData, tracker);
959 protected override void CreateHandle ()
961 base.CreateHandle ();
965 // Called when the number of items has changed
966 internal void RefreshItems ()
968 MenuAPI.MENU menu = MenuAPI.GetMenuFromID (hMenu);
969 ThemeEngine.Current.CalcPopupMenuSize (DeviceContext, hMenu);
972 Height = menu.Height;
975 private void Draw (Rectangle clip)
977 ThemeEngine.Current.DrawPopupMenu (DeviceContext, hMenu, clip, ClientRectangle);