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 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , pos
);
151 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu
) , pos
, pItem
->GetId() ) ;
152 pItem
->UpdateItemText() ;
153 pItem
->UpdateItemBitmap() ;
154 pItem
->UpdateItemStatus() ;
156 if ( pItem
->GetId() == idMenuTitle
)
158 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , pos
, false ) ;
162 // if we're already attached to the menubar, we must update it
165 m_menuBar
->Refresh();
170 void wxMenu::EndRadioGroup()
172 // we're not inside a radio group any longer
173 m_startRadioGroup
= -1;
176 bool wxMenu::DoAppend(wxMenuItem
*item
)
178 wxCHECK_MSG( item
, FALSE
, _T("NULL item in wxMenu::DoAppend") );
182 if ( item
->GetKind() == wxITEM_RADIO
)
184 int count
= GetMenuItemCount();
186 if ( m_startRadioGroup
== -1 )
188 // start a new radio group
189 m_startRadioGroup
= count
;
191 // for now it has just one element
192 item
->SetAsRadioGroupStart();
193 item
->SetRadioGroupEnd(m_startRadioGroup
);
195 // ensure that we have a checked item in the radio group
198 else // extend the current radio group
200 // we need to update its end item
201 item
->SetRadioGroupStart(m_startRadioGroup
);
202 wxMenuItemList::Node
*node
= GetMenuItems().Item(m_startRadioGroup
);
206 node
->GetData()->SetRadioGroupEnd(count
);
210 wxFAIL_MSG( _T("where is the radio group start item?") );
214 else // not a radio item
219 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
226 // check the item initially
233 bool wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
235 return 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 m_menuBar
->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
) ;
271 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
273 bool processed
= FALSE
;
275 #if WXWIN_COMPATIBILITY
279 (void)(*(m_callback
))(*this, event
);
282 #endif WXWIN_COMPATIBILITY
284 // Try the menu's event handler
285 if ( !processed
&& GetEventHandler())
287 processed
= GetEventHandler()->ProcessEvent(event
);
290 // Try the window the menu was popped up from (and up through the
292 wxWindow
*win
= GetInvokingWindow();
293 if ( !processed
&& win
)
294 processed
= win
->GetEventHandler()->ProcessEvent(event
);
300 // ---------------------------------------------------------------------------
302 // ---------------------------------------------------------------------------
304 wxWindow
*wxMenu::GetWindow() const
306 if ( m_invokingWindow
!= NULL
)
307 return m_invokingWindow
;
308 else if ( m_menuBar
!= NULL
)
309 return (wxWindow
*) m_menuBar
->GetFrame();
314 // helper functions returning the mac menu position for a certain item, note that this is
315 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
317 int wxMenu::MacGetIndexFromId( int id
)
320 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
321 for ( pos
= 0; node
; pos
++ )
323 if ( node
->GetData()->GetId() == id
)
326 node
= node
->GetNext();
335 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
338 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
339 for ( pos
= 0; node
; pos
++ )
341 if ( node
->GetData() == pItem
)
344 node
= node
->GetNext();
353 void wxMenu::MacEnableMenu( bool bDoEnable
)
355 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
360 // MacOS needs to know about submenus somewhere within this menu
361 // before it can be displayed , also hide special menu items like preferences
362 // that are handled by the OS
363 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
365 wxMenuItem
* previousItem
= NULL
;
367 wxMenuItemList::Node
*node
;
369 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
371 item
= (wxMenuItem
*)node
->GetData();
372 wxMenu
* subMenu
= item
->GetSubMenu() ;
375 subMenu
->MacBeforeDisplay( true ) ;
380 if ( UMAGetSystemVersion() >= 0x1000 )
382 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
384 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
385 if ( GetMenuItems().GetCount() == pos
+ 1 &&
386 previousItem
!= NULL
&&
387 previousItem
->IsSeparator() )
389 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
395 previousItem
= item
;
399 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
402 // undo all changes from the MacBeforeDisplay call
403 void wxMenu::MacAfterDisplay( bool isSubMenu
)
406 ::DeleteMenu(MacGetMenuId());
408 wxMenuItem
* previousItem
= NULL
;
410 wxMenuItemList::Node
*node
;
412 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
414 item
= (wxMenuItem
*)node
->GetData();
415 wxMenu
* subMenu
= item
->GetSubMenu() ;
418 subMenu
->MacAfterDisplay( true ) ;
422 // no need to undo hidings
424 previousItem
= item
;
432 Mac Implementation note :
434 The Mac has only one global menubar, so we attempt to install the currently
435 active menubar from a frame, we currently don't take into account mdi-frames
436 which would ask for menu-merging
438 Secondly there is no mac api for changing a menubar that is not the current
439 menubar, so we have to wait for preparing the actual menubar until the
440 wxMenubar is to be used
442 We can in subsequent versions use MacInstallMenuBar to provide some sort of
443 auto-merge for MDI in case this will be necessary
447 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
449 void wxMenuBar::Init()
451 m_eventHandler
= this;
452 m_menuBarFrame
= NULL
;
453 m_invokingWindow
= (wxWindow
*) NULL
;
456 wxMenuBar::wxMenuBar()
461 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
467 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
471 m_titles
.Alloc(count
);
473 for ( int i
= 0; i
< count
; i
++ )
475 m_menus
.Append(menus
[i
]);
476 m_titles
.Add(titles
[i
]);
478 menus
[i
]->Attach(this);
482 wxMenuBar::~wxMenuBar()
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() );
583 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
584 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
585 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
591 UMAAppendMenuItem(mh
, item
->GetText() , entry
);
592 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
603 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
] ) ;
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
) ;
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
) ;
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() */ ) ;
751 m_titles
.Remove(pos
);
756 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
758 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
759 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
761 if ( !wxMenuBarBase::Append(menu
, title
) )
766 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
770 if (s_macInstalledMenuBar
== this)
772 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
778 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
779 // adding menu later on.
780 if (m_invokingWindow
)
781 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
786 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
788 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
790 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
793 wxMenuItem
*menuitem
= node
->GetData();
794 if (menuitem
->IsSubMenu())
795 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
796 node
= node
->GetNext();
800 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
802 menu
->SetInvokingWindow( win
);
804 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
807 wxMenuItem
*menuitem
= node
->GetData();
808 if (menuitem
->IsSubMenu())
809 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
810 node
= node
->GetNext();
814 void wxMenuBar::UnsetInvokingWindow()
816 m_invokingWindow
= (wxWindow
*) NULL
;
817 wxMenuList::Node
*node
= m_menus
.GetFirst();
820 wxMenu
*menu
= node
->GetData();
821 wxMenubarUnsetInvokingWindow( menu
);
822 node
= node
->GetNext();
826 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
828 m_invokingWindow
= frame
;
829 wxMenuList::Node
*node
= m_menus
.GetFirst();
832 wxMenu
*menu
= node
->GetData();
833 wxMenubarSetInvokingWindow( menu
, frame
);
834 node
= node
->GetNext();
838 void wxMenuBar::Detach()
840 wxMenuBarBase::Detach() ;
843 void wxMenuBar::Attach(wxFrame
*frame
)
845 wxMenuBarBase::Attach( frame
) ;
847 // ---------------------------------------------------------------------------
848 // wxMenuBar searching for menu items
849 // ---------------------------------------------------------------------------
851 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
852 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
853 const wxString
& itemString
) const
855 wxString menuLabel
= wxStripMenuCodes(menuString
);
856 size_t count
= GetMenuCount();
857 for ( size_t i
= 0; i
< count
; i
++ )
859 wxString title
= wxStripMenuCodes(m_titles
[i
]);
860 if ( menuString
== title
)
861 return m_menus
[i
]->FindItem(itemString
);
867 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
872 wxMenuItem
*item
= NULL
;
873 size_t count
= GetMenuCount();
874 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
876 item
= m_menus
[i
]->FindItem(id
, itemMenu
);