1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
8 // Copyright: (c) AUTHOR
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
13 // ============================================================================
14 // headers & declarations
15 // ============================================================================
21 #pragma implementation "menu.h"
22 #pragma implementation "menuitem.h"
26 #include "wx/menuitem.h"
30 #include "wx/mac/uma.h"
32 // other standard headers
33 // ----------------------
36 #if !USE_SHARED_LIBRARY
37 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
38 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
41 // the (popup) menu title has this special id
42 static const int idMenuTitle
= -2;
43 static int formerHelpMenuItems
= 0 ;
45 const short kwxMacMenuBarResource
= 1 ;
46 const short kwxMacAppleMenuId
= 1 ;
48 // ============================================================================
50 // ============================================================================
53 // Helper Functions to get Mac Menus the way they should be ;-)
56 void wxMacCtoPString(const char* theCString
, Str255 thePString
);
58 // remove inappropriate characters, if useShortcuts is false, the ampersand will not auto-generate a mac menu-shortcut
60 static void wxMacBuildMenuString(StringPtr outMacItemText
, char *outMacShortcutChar
, short *outMacModifiers
, const char *inItemName
, bool useShortcuts
)
62 char *p
= (char *) &outMacItemText
[1] ;
63 short macModifiers
= 0 ;
64 char macShortCut
= 0 ;
66 if ( useShortcuts
&& !wxApp::s_macSupportPCMenuShortcuts
)
67 useShortcuts
= false ;
69 // we have problems with a leading hypen - it will be taken as a separator
71 while ( *inItemName
== '-' )
76 switch ( *inItemName
)
78 // special characters for macintosh menus -> use some replacement
108 macShortCut
= *inItemName
;
114 // win-like accelerators
120 if (strncmp("Ctrl+", inItemName
, 5) == 0)
122 inItemName
= inItemName
+ 5;
123 macShortCut
= *inItemName
;
125 else if (strncmp("Alt+", inItemName
, 4) == 0)
127 inItemName
= inItemName
+ 4;
128 macModifiers
|= kMenuOptionModifier
;
129 macShortCut
= *inItemName
;
131 else if (strncmp("Shift+", inItemName
, 6) == 0)
133 inItemName
= inItemName
+ 6;
134 macModifiers
|= kMenuShiftModifier
;
135 macShortCut
= *inItemName
;
137 else if (strncmp("F", inItemName
, 1) == 0)
139 inItemName
+= strlen( inItemName
) ;
140 // no function keys at the moment
141 // macModifiers |= kMenuShiftModifier ;
142 // macShortCut = *inItemName ;
150 if ( *inItemName
== 0 )
161 outMacItemText
[0] = (p
- (char *)outMacItemText
) - 1;
162 if ( outMacShortcutChar
)
163 *outMacShortcutChar
= macShortCut
;
164 if ( outMacModifiers
)
165 *outMacModifiers
= macModifiers
;
168 int pos
= outMacItemText
[0] ;
169 outMacItemText
[++pos
] = '/';
170 outMacItemText
[++pos
] = toupper( macShortCut
);
171 outMacItemText
[0] = pos
;
177 // Construct a menu with optional title (then use append)
179 short wxMenu::s_macNextMenuId
= 2 ;
187 wxMacBuildMenuString( label
, NULL
, NULL
, m_title
, false );
188 m_macMenuId
= s_macNextMenuId
++;
189 wxCHECK_RET( s_macNextMenuId
< 236 , "menu ids > 235 cannot be used for submenus on mac" );
190 m_hMenu
= ::NewMenu(m_macMenuId
, label
);
194 wxLogLastError("CreatePopupMenu");
197 // if we have a title, insert it in the beginning of the menu
200 Append(idMenuTitle
, m_title
) ;
208 ::DisposeMenu(m_hMenu
);
212 WX_CLEAR_ARRAY(m_accels
);
213 #endif // wxUSE_ACCEL
218 // not available on the mac platform
223 int wxMenu::FindAccel(int id
) const
225 size_t n
, count
= m_accels
.GetCount();
226 for ( n
= 0; n
< count
; n
++ )
228 if ( m_accels
[n
]->m_command
== id
)
235 void wxMenu::UpdateAccel(wxMenuItem
*item
)
237 // find the (new) accel for this item
238 wxAcceleratorEntry
*accel
= wxGetAccelFromString(item
->GetText());
240 accel
->m_command
= item
->GetId();
243 int n
= FindAccel(item
->GetId());
244 if ( n
== wxNOT_FOUND
)
246 // no old, add new if any
250 return; // skipping RebuildAccelTable() below
254 // replace old with new or just remove the old one if no new
264 m_menuBar
->RebuildAccelTable();
268 #endif // wxUSE_ACCEL
270 // function appends a new item or submenu to the menu
271 // append a new item or submenu to the menu
272 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
274 wxASSERT_MSG( pItem
!= NULL
, "can't append NULL item to the menu" );
277 #endif // wxUSE_ACCEL
279 if ( pItem
->IsSeparator() )
281 if ( pos
== (size_t)-1 )
283 MacAppendMenu(m_hMenu
, "\p-");
287 MacInsertMenuItem(m_hMenu
, "\p-" , pos
);
292 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
293 if ( pSubMenu
!= NULL
)
296 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, "invalid submenu added");
297 pSubMenu
->m_menuParent
= this ;
298 wxMacBuildMenuString( label
, NULL
, NULL
, pItem
->GetText() ,false);
300 // hardcoded adding of the submenu combination for mac
302 int theEnd
= label
[0] + 1;
304 theEnd
= 251; // mac allows only 255 characters
305 label
[theEnd
++] = '/';
306 label
[theEnd
++] = hMenuCmd
;
307 label
[theEnd
++] = '!';
308 label
[theEnd
++] = pSubMenu
->m_macMenuId
;
309 label
[theEnd
] = 0x00;
312 if (wxMenuBar::MacGetInstalledMenuBar() == m_menuBar
)
314 ::InsertMenu( pSubMenu
->m_hMenu
, -1 ) ;
317 if ( pos
== (size_t)-1 )
319 MacAppendMenu(m_hMenu
, label
);
323 MacInsertMenuItem(m_hMenu
, label
, pos
);
329 wxMacBuildMenuString( label
, NULL
, NULL
, pItem
->GetText(), pItem
->GetId() == wxApp::s_macAboutMenuItemId
);
332 // we cannot add empty menus on mac
336 if ( pos
== (size_t)-1 )
338 MacAppendMenu(m_hMenu
, label
);
342 MacInsertMenuItem(m_hMenu
, label
, pos
);
344 if ( pItem
->GetId() == idMenuTitle
)
346 if ( pos
== (size_t)-1 )
348 UMADisableMenuItem( m_hMenu
, CountMItems( m_hMenu
) ) ;
352 UMADisableMenuItem( m_hMenu
, pos
+ 1 ) ;
357 // if we're already attached to the menubar, we must update it
360 m_menuBar
->Refresh();
365 bool wxMenu::DoAppend(wxMenuItem
*item
)
367 return wxMenuBase::DoAppend(item
) && DoInsertOrAppend(item
);
370 bool wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
372 return wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
);
375 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
377 // we need to find the items position in the child list
379 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
380 for ( pos
= 0; node
; pos
++ )
382 if ( node
->GetData() == item
)
385 node
= node
->GetNext();
388 // DoRemove() (unlike Remove) can only be called for existing item!
389 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
392 // remove the corresponding accel from the accel table
393 int n
= FindAccel(item
->GetId());
394 if ( n
!= wxNOT_FOUND
)
400 //else: this item doesn't have an accel, nothing to do
401 #endif // wxUSE_ACCEL
403 ::DeleteMenuItem( m_hMenu
, pos
+ 1);
407 // otherwise, the chane won't be visible
408 m_menuBar
->Refresh();
411 // and from internal data structures
412 return wxMenuBase::DoRemove(item
);
415 // ---------------------------------------------------------------------------
416 // accelerator helpers
417 // ---------------------------------------------------------------------------
421 // create the wxAcceleratorEntries for our accels and put them into provided
422 // array - return the number of accels we have
423 size_t wxMenu::CopyAccels(wxAcceleratorEntry
*accels
) const
425 size_t count
= GetAccelCount();
426 for ( size_t n
= 0; n
< count
; n
++ )
428 *accels
++ = *m_accels
[n
];
434 #endif // wxUSE_ACCEL
436 void wxMenu::SetTitle(const wxString
& label
)
440 wxMacBuildMenuString( title
, NULL
, NULL
, label
, false );
441 UMASetMenuTitle( m_hMenu
, title
) ;
446 void wxMenu::SetLabel(int id, const wxString& label)
450 wxMenuItem *item = FindItemForId(id) ;
454 index = MacGetIndexFromItem( item ) ;
458 if (item->GetSubMenu()==NULL)
460 wxMacBuildMenuString( maclabel , NULL , NULL , label , false );
461 ::SetMenuItemText( m_hMenu , index , maclabel ) ;
465 wxMacBuildMenuString( maclabel , NULL , NULL , label , false );
466 ::SetMenuItemText( m_hMenu , index , maclabel ) ;
468 item->SetName(label);
471 wxString wxMenu::GetLabel(int Id) const
473 wxMenuItem *pItem = FindItemForId(Id) ;
474 return pItem->GetName() ;
477 // Finds the item id matching the given string, -1 if not found.
478 int wxMenu::FindItem (const wxString& itemString) const
482 wxStripMenuCodes ((char *)(const char *)itemString, buf1);
484 for (wxNode * node = m_menuItems.First (); node; node = node->Next ())
486 wxMenuItem *item = (wxMenuItem *) node->Data ();
487 if (item->GetSubMenu())
489 int ans = item->GetSubMenu()->FindItem(itemString);
493 if ( !item->IsSeparator() )
495 wxStripMenuCodes((char *)item->GetName().c_str(), buf2);
496 if (strcmp(buf1, buf2) == 0)
497 return item->GetId();
504 wxMenuItem *wxMenu::FindItemForId(int itemId, wxMenu ** itemMenu) const
508 for (wxNode * node = m_menuItems.First (); node; node = node->Next ())
510 wxMenuItem *item = (wxMenuItem *) node->Data ();
512 if (item->GetId() == itemId)
515 *itemMenu = (wxMenu *) this;
519 if (item->GetSubMenu())
521 wxMenuItem *ans = item->GetSubMenu()->FindItemForId (itemId, itemMenu);
532 void wxMenu::SetHelpString(int itemId, const wxString& helpString)
534 wxMenuItem *item = FindItemForId (itemId);
536 item->SetHelp(helpString);
539 wxString wxMenu::GetHelpString (int itemId) const
541 wxMenuItem *item = FindItemForId (itemId);
543 return (item == NULL) ? str : item->GetHelp();
547 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
549 bool processed
= FALSE
;
551 #if WXWIN_COMPATIBILITY
555 (void)(*(m_callback
))(*this, event
);
558 #endif WXWIN_COMPATIBILITY
560 // Try the menu's event handler
561 if ( !processed
&& GetEventHandler())
563 processed
= GetEventHandler()->ProcessEvent(event
);
566 // Try the window the menu was popped up from (and up through the
568 wxWindow
*win
= GetInvokingWindow();
569 if ( !processed
&& win
)
570 processed
= win
->GetEventHandler()->ProcessEvent(event
);
576 // ---------------------------------------------------------------------------
578 // ---------------------------------------------------------------------------
580 void wxMenu::Attach(wxMenuBar
*menubar
)
582 // menu can be in at most one menubar because otherwise they would both
583 // delete the menu pointer
584 wxASSERT_MSG( !m_menuBar
, wxT("menu belongs to 2 menubars, expect a crash") );
589 void wxMenu::Detach()
591 wxASSERT_MSG( m_menuBar
, wxT("can't detach menu if it's not attached") );
596 wxWindow
*wxMenu::GetWindow() const
598 if ( m_invokingWindow
!= NULL
)
599 return m_invokingWindow
;
600 else if ( m_menuBar
!= NULL
)
601 return m_menuBar
->GetFrame();
606 bool wxWindow::PopupMenu(wxMenu *menu, int x, int y)
608 menu->SetInvokingWindow(this);
609 ClientToScreen( &x , &y ) ;
611 ::InsertMenu( menu->m_hMenu , -1 ) ;
612 long menuResult = ::PopUpMenuSelect(menu->m_hMenu ,y,x, 0) ;
613 menu->MacMenuSelect( this , TickCount() , HiWord(menuResult) , LoWord(menuResult) ) ;
614 ::DeleteMenu( menu->m_macMenuId ) ;
615 menu->SetInvokingWindow(NULL);
620 // helper functions returning the mac menu position for a certain item, note that this is
621 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
623 int wxMenu::MacGetIndexFromId( int id
)
626 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
627 for ( pos
= 0; node
; pos
++ )
629 if ( node
->GetData()->GetId() == id
)
632 node
= node
->GetNext();
641 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
644 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
645 for ( pos
= 0; node
; pos
++ )
647 if ( node
->GetData() == pItem
)
650 node
= node
->GetNext();
659 void wxMenu::MacEnableMenu( bool bDoEnable
)
662 UMAEnableMenuItem( m_hMenu
, 0 ) ;
664 UMADisableMenuItem( m_hMenu
, 0 ) ;
669 bool wxMenu::MacMenuSelect( wxEvtHandler
* handler
, long when
, int macMenuId
, int macMenuItemNum
)
674 if ( m_macMenuId
== macMenuId
)
676 node
= GetMenuItems().Nth(macMenuItemNum
-1);
679 wxMenuItem
*pItem
= (wxMenuItem
*)node
->Data();
681 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, pItem
->GetId());
682 event
.m_timeStamp
= when
;
683 event
.SetEventObject(handler
);
684 event
.SetInt( pItem
->GetId() );
685 ProcessCommand( event
) ;
689 else if ( macMenuId
== kHMHelpMenuID
)
691 int menuItem
= formerHelpMenuItems
;
692 for (pos
= 0, node
= GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
694 wxMenuItem
* pItem
= (wxMenuItem
*) node
->Data() ;
696 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
697 if ( pSubMenu
!= NULL
)
702 if ( pItem
->GetId() != wxApp::s_macAboutMenuItemId
)
705 if ( menuItem
== macMenuItemNum
)
707 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, pItem
->GetId());
708 event
.m_timeStamp
= when
;
709 event
.SetEventObject(handler
);
710 event
.SetInt( pItem
->GetId() );
711 ProcessCommand( event
) ;
718 for (pos
= 0, node
= GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
720 wxMenuItem
* pItem
= (wxMenuItem
*) node
->Data() ;
722 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
723 if ( pSubMenu
!= NULL
)
725 if ( pSubMenu
->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )
737 Mac Implementation note :
739 The Mac has only one global menubar, so we attempt to install the currently
740 active menubar from a frame, we currently don't take into account mdi-frames
741 which would ask for menu-merging
743 Secondly there is no mac api for changing a menubar that is not the current
744 menubar, so we have to wait for preparing the actual menubar until the
745 wxMenubar is to be used
747 We can in subsequent versions use MacInstallMenuBar to provide some sort of
748 auto-merge for MDI in case this will be necessary
752 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
754 void wxMenuBar::Init()
756 m_eventHandler
= this;
757 m_menuBarFrame
= NULL
;
761 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
765 m_titles
.Alloc(count
);
767 for ( int i
= 0; i
< count
; i
++ )
769 m_menus
.Append(menus
[i
]);
770 m_titles
.Add(titles
[i
]);
772 menus
[i
]->Attach(this);
776 wxMenuBar::~wxMenuBar()
778 if (s_macInstalledMenuBar
== this)
781 s_macInstalledMenuBar
= NULL
;
786 void wxMenuBar::Refresh()
788 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
793 void wxMenuBar::MacInstallMenuBar()
795 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
797 wxCHECK_RET( menubar
!= NULL
, "can't read MBAR resource" );
798 ::SetMenuBar( menubar
) ;
799 ::DisposeHandle( menubar
) ;
801 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
802 ::AppendResMenu(menu
, 'DRVR');
804 for (int i
= 0; i
< m_menus
.GetCount(); i
++)
810 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
813 if( m_titles
[i
] == "?" || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
815 MenuHandle mh
= NULL
;
816 if ( HMGetHelpMenuHandle( &mh
) != noErr
)
820 if ( formerHelpMenuItems
== 0 )
823 formerHelpMenuItems
= CountMenuItems( mh
) ;
826 for (pos
= 0 , node
= menu
->GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
828 item
= (wxMenuItem
*)node
->Data();
829 subMenu
= item
->GetSubMenu() ;
832 // we don't support hierarchical menus in the help menu yet
837 wxMacBuildMenuString( label
, NULL
, NULL
, item
->GetText(), item
->GetId() != wxApp::s_macAboutMenuItemId
); // no shortcut in about menu
840 // we cannot add empty menus on mac
844 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
846 ::SetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , label
);
847 // ::EnableMenuItem( GetMenuHandle( kwxMacAppleMenuId ) , 1 );
848 ::EnableItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 );
853 ::AppendMenu(mh
, label
);
860 wxMacBuildMenuString( label
, NULL
, NULL
, m_titles
[i
] , false );
861 UMASetMenuTitle( menu
->GetHMenu() , label
) ;
862 for (pos
= 0, node
= menu
->GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
864 item
= (wxMenuItem
*)node
->Data();
865 subMenu
= item
->GetSubMenu() ;
868 ::InsertMenu( subMenu
->GetHMenu() , -1 ) ;
871 ::InsertMenu(m_menus
[i
]->GetHMenu(), 0);
876 s_macInstalledMenuBar
= this;
879 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
881 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
882 m_menus
[pos
]->MacEnableMenu( enable
) ;
886 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
888 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
890 m_titles
[pos
] = label
;
897 m_menus
[pos
]->SetTitle( label
) ;
898 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
900 ::SetMenuBar( GetMenuBar() ) ;
905 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
907 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
908 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
910 return m_titles
[pos
];
913 int wxMenuBar::FindMenu(const wxString
& title
)
915 wxString menuTitle
= wxStripMenuCodes(title
);
917 size_t count
= GetMenuCount();
918 for ( size_t i
= 0; i
< count
; i
++ )
920 wxString title
= wxStripMenuCodes(m_titles
[i
]);
921 if ( menuTitle
== title
)
930 // ---------------------------------------------------------------------------
931 // wxMenuBar construction
932 // ---------------------------------------------------------------------------
934 // ---------------------------------------------------------------------------
935 // wxMenuBar construction
936 // ---------------------------------------------------------------------------
938 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
940 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
943 m_titles
[pos
] = title
;
947 if (s_macInstalledMenuBar
== this)
949 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
952 wxMacBuildMenuString( label
, NULL
, NULL
, title
, false );
953 UMASetMenuTitle( menu
->GetHMenu() , label
) ;
954 if ( pos
== m_menus
.GetCount() - 1)
956 ::InsertMenu( menu
->GetHMenu() , 0 ) ;
960 ::InsertMenu( menu
->GetHMenu() , m_menus
[pos
+1]->MacGetMenuId() ) ;
967 if ( menuOld
->HasAccels() || menu
->HasAccels() )
969 // need to rebuild accell table
972 #endif // wxUSE_ACCEL
980 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
982 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
985 m_titles
.Insert(title
, pos
);
991 if ( pos
== (size_t) -1 )
993 ::InsertMenu( menu
->GetHMenu() , 0 ) ;
997 ::InsertMenu( menu
->GetHMenu() , m_menus
[pos
+1]->MacGetMenuId() ) ;
1001 if ( menu
->HasAccels() )
1003 // need to rebuild accell table
1004 RebuildAccelTable();
1006 #endif // wxUSE_ACCEL
1014 void wxMenuBar::MacMenuSelect(wxEvtHandler
* handler
, long when
, int macMenuId
, int macMenuItemNum
)
1016 // first scan fast for direct commands, i.e. menus which have these commands directly in their own list
1018 if ( macMenuId
== kwxMacAppleMenuId
&& macMenuItemNum
== 1 )
1020 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, wxApp::s_macAboutMenuItemId
);
1021 event
.m_timeStamp
= when
;
1022 event
.SetEventObject(handler
);
1023 event
.SetInt( wxApp::s_macAboutMenuItemId
);
1024 handler
->ProcessEvent(event
);
1028 for (int i
= 0; i
< m_menus
.GetCount() ; i
++)
1030 if ( m_menus
[i
]->MacGetMenuId() == macMenuId
||
1031 ( macMenuId
== kHMHelpMenuID
&& ( m_titles
[i
] == "?" || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
) )
1034 if ( m_menus
[i
]->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )
1038 //TODO flag this as an error since it must contain the item
1044 for (int i
= 0; i
< m_menus
.GetCount(); i
++)
1046 if ( m_menus
[i
]->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )