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 #if WXWIN_COMPATIBILITY
283 (void)(*(m_callback
))(*this, event
);
286 #endif WXWIN_COMPATIBILITY
288 // Try the menu's event handler
289 if ( !processed
&& GetEventHandler())
291 processed
= GetEventHandler()->ProcessEvent(event
);
294 // Try the window the menu was popped up from (and up through the
296 wxWindow
*win
= GetInvokingWindow();
297 if ( !processed
&& win
)
298 processed
= win
->GetEventHandler()->ProcessEvent(event
);
304 // ---------------------------------------------------------------------------
306 // ---------------------------------------------------------------------------
308 wxWindow
*wxMenu::GetWindow() const
310 if ( m_invokingWindow
!= NULL
)
311 return m_invokingWindow
;
312 else if ( m_menuBar
!= NULL
)
313 return (wxWindow
*) m_menuBar
->GetFrame();
318 // helper functions returning the mac menu position for a certain item, note that this is
319 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
321 int wxMenu::MacGetIndexFromId( int id
)
324 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
325 for ( pos
= 0; node
; pos
++ )
327 if ( node
->GetData()->GetId() == id
)
330 node
= node
->GetNext();
339 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
342 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
343 for ( pos
= 0; node
; pos
++ )
345 if ( node
->GetData() == pItem
)
348 node
= node
->GetNext();
357 void wxMenu::MacEnableMenu( bool bDoEnable
)
359 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
364 // MacOS needs to know about submenus somewhere within this menu
365 // before it can be displayed , also hide special menu items like preferences
366 // that are handled by the OS
367 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
369 wxMenuItem
* previousItem
= NULL
;
371 wxMenuItemList::Node
*node
;
373 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
375 item
= (wxMenuItem
*)node
->GetData();
376 wxMenu
* subMenu
= item
->GetSubMenu() ;
379 subMenu
->MacBeforeDisplay( true ) ;
384 if ( UMAGetSystemVersion() >= 0x1000 )
386 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
388 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
389 if ( GetMenuItems().GetCount() == pos
+ 1 &&
390 previousItem
!= NULL
&&
391 previousItem
->IsSeparator() )
393 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
399 previousItem
= item
;
403 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
406 // undo all changes from the MacBeforeDisplay call
407 void wxMenu::MacAfterDisplay( bool isSubMenu
)
410 ::DeleteMenu(MacGetMenuId());
412 wxMenuItem
* previousItem
= NULL
;
414 wxMenuItemList::Node
*node
;
416 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
418 item
= (wxMenuItem
*)node
->GetData();
419 wxMenu
* subMenu
= item
->GetSubMenu() ;
422 subMenu
->MacAfterDisplay( true ) ;
426 // no need to undo hidings
428 previousItem
= item
;
436 Mac Implementation note :
438 The Mac has only one global menubar, so we attempt to install the currently
439 active menubar from a frame, we currently don't take into account mdi-frames
440 which would ask for menu-merging
442 Secondly there is no mac api for changing a menubar that is not the current
443 menubar, so we have to wait for preparing the actual menubar until the
444 wxMenubar is to be used
446 We can in subsequent versions use MacInstallMenuBar to provide some sort of
447 auto-merge for MDI in case this will be necessary
451 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
452 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
454 void wxMenuBar::Init()
456 m_eventHandler
= this;
457 m_menuBarFrame
= NULL
;
458 m_invokingWindow
= (wxWindow
*) NULL
;
461 wxMenuBar::wxMenuBar()
466 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
472 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
476 m_titles
.Alloc(count
);
478 for ( int i
= 0; i
< count
; i
++ )
480 m_menus
.Append(menus
[i
]);
481 m_titles
.Add(titles
[i
]);
483 menus
[i
]->Attach(this);
487 wxMenuBar::~wxMenuBar()
489 if (s_macCommonMenuBar
== this)
490 s_macCommonMenuBar
= NULL
;
491 if (s_macInstalledMenuBar
== this)
494 s_macInstalledMenuBar
= NULL
;
499 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
501 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
506 void wxMenuBar::MacInstallMenuBar()
508 if ( s_macInstalledMenuBar
== this )
511 wxStAppResource resload
;
513 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
515 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
516 ::SetMenuBar( menubar
) ;
517 #if TARGET_API_MAC_CARBON
518 ::DisposeMenuBar( menubar
) ;
520 ::DisposeHandle( menubar
) ;
523 #if TARGET_API_MAC_OS8
524 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
525 if ( CountMenuItems( menu
) == 2 )
527 ::AppendResMenu(menu
, 'DRVR');
531 // clean-up the help menu before adding new items
532 MenuHandle mh
= NULL
;
533 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
535 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
537 DeleteMenuItem( mh
, i
) ;
545 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
547 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
548 if ( item
== NULL
|| !(item
->IsEnabled()) )
549 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
551 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
554 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
556 wxMenuItemList::Node
*node
;
559 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
561 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
568 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
570 item
= (wxMenuItem
*)node
->GetData();
571 subMenu
= item
->GetSubMenu() ;
574 // we don't support hierarchical menus in the help menu yet
578 if ( item
->IsSeparator() )
581 MacAppendMenu(mh
, "\p-" );
585 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
587 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
589 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() );
590 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
591 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
592 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
598 UMAAppendMenuItem(mh
, item
->GetText() , entry
);
599 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
610 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
] ) ;
611 m_menus
[i
]->MacBeforeDisplay(false) ;
612 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
616 s_macInstalledMenuBar
= this;
619 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
621 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
622 m_menus
[pos
]->MacEnableMenu( enable
) ;
626 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
628 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
630 m_titles
[pos
] = label
;
637 m_menus
[pos
]->SetTitle( label
) ;
638 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
640 ::SetMenuBar( GetMenuBar() ) ;
645 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
647 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
648 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
650 return m_titles
[pos
];
653 int wxMenuBar::FindMenu(const wxString
& title
)
655 wxString menuTitle
= wxStripMenuCodes(title
);
657 size_t count
= GetMenuCount();
658 for ( size_t i
= 0; i
< count
; i
++ )
660 wxString title
= wxStripMenuCodes(m_titles
[i
]);
661 if ( menuTitle
== title
)
670 // ---------------------------------------------------------------------------
671 // wxMenuBar construction
672 // ---------------------------------------------------------------------------
674 // ---------------------------------------------------------------------------
675 // wxMenuBar construction
676 // ---------------------------------------------------------------------------
678 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
680 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
683 m_titles
[pos
] = title
;
687 if (s_macInstalledMenuBar
== this)
689 menuOld
->MacAfterDisplay( false ) ;
690 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
692 menu
->MacBeforeDisplay( false ) ;
693 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
694 if ( pos
== m_menus
.GetCount() - 1)
696 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
700 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
711 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
713 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
716 m_titles
.Insert(title
, pos
);
718 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
720 if ( IsAttached() && s_macInstalledMenuBar
== this )
722 if (s_macInstalledMenuBar
== this)
724 menu
->MacBeforeDisplay( false ) ;
725 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
727 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
731 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
740 wxMenu
*wxMenuBar::Remove(size_t pos
)
742 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
748 if (s_macInstalledMenuBar
== this)
750 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
758 m_titles
.RemoveAt(pos
);
763 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
765 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
766 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
768 if ( !wxMenuBarBase::Append(menu
, title
) )
773 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
777 if (s_macInstalledMenuBar
== this)
779 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
785 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
786 // adding menu later on.
787 if (m_invokingWindow
)
788 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
793 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
795 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
797 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
800 wxMenuItem
*menuitem
= node
->GetData();
801 if (menuitem
->IsSubMenu())
802 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
803 node
= node
->GetNext();
807 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
809 menu
->SetInvokingWindow( win
);
811 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
814 wxMenuItem
*menuitem
= node
->GetData();
815 if (menuitem
->IsSubMenu())
816 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
817 node
= node
->GetNext();
821 void wxMenuBar::UnsetInvokingWindow()
823 m_invokingWindow
= (wxWindow
*) NULL
;
824 wxMenuList::Node
*node
= m_menus
.GetFirst();
827 wxMenu
*menu
= node
->GetData();
828 wxMenubarUnsetInvokingWindow( menu
);
829 node
= node
->GetNext();
833 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
835 m_invokingWindow
= frame
;
836 wxMenuList::Node
*node
= m_menus
.GetFirst();
839 wxMenu
*menu
= node
->GetData();
840 wxMenubarSetInvokingWindow( menu
, frame
);
841 node
= node
->GetNext();
845 void wxMenuBar::Detach()
847 wxMenuBarBase::Detach() ;
850 void wxMenuBar::Attach(wxFrame
*frame
)
852 wxMenuBarBase::Attach( frame
) ;
854 // ---------------------------------------------------------------------------
855 // wxMenuBar searching for menu items
856 // ---------------------------------------------------------------------------
858 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
859 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
860 const wxString
& itemString
) const
862 wxString menuLabel
= wxStripMenuCodes(menuString
);
863 size_t count
= GetMenuCount();
864 for ( size_t i
= 0; i
< count
; i
++ )
866 wxString title
= wxStripMenuCodes(m_titles
[i
]);
867 if ( menuString
== title
)
868 return m_menus
[i
]->FindItem(itemString
);
874 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
879 wxMenuItem
*item
= NULL
;
880 size_t count
= GetMenuCount();
881 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
883 item
= m_menus
[i
]->FindItem(id
, itemMenu
);