1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
4 // Author: Stefan Csomor
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "menu.h"
14 #pragma implementation "menuitem.h"
17 // ============================================================================
18 // headers & declarations
19 // ============================================================================
26 #include "wx/menuitem.h"
27 #include "wx/window.h"
32 #include "wx/mac/uma.h"
34 // other standard headers
35 // ----------------------
38 #if !USE_SHARED_LIBRARY
39 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
40 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
43 // the (popup) menu title has this special id
44 static const int idMenuTitle
= -2;
45 static MenuItemIndex firstUserHelpMenuItem
= 0 ;
47 const short kwxMacMenuBarResource
= 1 ;
48 const short kwxMacAppleMenuId
= 1 ;
50 // ============================================================================
52 // ============================================================================
53 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
) ;
54 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
);
58 // Construct a menu with optional title (then use append)
61 short wxMenu::s_macNextMenuId
= 3 ;
63 short wxMenu::s_macNextMenuId
= 2 ;
69 m_startRadioGroup
= -1;
72 m_macMenuId
= s_macNextMenuId
++;
73 m_hMenu
= UMANewMenu(m_macMenuId
, m_title
);
77 wxLogLastError("UMANewMenu failed");
80 // if we have a title, insert it in the beginning of the menu
83 Append(idMenuTitle
, m_title
) ;
90 if (MAC_WXHMENU(m_hMenu
))
91 ::DisposeMenu(MAC_WXHMENU(m_hMenu
));
96 // not available on the mac platform
99 void wxMenu::Attach(wxMenuBarBase
*menubar
)
101 wxMenuBase::Attach(menubar
);
106 // function appends a new item or submenu to the menu
107 // append a new item or submenu to the menu
108 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
110 wxASSERT_MSG( pItem
!= NULL
, wxT("can't append NULL item to the menu") );
112 if ( pItem
->IsSeparator() )
114 if ( pos
== (size_t)-1 )
115 MacAppendMenu(MAC_WXHMENU(m_hMenu
), "\p-");
117 MacInsertMenuItem(MAC_WXHMENU(m_hMenu
), "\p-" , pos
);
121 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
122 if ( pSubMenu
!= NULL
)
124 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, wxT("invalid submenu added"));
125 pSubMenu
->m_menuParent
= this ;
127 if (wxMenuBar::MacGetInstalledMenuBar() == m_menuBar
)
129 pSubMenu
->MacBeforeDisplay( true ) ;
132 if ( pos
== (size_t)-1 )
133 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), pSubMenu
->m_macMenuId
);
135 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText() , pos
, pSubMenu
->m_macMenuId
);
136 pItem
->UpdateItemBitmap() ;
137 pItem
->UpdateItemStatus() ;
141 if ( pos
== (size_t)-1 )
143 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") );
144 pos
= CountMenuItems(MAC_WXHMENU(m_hMenu
)) ;
148 // MacOS counts menu items from 1 and inserts after, therefore having the
149 // same effect as wx 0 based and inserting before, we must correct pos
150 // after however for updates to be correct
151 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , pos
);
155 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu
) , pos
, pItem
->GetId() ) ;
156 pItem
->UpdateItemText() ;
157 pItem
->UpdateItemBitmap() ;
158 pItem
->UpdateItemStatus() ;
160 if ( pItem
->GetId() == idMenuTitle
)
162 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , pos
, false ) ;
166 // if we're already attached to the menubar, we must update it
169 m_menuBar
->Refresh();
174 void wxMenu::EndRadioGroup()
176 // we're not inside a radio group any longer
177 m_startRadioGroup
= -1;
180 bool wxMenu::DoAppend(wxMenuItem
*item
)
182 wxCHECK_MSG( item
, FALSE
, _T("NULL item in wxMenu::DoAppend") );
186 if ( item
->GetKind() == wxITEM_RADIO
)
188 int count
= GetMenuItemCount();
190 if ( m_startRadioGroup
== -1 )
192 // start a new radio group
193 m_startRadioGroup
= count
;
195 // for now it has just one element
196 item
->SetAsRadioGroupStart();
197 item
->SetRadioGroupEnd(m_startRadioGroup
);
199 // ensure that we have a checked item in the radio group
202 else // extend the current radio group
204 // we need to update its end item
205 item
->SetRadioGroupStart(m_startRadioGroup
);
206 wxMenuItemList::Node
*node
= GetMenuItems().Item(m_startRadioGroup
);
210 node
->GetData()->SetRadioGroupEnd(count
);
214 wxFAIL_MSG( _T("where is the radio group start item?") );
218 else // not a radio item
223 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
230 // check the item initially
237 bool wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
239 return wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
);
242 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
244 // we need to find the items position in the child list
246 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
247 for ( pos
= 0; node
; pos
++ )
249 if ( node
->GetData() == item
)
252 node
= node
->GetNext();
255 // DoRemove() (unlike Remove) can only be called for existing item!
256 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
258 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu
) , pos
+ 1);
262 // otherwise, the change won't be visible
263 m_menuBar
->Refresh();
266 // and from internal data structures
267 return wxMenuBase::DoRemove(item
);
270 void wxMenu::SetTitle(const wxString
& label
)
273 UMASetMenuTitle(MAC_WXHMENU(m_hMenu
) , label
) ;
275 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
277 bool processed
= FALSE
;
279 // Try the menu's event handler
280 if ( !processed
&& GetEventHandler())
282 processed
= GetEventHandler()->ProcessEvent(event
);
285 // Try the window the menu was popped up from (and up through the
287 wxWindow
*win
= GetInvokingWindow();
288 if ( !processed
&& win
)
289 processed
= win
->GetEventHandler()->ProcessEvent(event
);
295 // ---------------------------------------------------------------------------
297 // ---------------------------------------------------------------------------
299 wxWindow
*wxMenu::GetWindow() const
301 if ( m_invokingWindow
!= NULL
)
302 return m_invokingWindow
;
303 else if ( m_menuBar
!= NULL
)
304 return (wxWindow
*) m_menuBar
->GetFrame();
309 // helper functions returning the mac menu position for a certain item, note that this is
310 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
312 int wxMenu::MacGetIndexFromId( int id
)
315 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
316 for ( pos
= 0; node
; pos
++ )
318 if ( node
->GetData()->GetId() == id
)
321 node
= node
->GetNext();
330 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
333 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
334 for ( pos
= 0; node
; pos
++ )
336 if ( node
->GetData() == pItem
)
339 node
= node
->GetNext();
348 void wxMenu::MacEnableMenu( bool bDoEnable
)
350 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
355 // MacOS needs to know about submenus somewhere within this menu
356 // before it can be displayed , also hide special menu items like preferences
357 // that are handled by the OS
358 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
360 wxMenuItem
* previousItem
= NULL
;
362 wxMenuItemList::Node
*node
;
364 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
366 item
= (wxMenuItem
*)node
->GetData();
367 wxMenu
* subMenu
= item
->GetSubMenu() ;
370 subMenu
->MacBeforeDisplay( true ) ;
375 if ( UMAGetSystemVersion() >= 0x1000 )
377 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
379 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
380 if ( GetMenuItems().GetCount() == pos
+ 1 &&
381 previousItem
!= NULL
&&
382 previousItem
->IsSeparator() )
384 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
390 previousItem
= item
;
394 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
397 // undo all changes from the MacBeforeDisplay call
398 void wxMenu::MacAfterDisplay( bool isSubMenu
)
401 ::DeleteMenu(MacGetMenuId());
403 wxMenuItem
* previousItem
= NULL
;
405 wxMenuItemList::Node
*node
;
407 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
409 item
= (wxMenuItem
*)node
->GetData();
410 wxMenu
* subMenu
= item
->GetSubMenu() ;
413 subMenu
->MacAfterDisplay( true ) ;
417 // no need to undo hidings
419 previousItem
= item
;
427 Mac Implementation note :
429 The Mac has only one global menubar, so we attempt to install the currently
430 active menubar from a frame, we currently don't take into account mdi-frames
431 which would ask for menu-merging
433 Secondly there is no mac api for changing a menubar that is not the current
434 menubar, so we have to wait for preparing the actual menubar until the
435 wxMenubar is to be used
437 We can in subsequent versions use MacInstallMenuBar to provide some sort of
438 auto-merge for MDI in case this will be necessary
442 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
443 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
445 void wxMenuBar::Init()
447 m_eventHandler
= this;
448 m_menuBarFrame
= NULL
;
449 m_invokingWindow
= (wxWindow
*) NULL
;
452 wxMenuBar::wxMenuBar()
457 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
463 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
467 m_titles
.Alloc(count
);
469 for ( int i
= 0; i
< count
; i
++ )
471 m_menus
.Append(menus
[i
]);
472 m_titles
.Add(titles
[i
]);
474 menus
[i
]->Attach(this);
478 wxMenuBar::~wxMenuBar()
480 if (s_macCommonMenuBar
== this)
481 s_macCommonMenuBar
= NULL
;
482 if (s_macInstalledMenuBar
== this)
485 s_macInstalledMenuBar
= NULL
;
490 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
492 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
497 void wxMenuBar::MacInstallMenuBar()
499 if ( s_macInstalledMenuBar
== this )
502 wxStAppResource resload
;
504 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
506 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
507 ::SetMenuBar( menubar
) ;
508 #if TARGET_API_MAC_CARBON
509 ::DisposeMenuBar( menubar
) ;
511 ::DisposeHandle( menubar
) ;
514 #if TARGET_API_MAC_OS8
515 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
516 if ( CountMenuItems( menu
) == 2 )
518 ::AppendResMenu(menu
, 'DRVR');
522 // clean-up the help menu before adding new items
523 MenuHandle mh
= NULL
;
524 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
526 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
528 DeleteMenuItem( mh
, i
) ;
536 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
538 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
539 if ( item
== NULL
|| !(item
->IsEnabled()) )
540 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
542 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
545 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
547 wxMenuItemList::Node
*node
;
550 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
552 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
559 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
561 item
= (wxMenuItem
*)node
->GetData();
562 subMenu
= item
->GetSubMenu() ;
565 // we don't support hierarchical menus in the help menu yet
569 if ( item
->IsSeparator() )
572 MacAppendMenu(mh
, "\p-" );
576 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
578 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
580 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() );
581 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
582 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
583 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
589 UMAAppendMenuItem(mh
, item
->GetText() , entry
);
590 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
601 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
] ) ;
602 m_menus
[i
]->MacBeforeDisplay(false) ;
603 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
607 s_macInstalledMenuBar
= this;
610 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
612 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
613 m_menus
[pos
]->MacEnableMenu( enable
) ;
617 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
619 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
621 m_titles
[pos
] = label
;
628 m_menus
[pos
]->SetTitle( label
) ;
629 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
631 ::SetMenuBar( GetMenuBar() ) ;
636 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
638 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
639 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
641 return m_titles
[pos
];
644 int wxMenuBar::FindMenu(const wxString
& title
)
646 wxString menuTitle
= wxStripMenuCodes(title
);
648 size_t count
= GetMenuCount();
649 for ( size_t i
= 0; i
< count
; i
++ )
651 wxString title
= wxStripMenuCodes(m_titles
[i
]);
652 if ( menuTitle
== title
)
661 // ---------------------------------------------------------------------------
662 // wxMenuBar construction
663 // ---------------------------------------------------------------------------
665 // ---------------------------------------------------------------------------
666 // wxMenuBar construction
667 // ---------------------------------------------------------------------------
669 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
671 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
674 m_titles
[pos
] = title
;
678 if (s_macInstalledMenuBar
== this)
680 menuOld
->MacAfterDisplay( false ) ;
681 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
683 menu
->MacBeforeDisplay( false ) ;
684 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
685 if ( pos
== m_menus
.GetCount() - 1)
687 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
691 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
702 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
704 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
707 m_titles
.Insert(title
, pos
);
709 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
711 if ( IsAttached() && s_macInstalledMenuBar
== this )
713 if (s_macInstalledMenuBar
== this)
715 menu
->MacBeforeDisplay( false ) ;
716 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
718 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
722 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
731 wxMenu
*wxMenuBar::Remove(size_t pos
)
733 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
739 if (s_macInstalledMenuBar
== this)
741 ::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
) ;
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
);