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"
29 #include "wx/menuitem.h"
32 #include "wx/mac/uma.h"
34 // other standard headers
35 // ----------------------
38 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
39 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
41 // the (popup) menu title has this special id
42 static const int idMenuTitle
= -2;
43 static MenuItemIndex firstUserHelpMenuItem
= 0 ;
45 const short kwxMacMenuBarResource
= 1 ;
46 const short kwxMacAppleMenuId
= 1 ;
48 // ============================================================================
50 // ============================================================================
51 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
) ;
52 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
);
56 // Construct a menu with optional title (then use append)
59 short wxMenu::s_macNextMenuId
= 3 ;
61 short wxMenu::s_macNextMenuId
= 2 ;
67 m_startRadioGroup
= -1;
70 m_macMenuId
= s_macNextMenuId
++;
71 m_hMenu
= UMANewMenu(m_macMenuId
, m_title
, wxFont::GetDefaultEncoding() );
75 wxLogLastError(wxT("UMANewMenu failed"));
78 // if we have a title, insert it in the beginning of the menu
79 if ( !m_title
.empty() )
81 Append(idMenuTitle
, m_title
) ;
88 if (MAC_WXHMENU(m_hMenu
))
89 ::DisposeMenu(MAC_WXHMENU(m_hMenu
));
94 // not available on the mac platform
97 void wxMenu::Attach(wxMenuBarBase
*menubar
)
99 wxMenuBase::Attach(menubar
);
104 // function appends a new item or submenu to the menu
105 // append a new item or submenu to the menu
106 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
108 wxASSERT_MSG( pItem
!= NULL
, wxT("can't append NULL item to the menu") );
110 if ( pItem
->IsSeparator() )
112 if ( pos
== (size_t)-1 )
113 MacAppendMenu(MAC_WXHMENU(m_hMenu
), "\p-");
115 MacInsertMenuItem(MAC_WXHMENU(m_hMenu
), "\p-" , pos
);
119 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
120 if ( pSubMenu
!= NULL
)
122 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, wxT("invalid submenu added"));
123 pSubMenu
->m_menuParent
= this ;
125 if (wxMenuBar::MacGetInstalledMenuBar() == GetMenuBar())
127 pSubMenu
->MacBeforeDisplay( true ) ;
130 if ( pos
== (size_t)-1 )
131 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pSubMenu
->m_macMenuId
);
133 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pos
, pSubMenu
->m_macMenuId
);
134 pItem
->UpdateItemBitmap() ;
135 pItem
->UpdateItemStatus() ;
139 if ( pos
== (size_t)-1 )
141 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , wxFont::GetDefaultEncoding() );
142 pos
= CountMenuItems(MAC_WXHMENU(m_hMenu
)) ;
146 // MacOS counts menu items from 1 and inserts after, therefore having the
147 // same effect as wx 0 based and inserting before, we must correct pos
148 // after however for updates to be correct
149 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a"), wxFont::GetDefaultEncoding(), pos
);
153 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu
) , pos
, pItem
->GetId() ) ;
154 pItem
->UpdateItemText() ;
155 pItem
->UpdateItemBitmap() ;
156 pItem
->UpdateItemStatus() ;
158 if ( pItem
->GetId() == idMenuTitle
)
160 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , pos
, false ) ;
164 // if we're already attached to the menubar, we must update it
167 GetMenuBar()->Refresh();
172 void wxMenu::EndRadioGroup()
174 // we're not inside a radio group any longer
175 m_startRadioGroup
= -1;
178 wxMenuItem
* wxMenu::DoAppend(wxMenuItem
*item
)
180 wxCHECK_MSG( item
, NULL
, _T("NULL item in wxMenu::DoAppend") );
184 if ( item
->GetKind() == wxITEM_RADIO
)
186 int count
= GetMenuItemCount();
188 if ( m_startRadioGroup
== -1 )
190 // start a new radio group
191 m_startRadioGroup
= count
;
193 // for now it has just one element
194 item
->SetAsRadioGroupStart();
195 item
->SetRadioGroupEnd(m_startRadioGroup
);
197 // ensure that we have a checked item in the radio group
200 else // extend the current radio group
202 // we need to update its end item
203 item
->SetRadioGroupStart(m_startRadioGroup
);
204 wxMenuItemList::Node
*node
= GetMenuItems().Item(m_startRadioGroup
);
208 node
->GetData()->SetRadioGroupEnd(count
);
212 wxFAIL_MSG( _T("where is the radio group start item?") );
216 else // not a radio item
221 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
228 // check the item initially
235 wxMenuItem
* wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
237 if (wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
))
243 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
245 // we need to find the items position in the child list
247 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
248 for ( pos
= 0; node
; pos
++ )
250 if ( node
->GetData() == item
)
253 node
= node
->GetNext();
256 // DoRemove() (unlike Remove) can only be called for existing item!
257 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
259 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu
) , pos
+ 1);
263 // otherwise, the change won't be visible
264 GetMenuBar()->Refresh();
267 // and from internal data structures
268 return wxMenuBase::DoRemove(item
);
271 void wxMenu::SetTitle(const wxString
& label
)
274 UMASetMenuTitle(MAC_WXHMENU(m_hMenu
) , label
, wxFont::GetDefaultEncoding() ) ;
276 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
278 bool processed
= false;
280 // Try the menu's event handler
281 if ( !processed
&& GetEventHandler())
283 processed
= GetEventHandler()->ProcessEvent(event
);
286 // Try the window the menu was popped up from (and up through the
288 wxWindow
*win
= GetInvokingWindow();
289 if ( !processed
&& win
)
290 processed
= win
->GetEventHandler()->ProcessEvent(event
);
296 // ---------------------------------------------------------------------------
298 // ---------------------------------------------------------------------------
300 wxWindow
*wxMenu::GetWindow() const
302 if ( m_invokingWindow
!= NULL
)
303 return m_invokingWindow
;
304 else if ( GetMenuBar() != NULL
)
305 return (wxWindow
*) GetMenuBar()->GetFrame();
310 // helper functions returning the mac menu position for a certain item, note that this is
311 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
313 int wxMenu::MacGetIndexFromId( int id
)
316 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
317 for ( pos
= 0; node
; pos
++ )
319 if ( node
->GetData()->GetId() == id
)
322 node
= node
->GetNext();
331 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
334 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
335 for ( pos
= 0; node
; pos
++ )
337 if ( node
->GetData() == pItem
)
340 node
= node
->GetNext();
349 void wxMenu::MacEnableMenu( bool bDoEnable
)
351 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
356 // MacOS needs to know about submenus somewhere within this menu
357 // before it can be displayed , also hide special menu items like preferences
358 // that are handled by the OS
359 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
361 wxMenuItem
* previousItem
= NULL
;
363 wxMenuItemList::Node
*node
;
365 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
367 item
= (wxMenuItem
*)node
->GetData();
368 wxMenu
* subMenu
= item
->GetSubMenu() ;
371 subMenu
->MacBeforeDisplay( true ) ;
376 if ( UMAGetSystemVersion() >= 0x1000 )
378 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
380 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
381 if ( GetMenuItems().GetCount() == pos
+ 1 &&
382 previousItem
!= NULL
&&
383 previousItem
->IsSeparator() )
385 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
391 previousItem
= item
;
395 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
398 // undo all changes from the MacBeforeDisplay call
399 void wxMenu::MacAfterDisplay( bool isSubMenu
)
402 ::DeleteMenu(MacGetMenuId());
404 wxMenuItem
* previousItem
= NULL
;
406 wxMenuItemList::Node
*node
;
408 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
410 item
= (wxMenuItem
*)node
->GetData();
411 wxMenu
* subMenu
= item
->GetSubMenu() ;
414 subMenu
->MacAfterDisplay( true ) ;
418 // no need to undo hidings
420 previousItem
= item
;
428 Mac Implementation note :
430 The Mac has only one global menubar, so we attempt to install the currently
431 active menubar from a frame, we currently don't take into account mdi-frames
432 which would ask for menu-merging
434 Secondly there is no mac api for changing a menubar that is not the current
435 menubar, so we have to wait for preparing the actual menubar until the
436 wxMenubar is to be used
438 We can in subsequent versions use MacInstallMenuBar to provide some sort of
439 auto-merge for MDI in case this will be necessary
443 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
444 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
446 void wxMenuBar::Init()
448 m_eventHandler
= this;
449 m_menuBarFrame
= NULL
;
450 m_invokingWindow
= (wxWindow
*) NULL
;
453 wxMenuBar::wxMenuBar()
458 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
464 wxMenuBar::wxMenuBar(size_t count
, wxMenu
*menus
[], const wxString titles
[], long WXUNUSED(style
))
468 m_titles
.Alloc(count
);
470 for ( size_t i
= 0; i
< count
; i
++ )
472 m_menus
.Append(menus
[i
]);
473 m_titles
.Add(titles
[i
]);
475 menus
[i
]->Attach(this);
479 wxMenuBar::~wxMenuBar()
481 if (s_macCommonMenuBar
== this)
482 s_macCommonMenuBar
= NULL
;
483 if (s_macInstalledMenuBar
== this)
486 s_macInstalledMenuBar
= NULL
;
491 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
493 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
498 void wxMenuBar::MacInstallMenuBar()
500 if ( s_macInstalledMenuBar
== this )
503 wxStAppResource resload
;
505 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
507 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
508 ::SetMenuBar( menubar
) ;
509 #if TARGET_API_MAC_CARBON
510 ::DisposeMenuBar( menubar
) ;
512 ::DisposeHandle( menubar
) ;
515 #if TARGET_API_MAC_OS8
516 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
517 if ( CountMenuItems( menu
) == 2 )
519 ::AppendResMenu(menu
, 'DRVR');
523 // clean-up the help menu before adding new items
524 MenuHandle mh
= NULL
;
525 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
527 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
529 DeleteMenuItem( mh
, i
) ;
537 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
539 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
540 if ( item
== NULL
|| !(item
->IsEnabled()) )
541 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
543 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
546 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
548 wxMenuItemList::Node
*node
;
551 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
553 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
560 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
562 item
= (wxMenuItem
*)node
->GetData();
563 subMenu
= item
->GetSubMenu() ;
566 // we don't support hierarchical menus in the help menu yet
570 if ( item
->IsSeparator() )
573 MacAppendMenu(mh
, "\p-" );
577 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
579 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
581 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() , wxFont::GetDefaultEncoding() );
582 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
583 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
584 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
590 UMAAppendMenuItem(mh
, item
->GetText() , wxFont::GetDefaultEncoding(), entry
);
591 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
602 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
], m_font
.GetEncoding() ) ;
603 m_menus
[i
]->MacBeforeDisplay(false) ;
604 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
608 s_macInstalledMenuBar
= this;
611 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
613 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
614 m_menus
[pos
]->MacEnableMenu( enable
) ;
618 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
620 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
622 m_titles
[pos
] = label
;
629 m_menus
[pos
]->SetTitle( label
) ;
630 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
632 ::SetMenuBar( GetMenuBar() ) ;
637 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
639 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
640 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
642 return m_titles
[pos
];
645 int wxMenuBar::FindMenu(const wxString
& title
)
647 wxString menuTitle
= wxStripMenuCodes(title
);
649 size_t count
= GetMenuCount();
650 for ( size_t i
= 0; i
< count
; i
++ )
652 wxString title
= wxStripMenuCodes(m_titles
[i
]);
653 if ( menuTitle
== title
)
662 // ---------------------------------------------------------------------------
663 // wxMenuBar construction
664 // ---------------------------------------------------------------------------
666 // ---------------------------------------------------------------------------
667 // wxMenuBar construction
668 // ---------------------------------------------------------------------------
670 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
672 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
675 m_titles
[pos
] = title
;
679 if (s_macInstalledMenuBar
== this)
681 menuOld
->MacAfterDisplay( false ) ;
682 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
684 menu
->MacBeforeDisplay( false ) ;
685 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
686 if ( pos
== m_menus
.GetCount() - 1)
688 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
692 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
703 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
705 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
708 m_titles
.Insert(title
, pos
);
710 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
712 if ( IsAttached() && s_macInstalledMenuBar
== this )
714 if (s_macInstalledMenuBar
== this)
716 menu
->MacBeforeDisplay( false ) ;
717 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
719 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
723 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
732 wxMenu
*wxMenuBar::Remove(size_t pos
)
734 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
740 if (s_macInstalledMenuBar
== this)
742 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
748 m_titles
.RemoveAt(pos
);
753 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
755 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
756 wxCHECK_MSG( submenu
, false, wxT("can't append invalid menu to menubar") );
758 if ( !wxMenuBarBase::Append(menu
, title
) )
763 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
767 if (s_macInstalledMenuBar
== this)
769 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
775 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
776 // adding menu later on.
777 if (m_invokingWindow
)
778 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
783 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
785 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
787 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
790 wxMenuItem
*menuitem
= node
->GetData();
791 if (menuitem
->IsSubMenu())
792 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
793 node
= node
->GetNext();
797 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
799 menu
->SetInvokingWindow( win
);
801 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
804 wxMenuItem
*menuitem
= node
->GetData();
805 if (menuitem
->IsSubMenu())
806 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
807 node
= node
->GetNext();
811 void wxMenuBar::UnsetInvokingWindow()
813 m_invokingWindow
= (wxWindow
*) NULL
;
814 wxMenuList::Node
*node
= m_menus
.GetFirst();
817 wxMenu
*menu
= node
->GetData();
818 wxMenubarUnsetInvokingWindow( menu
);
819 node
= node
->GetNext();
823 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
825 m_invokingWindow
= frame
;
826 wxMenuList::Node
*node
= m_menus
.GetFirst();
829 wxMenu
*menu
= node
->GetData();
830 wxMenubarSetInvokingWindow( menu
, frame
);
831 node
= node
->GetNext();
835 void wxMenuBar::Detach()
837 wxMenuBarBase::Detach() ;
840 void wxMenuBar::Attach(wxFrame
*frame
)
842 wxMenuBarBase::Attach( frame
) ;
844 // ---------------------------------------------------------------------------
845 // wxMenuBar searching for menu items
846 // ---------------------------------------------------------------------------
848 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
849 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
850 const wxString
& itemString
) const
852 wxString menuLabel
= wxStripMenuCodes(menuString
);
853 size_t count
= GetMenuCount();
854 for ( size_t i
= 0; i
< count
; i
++ )
856 wxString title
= wxStripMenuCodes(m_titles
[i
]);
857 if ( menuString
== title
)
858 return m_menus
[i
]->FindItem(itemString
);
864 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
869 wxMenuItem
*item
= NULL
;
870 size_t count
= GetMenuCount();
871 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
873 item
= m_menus
[i
]->FindItem(id
, itemMenu
);