1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
4 // Author: Stefan Csomor
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
13 // headers & declarations
14 // ============================================================================
21 #include "wx/menuitem.h"
22 #include "wx/window.h"
27 #include "wx/mac/uma.h"
29 // other standard headers
30 // ----------------------
33 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
34 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
36 // the (popup) menu title has this special id
37 static const int idMenuTitle
= -2;
38 static MenuItemIndex firstUserHelpMenuItem
= 0 ;
40 const short kwxMacMenuBarResource
= 1 ;
41 const short kwxMacAppleMenuId
= 1 ;
43 // ============================================================================
45 // ============================================================================
46 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
) ;
47 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
);
51 // Construct a menu with optional title (then use append)
54 short wxMenu::s_macNextMenuId
= 3 ;
56 short wxMenu::s_macNextMenuId
= 2 ;
62 m_startRadioGroup
= -1;
65 m_macMenuId
= s_macNextMenuId
++;
66 m_hMenu
= UMANewMenu(m_macMenuId
, m_title
, wxFont::GetDefaultEncoding() );
70 wxLogLastError(wxT("UMANewMenu failed"));
73 // if we have a title, insert it in the beginning of the menu
76 Append(idMenuTitle
, m_title
) ;
83 if (MAC_WXHMENU(m_hMenu
))
84 ::DisposeMenu(MAC_WXHMENU(m_hMenu
));
89 // not available on the mac platform
92 void wxMenu::Attach(wxMenuBarBase
*menubar
)
94 wxMenuBase::Attach(menubar
);
99 // function appends a new item or submenu to the menu
100 // append a new item or submenu to the menu
101 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
103 wxASSERT_MSG( pItem
!= NULL
, wxT("can't append NULL item to the menu") );
105 if ( pItem
->IsSeparator() )
107 if ( pos
== (size_t)-1 )
108 MacAppendMenu(MAC_WXHMENU(m_hMenu
), "\p-");
110 MacInsertMenuItem(MAC_WXHMENU(m_hMenu
), "\p-" , pos
);
114 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
115 if ( pSubMenu
!= NULL
)
117 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, wxT("invalid submenu added"));
118 pSubMenu
->m_menuParent
= this ;
120 if (wxMenuBar::MacGetInstalledMenuBar() == GetMenuBar())
122 pSubMenu
->MacBeforeDisplay( true ) ;
125 if ( pos
== (size_t)-1 )
126 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pSubMenu
->m_macMenuId
);
128 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pos
, pSubMenu
->m_macMenuId
);
129 pItem
->UpdateItemBitmap() ;
130 pItem
->UpdateItemStatus() ;
134 if ( pos
== (size_t)-1 )
136 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , wxFont::GetDefaultEncoding() );
137 pos
= CountMenuItems(MAC_WXHMENU(m_hMenu
)) ;
141 // MacOS counts menu items from 1 and inserts after, therefore having the
142 // same effect as wx 0 based and inserting before, we must correct pos
143 // after however for updates to be correct
144 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a"), wxFont::GetDefaultEncoding(), pos
);
148 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu
) , pos
, pItem
->GetId() ) ;
149 pItem
->UpdateItemText() ;
150 pItem
->UpdateItemBitmap() ;
151 pItem
->UpdateItemStatus() ;
153 if ( pItem
->GetId() == idMenuTitle
)
155 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , pos
, false ) ;
159 // if we're already attached to the menubar, we must update it
162 GetMenuBar()->Refresh();
167 void wxMenu::EndRadioGroup()
169 // we're not inside a radio group any longer
170 m_startRadioGroup
= -1;
173 wxMenuItem
* wxMenu::DoAppend(wxMenuItem
*item
)
175 wxCHECK_MSG( item
, NULL
, _T("NULL item in wxMenu::DoAppend") );
179 if ( item
->GetKind() == wxITEM_RADIO
)
181 int count
= GetMenuItemCount();
183 if ( m_startRadioGroup
== -1 )
185 // start a new radio group
186 m_startRadioGroup
= count
;
188 // for now it has just one element
189 item
->SetAsRadioGroupStart();
190 item
->SetRadioGroupEnd(m_startRadioGroup
);
192 // ensure that we have a checked item in the radio group
195 else // extend the current radio group
197 // we need to update its end item
198 item
->SetRadioGroupStart(m_startRadioGroup
);
199 wxMenuItemList::Node
*node
= GetMenuItems().Item(m_startRadioGroup
);
203 node
->GetData()->SetRadioGroupEnd(count
);
207 wxFAIL_MSG( _T("where is the radio group start item?") );
211 else // not a radio item
216 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
223 // check the item initially
230 wxMenuItem
* wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
232 if (wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
))
238 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
240 // we need to find the items position in the child list
242 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
243 for ( pos
= 0; node
; pos
++ )
245 if ( node
->GetData() == item
)
248 node
= node
->GetNext();
251 // DoRemove() (unlike Remove) can only be called for existing item!
252 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
254 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu
) , pos
+ 1);
258 // otherwise, the change won't be visible
259 GetMenuBar()->Refresh();
262 // and from internal data structures
263 return wxMenuBase::DoRemove(item
);
266 void wxMenu::SetTitle(const wxString
& label
)
269 UMASetMenuTitle(MAC_WXHMENU(m_hMenu
) , label
, wxFont::GetDefaultEncoding() ) ;
271 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
273 bool processed
= FALSE
;
275 // Try the menu's event handler
276 if ( !processed
&& GetEventHandler())
278 processed
= GetEventHandler()->ProcessEvent(event
);
281 // Try the window the menu was popped up from (and up through the
283 wxWindow
*win
= GetInvokingWindow();
284 if ( !processed
&& win
)
285 processed
= win
->GetEventHandler()->ProcessEvent(event
);
291 // ---------------------------------------------------------------------------
293 // ---------------------------------------------------------------------------
295 wxWindow
*wxMenu::GetWindow() const
297 if ( m_invokingWindow
!= NULL
)
298 return m_invokingWindow
;
299 else if ( GetMenuBar() != NULL
)
300 return (wxWindow
*) GetMenuBar()->GetFrame();
305 // helper functions returning the mac menu position for a certain item, note that this is
306 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
308 int wxMenu::MacGetIndexFromId( int id
)
311 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
312 for ( pos
= 0; node
; pos
++ )
314 if ( node
->GetData()->GetId() == id
)
317 node
= node
->GetNext();
326 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
329 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
330 for ( pos
= 0; node
; pos
++ )
332 if ( node
->GetData() == pItem
)
335 node
= node
->GetNext();
344 void wxMenu::MacEnableMenu( bool bDoEnable
)
346 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
351 // MacOS needs to know about submenus somewhere within this menu
352 // before it can be displayed , also hide special menu items like preferences
353 // that are handled by the OS
354 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
356 wxMenuItem
* previousItem
= NULL
;
358 wxMenuItemList::Node
*node
;
360 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
362 item
= (wxMenuItem
*)node
->GetData();
363 wxMenu
* subMenu
= item
->GetSubMenu() ;
366 subMenu
->MacBeforeDisplay( true ) ;
371 if ( UMAGetSystemVersion() >= 0x1000 )
373 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
375 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
376 if ( GetMenuItems().GetCount() == pos
+ 1 &&
377 previousItem
!= NULL
&&
378 previousItem
->IsSeparator() )
380 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
386 previousItem
= item
;
390 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
393 // undo all changes from the MacBeforeDisplay call
394 void wxMenu::MacAfterDisplay( bool isSubMenu
)
397 ::DeleteMenu(MacGetMenuId());
399 wxMenuItem
* previousItem
= NULL
;
401 wxMenuItemList::Node
*node
;
403 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
405 item
= (wxMenuItem
*)node
->GetData();
406 wxMenu
* subMenu
= item
->GetSubMenu() ;
409 subMenu
->MacAfterDisplay( true ) ;
413 // no need to undo hidings
415 previousItem
= item
;
423 Mac Implementation note :
425 The Mac has only one global menubar, so we attempt to install the currently
426 active menubar from a frame, we currently don't take into account mdi-frames
427 which would ask for menu-merging
429 Secondly there is no mac api for changing a menubar that is not the current
430 menubar, so we have to wait for preparing the actual menubar until the
431 wxMenubar is to be used
433 We can in subsequent versions use MacInstallMenuBar to provide some sort of
434 auto-merge for MDI in case this will be necessary
438 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
439 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
441 void wxMenuBar::Init()
443 m_eventHandler
= this;
444 m_menuBarFrame
= NULL
;
445 m_invokingWindow
= (wxWindow
*) NULL
;
448 wxMenuBar::wxMenuBar()
453 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
459 wxMenuBar::wxMenuBar(size_t count
, wxMenu
*menus
[], const wxString titles
[], long WXUNUSED(style
))
463 m_titles
.Alloc(count
);
465 for ( size_t i
= 0; i
< count
; i
++ )
467 m_menus
.Append(menus
[i
]);
468 m_titles
.Add(titles
[i
]);
470 menus
[i
]->Attach(this);
474 wxMenuBar::~wxMenuBar()
476 if (s_macCommonMenuBar
== this)
477 s_macCommonMenuBar
= NULL
;
478 if (s_macInstalledMenuBar
== this)
481 s_macInstalledMenuBar
= NULL
;
486 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
488 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
493 void wxMenuBar::MacInstallMenuBar()
495 if ( s_macInstalledMenuBar
== this )
498 wxStAppResource resload
;
500 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
502 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
503 ::SetMenuBar( menubar
) ;
504 #if TARGET_API_MAC_CARBON
505 ::DisposeMenuBar( menubar
) ;
507 ::DisposeHandle( menubar
) ;
510 #if TARGET_API_MAC_OS8
511 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
512 if ( CountMenuItems( menu
) == 2 )
514 ::AppendResMenu(menu
, 'DRVR');
518 // clean-up the help menu before adding new items
519 MenuHandle mh
= NULL
;
520 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
522 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
524 DeleteMenuItem( mh
, i
) ;
532 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
534 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
535 if ( item
== NULL
|| !(item
->IsEnabled()) )
536 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
538 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
541 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
543 wxMenuItemList::Node
*node
;
546 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
548 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
555 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
557 item
= (wxMenuItem
*)node
->GetData();
558 subMenu
= item
->GetSubMenu() ;
561 // we don't support hierarchical menus in the help menu yet
565 if ( item
->IsSeparator() )
568 MacAppendMenu(mh
, "\p-" );
572 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
574 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
576 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() , wxFont::GetDefaultEncoding() );
577 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
578 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
579 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
585 UMAAppendMenuItem(mh
, item
->GetText() , wxFont::GetDefaultEncoding(), entry
);
586 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
597 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
], m_font
.GetEncoding() ) ;
598 m_menus
[i
]->MacBeforeDisplay(false) ;
599 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
603 s_macInstalledMenuBar
= this;
606 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
608 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
609 m_menus
[pos
]->MacEnableMenu( enable
) ;
613 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
615 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
617 m_titles
[pos
] = label
;
624 m_menus
[pos
]->SetTitle( label
) ;
625 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
627 ::SetMenuBar( GetMenuBar() ) ;
632 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
634 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
635 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
637 return m_titles
[pos
];
640 int wxMenuBar::FindMenu(const wxString
& title
)
642 wxString menuTitle
= wxStripMenuCodes(title
);
644 size_t count
= GetMenuCount();
645 for ( size_t i
= 0; i
< count
; i
++ )
647 wxString title
= wxStripMenuCodes(m_titles
[i
]);
648 if ( menuTitle
== title
)
657 // ---------------------------------------------------------------------------
658 // wxMenuBar construction
659 // ---------------------------------------------------------------------------
661 // ---------------------------------------------------------------------------
662 // wxMenuBar construction
663 // ---------------------------------------------------------------------------
665 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
667 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
670 m_titles
[pos
] = title
;
674 if (s_macInstalledMenuBar
== this)
676 menuOld
->MacAfterDisplay( false ) ;
677 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
679 menu
->MacBeforeDisplay( false ) ;
680 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
681 if ( pos
== m_menus
.GetCount() - 1)
683 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
687 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
698 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
700 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
703 m_titles
.Insert(title
, pos
);
705 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
707 if ( IsAttached() && s_macInstalledMenuBar
== this )
709 if (s_macInstalledMenuBar
== this)
711 menu
->MacBeforeDisplay( false ) ;
712 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
714 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
718 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
727 wxMenu
*wxMenuBar::Remove(size_t pos
)
729 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
735 if (s_macInstalledMenuBar
== this)
737 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
743 m_titles
.RemoveAt(pos
);
748 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
750 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
751 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
753 if ( !wxMenuBarBase::Append(menu
, title
) )
758 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
762 if (s_macInstalledMenuBar
== this)
764 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
770 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
771 // adding menu later on.
772 if (m_invokingWindow
)
773 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
778 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
780 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
782 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
785 wxMenuItem
*menuitem
= node
->GetData();
786 if (menuitem
->IsSubMenu())
787 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
788 node
= node
->GetNext();
792 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
794 menu
->SetInvokingWindow( win
);
796 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
799 wxMenuItem
*menuitem
= node
->GetData();
800 if (menuitem
->IsSubMenu())
801 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
802 node
= node
->GetNext();
806 void wxMenuBar::UnsetInvokingWindow()
808 m_invokingWindow
= (wxWindow
*) NULL
;
809 wxMenuList::Node
*node
= m_menus
.GetFirst();
812 wxMenu
*menu
= node
->GetData();
813 wxMenubarUnsetInvokingWindow( menu
);
814 node
= node
->GetNext();
818 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
820 m_invokingWindow
= frame
;
821 wxMenuList::Node
*node
= m_menus
.GetFirst();
824 wxMenu
*menu
= node
->GetData();
825 wxMenubarSetInvokingWindow( menu
, frame
);
826 node
= node
->GetNext();
830 void wxMenuBar::Detach()
832 wxMenuBarBase::Detach() ;
835 void wxMenuBar::Attach(wxFrame
*frame
)
837 wxMenuBarBase::Attach( frame
) ;
839 // ---------------------------------------------------------------------------
840 // wxMenuBar searching for menu items
841 // ---------------------------------------------------------------------------
843 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
844 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
845 const wxString
& itemString
) const
847 wxString menuLabel
= wxStripMenuCodes(menuString
);
848 size_t count
= GetMenuCount();
849 for ( size_t i
= 0; i
< count
; i
++ )
851 wxString title
= wxStripMenuCodes(m_titles
[i
]);
852 if ( menuString
== title
)
853 return m_menus
[i
]->FindItem(itemString
);
859 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
864 wxMenuItem
*item
= NULL
;
865 size_t count
= GetMenuCount();
866 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
868 item
= m_menus
[i
]->FindItem(id
, itemMenu
);