1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/mac/classic/menu.cpp
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
4 // Author: Stefan Csomor
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
13 // headers & declarations
14 // ============================================================================
19 #include "wx/wxprec.h"
27 #include "wx/window.h"
31 #include "wx/menuitem.h"
33 #include "wx/mac/uma.h"
35 // other standard headers
36 // ----------------------
39 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
40 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
42 // the (popup) menu title has this special id
43 static const int idMenuTitle
= -2;
44 static MenuItemIndex firstUserHelpMenuItem
= 0 ;
46 const short kwxMacMenuBarResource
= 1 ;
47 const short kwxMacAppleMenuId
= 1 ;
49 // ============================================================================
51 // ============================================================================
52 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
) ;
53 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
);
57 // Construct a menu with optional title (then use append)
60 short wxMenu::s_macNextMenuId
= 3 ;
62 short wxMenu::s_macNextMenuId
= 2 ;
68 m_startRadioGroup
= -1;
71 m_macMenuId
= s_macNextMenuId
++;
72 m_hMenu
= UMANewMenu(m_macMenuId
, m_title
, wxFont::GetDefaultEncoding() );
76 wxLogLastError(wxT("UMANewMenu failed"));
79 // if we have a title, insert it in the beginning of the menu
80 if ( !m_title
.empty() )
82 Append(idMenuTitle
, m_title
) ;
89 if (MAC_WXHMENU(m_hMenu
))
90 ::DisposeMenu(MAC_WXHMENU(m_hMenu
));
95 // not available on the mac platform
98 void wxMenu::Attach(wxMenuBarBase
*menubar
)
100 wxMenuBase::Attach(menubar
);
105 // function appends a new item or submenu to the menu
106 // append a new item or submenu to the menu
107 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
109 wxASSERT_MSG( pItem
!= NULL
, wxT("can't append NULL item to the menu") );
111 if ( pItem
->IsSeparator() )
113 if ( pos
== (size_t)-1 )
114 MacAppendMenu(MAC_WXHMENU(m_hMenu
), "\p-");
116 MacInsertMenuItem(MAC_WXHMENU(m_hMenu
), "\p-" , pos
);
120 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
121 if ( pSubMenu
!= NULL
)
123 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, wxT("invalid submenu added"));
124 pSubMenu
->m_menuParent
= this ;
126 if (wxMenuBar::MacGetInstalledMenuBar() == GetMenuBar())
128 pSubMenu
->MacBeforeDisplay( true ) ;
131 if ( pos
== (size_t)-1 )
132 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pSubMenu
->m_macMenuId
);
134 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pos
, pSubMenu
->m_macMenuId
);
135 pItem
->UpdateItemBitmap() ;
136 pItem
->UpdateItemStatus() ;
140 if ( pos
== (size_t)-1 )
142 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , wxFont::GetDefaultEncoding() );
143 pos
= CountMenuItems(MAC_WXHMENU(m_hMenu
)) ;
147 // MacOS counts menu items from 1 and inserts after, therefore having the
148 // same effect as wx 0 based and inserting before, we must correct pos
149 // after however for updates to be correct
150 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a"), wxFont::GetDefaultEncoding(), pos
);
154 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu
) , pos
, pItem
->GetId() ) ;
155 pItem
->UpdateItemText() ;
156 pItem
->UpdateItemBitmap() ;
157 pItem
->UpdateItemStatus() ;
159 if ( pItem
->GetId() == idMenuTitle
)
161 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , pos
, false ) ;
165 // if we're already attached to the menubar, we must update it
168 GetMenuBar()->Refresh();
173 void wxMenu::EndRadioGroup()
175 // we're not inside a radio group any longer
176 m_startRadioGroup
= -1;
179 wxMenuItem
* wxMenu::DoAppend(wxMenuItem
*item
)
181 wxCHECK_MSG( item
, NULL
, _T("NULL item in wxMenu::DoAppend") );
185 if ( item
->GetKind() == wxITEM_RADIO
)
187 int count
= GetMenuItemCount();
189 if ( m_startRadioGroup
== -1 )
191 // start a new radio group
192 m_startRadioGroup
= count
;
194 // for now it has just one element
195 item
->SetAsRadioGroupStart();
196 item
->SetRadioGroupEnd(m_startRadioGroup
);
198 // ensure that we have a checked item in the radio group
201 else // extend the current radio group
203 // we need to update its end item
204 item
->SetRadioGroupStart(m_startRadioGroup
);
205 wxMenuItemList::Node
*node
= GetMenuItems().Item(m_startRadioGroup
);
209 node
->GetData()->SetRadioGroupEnd(count
);
213 wxFAIL_MSG( _T("where is the radio group start item?") );
217 else // not a radio item
222 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
229 // check the item initially
236 wxMenuItem
* wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
238 if (wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
))
244 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
246 // we need to find the items position in the child list
248 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
249 for ( pos
= 0; node
; pos
++ )
251 if ( node
->GetData() == item
)
254 node
= node
->GetNext();
257 // DoRemove() (unlike Remove) can only be called for existing item!
258 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
260 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu
) , pos
+ 1);
264 // otherwise, the change won't be visible
265 GetMenuBar()->Refresh();
268 // and from internal data structures
269 return wxMenuBase::DoRemove(item
);
272 void wxMenu::SetTitle(const wxString
& label
)
275 UMASetMenuTitle(MAC_WXHMENU(m_hMenu
) , label
, wxFont::GetDefaultEncoding() ) ;
277 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
279 bool processed
= false;
281 // Try the menu's event handler
282 if ( !processed
&& GetEventHandler())
284 processed
= GetEventHandler()->ProcessEvent(event
);
287 // Try the window the menu was popped up from (and up through the
289 wxWindow
*win
= GetInvokingWindow();
290 if ( !processed
&& win
)
291 processed
= win
->GetEventHandler()->ProcessEvent(event
);
297 // ---------------------------------------------------------------------------
299 // ---------------------------------------------------------------------------
301 wxWindow
*wxMenu::GetWindow() const
303 if ( m_invokingWindow
!= NULL
)
304 return m_invokingWindow
;
305 else if ( GetMenuBar() != NULL
)
306 return (wxWindow
*) GetMenuBar()->GetFrame();
311 // helper functions returning the mac menu position for a certain item, note that this is
312 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
314 int wxMenu::MacGetIndexFromId( int id
)
317 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
318 for ( pos
= 0; node
; pos
++ )
320 if ( node
->GetData()->GetId() == id
)
323 node
= node
->GetNext();
332 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
335 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
336 for ( pos
= 0; node
; pos
++ )
338 if ( node
->GetData() == pItem
)
341 node
= node
->GetNext();
350 void wxMenu::MacEnableMenu( bool bDoEnable
)
352 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
357 // MacOS needs to know about submenus somewhere within this menu
358 // before it can be displayed , also hide special menu items like preferences
359 // that are handled by the OS
360 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
362 wxMenuItem
* previousItem
= NULL
;
364 wxMenuItemList::Node
*node
;
366 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
368 item
= (wxMenuItem
*)node
->GetData();
369 wxMenu
* subMenu
= item
->GetSubMenu() ;
372 subMenu
->MacBeforeDisplay( true ) ;
377 if ( UMAGetSystemVersion() >= 0x1000 )
379 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
381 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
382 if ( GetMenuItems().GetCount() == pos
+ 1 &&
383 previousItem
!= NULL
&&
384 previousItem
->IsSeparator() )
386 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
392 previousItem
= item
;
396 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
399 // undo all changes from the MacBeforeDisplay call
400 void wxMenu::MacAfterDisplay( bool isSubMenu
)
403 ::DeleteMenu(MacGetMenuId());
405 wxMenuItem
* previousItem
= NULL
;
407 wxMenuItemList::Node
*node
;
409 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
411 item
= (wxMenuItem
*)node
->GetData();
412 wxMenu
* subMenu
= item
->GetSubMenu() ;
415 subMenu
->MacAfterDisplay( true ) ;
419 // no need to undo hidings
421 previousItem
= item
;
429 Mac Implementation note :
431 The Mac has only one global menubar, so we attempt to install the currently
432 active menubar from a frame, we currently don't take into account mdi-frames
433 which would ask for menu-merging
435 Secondly there is no mac api for changing a menubar that is not the current
436 menubar, so we have to wait for preparing the actual menubar until the
437 wxMenubar is to be used
439 We can in subsequent versions use MacInstallMenuBar to provide some sort of
440 auto-merge for MDI in case this will be necessary
444 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
445 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
447 void wxMenuBar::Init()
449 m_eventHandler
= this;
450 m_menuBarFrame
= NULL
;
451 m_invokingWindow
= (wxWindow
*) NULL
;
454 wxMenuBar::wxMenuBar()
459 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
465 wxMenuBar::wxMenuBar(size_t count
, wxMenu
*menus
[], const wxString titles
[], long WXUNUSED(style
))
469 m_titles
.Alloc(count
);
471 for ( size_t i
= 0; i
< count
; i
++ )
473 m_menus
.Append(menus
[i
]);
474 m_titles
.Add(titles
[i
]);
476 menus
[i
]->Attach(this);
480 wxMenuBar::~wxMenuBar()
482 if (s_macCommonMenuBar
== this)
483 s_macCommonMenuBar
= NULL
;
484 if (s_macInstalledMenuBar
== this)
487 s_macInstalledMenuBar
= NULL
;
492 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
494 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
499 void wxMenuBar::MacInstallMenuBar()
501 if ( s_macInstalledMenuBar
== this )
504 wxStAppResource resload
;
506 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
508 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
509 ::SetMenuBar( menubar
) ;
510 #if TARGET_API_MAC_CARBON
511 ::DisposeMenuBar( menubar
) ;
513 ::DisposeHandle( menubar
) ;
516 #if TARGET_API_MAC_OS8
517 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
518 if ( CountMenuItems( menu
) == 2 )
520 ::AppendResMenu(menu
, 'DRVR');
524 // clean-up the help menu before adding new items
525 MenuHandle mh
= NULL
;
526 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
528 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
530 DeleteMenuItem( mh
, i
) ;
538 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
540 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
541 if ( item
== NULL
|| !(item
->IsEnabled()) )
542 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
544 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
547 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
549 wxMenuItemList::Node
*node
;
552 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
554 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
561 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
563 item
= (wxMenuItem
*)node
->GetData();
564 subMenu
= item
->GetSubMenu() ;
567 // we don't support hierarchical menus in the help menu yet
571 if ( item
->IsSeparator() )
574 MacAppendMenu(mh
, "\p-" );
578 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
580 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
582 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() , wxFont::GetDefaultEncoding() );
583 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
584 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
585 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
591 UMAAppendMenuItem(mh
, item
->GetText() , wxFont::GetDefaultEncoding(), entry
);
592 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
603 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
], m_font
.GetEncoding() ) ;
604 m_menus
[i
]->MacBeforeDisplay(false) ;
605 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
609 s_macInstalledMenuBar
= this;
612 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
614 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
615 m_menus
[pos
]->MacEnableMenu( enable
) ;
619 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
621 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
623 m_titles
[pos
] = label
;
630 m_menus
[pos
]->SetTitle( label
) ;
631 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
633 ::SetMenuBar( GetMenuBar() ) ;
638 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
640 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
641 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
643 return m_titles
[pos
];
646 int wxMenuBar::FindMenu(const wxString
& title
)
648 wxString menuTitle
= wxStripMenuCodes(title
);
650 size_t count
= GetMenuCount();
651 for ( size_t i
= 0; i
< count
; i
++ )
653 wxString title
= wxStripMenuCodes(m_titles
[i
]);
654 if ( menuTitle
== title
)
663 // ---------------------------------------------------------------------------
664 // wxMenuBar construction
665 // ---------------------------------------------------------------------------
667 // ---------------------------------------------------------------------------
668 // wxMenuBar construction
669 // ---------------------------------------------------------------------------
671 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
673 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
676 m_titles
[pos
] = title
;
680 if (s_macInstalledMenuBar
== this)
682 menuOld
->MacAfterDisplay( false ) ;
683 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
685 menu
->MacBeforeDisplay( false ) ;
686 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
687 if ( pos
== m_menus
.GetCount() - 1)
689 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
693 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
704 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
706 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
709 m_titles
.Insert(title
, pos
);
711 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
713 if ( IsAttached() && s_macInstalledMenuBar
== this )
715 if (s_macInstalledMenuBar
== this)
717 menu
->MacBeforeDisplay( false ) ;
718 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
720 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
724 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
733 wxMenu
*wxMenuBar::Remove(size_t pos
)
735 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
741 if (s_macInstalledMenuBar
== this)
743 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
749 m_titles
.RemoveAt(pos
);
754 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
756 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
757 wxCHECK_MSG( submenu
, false, wxT("can't append invalid menu to menubar") );
759 if ( !wxMenuBarBase::Append(menu
, title
) )
764 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
768 if (s_macInstalledMenuBar
== this)
770 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
776 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
777 // adding menu later on.
778 if (m_invokingWindow
)
779 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
784 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
786 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
788 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
791 wxMenuItem
*menuitem
= node
->GetData();
792 if (menuitem
->IsSubMenu())
793 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
794 node
= node
->GetNext();
798 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
800 menu
->SetInvokingWindow( win
);
802 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
805 wxMenuItem
*menuitem
= node
->GetData();
806 if (menuitem
->IsSubMenu())
807 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
808 node
= node
->GetNext();
812 void wxMenuBar::UnsetInvokingWindow()
814 m_invokingWindow
= (wxWindow
*) NULL
;
815 wxMenuList::Node
*node
= m_menus
.GetFirst();
818 wxMenu
*menu
= node
->GetData();
819 wxMenubarUnsetInvokingWindow( menu
);
820 node
= node
->GetNext();
824 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
826 m_invokingWindow
= frame
;
827 wxMenuList::Node
*node
= m_menus
.GetFirst();
830 wxMenu
*menu
= node
->GetData();
831 wxMenubarSetInvokingWindow( menu
, frame
);
832 node
= node
->GetNext();
836 void wxMenuBar::Detach()
838 wxMenuBarBase::Detach() ;
841 void wxMenuBar::Attach(wxFrame
*frame
)
843 wxMenuBarBase::Attach( frame
) ;
845 // ---------------------------------------------------------------------------
846 // wxMenuBar searching for menu items
847 // ---------------------------------------------------------------------------
849 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
850 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
851 const wxString
& itemString
) const
853 wxString menuLabel
= wxStripMenuCodes(menuString
);
854 size_t count
= GetMenuCount();
855 for ( size_t i
= 0; i
< count
; i
++ )
857 wxString title
= wxStripMenuCodes(m_titles
[i
]);
858 if ( menuString
== title
)
859 return m_menus
[i
]->FindItem(itemString
);
865 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
870 wxMenuItem
*item
= NULL
;
871 size_t count
= GetMenuCount();
872 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
874 item
= m_menus
[i
]->FindItem(id
, itemMenu
);