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
, wxFont::GetDefaultEncoding() );
77 wxLogLastError(wxT("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() == GetMenuBar())
129 pSubMenu
->MacBeforeDisplay( true ) ;
132 if ( pos
== (size_t)-1 )
133 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pSubMenu
->m_macMenuId
);
135 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu
), pItem
->GetText(), wxFont::GetDefaultEncoding() , pos
, pSubMenu
->m_macMenuId
);
136 pItem
->UpdateItemBitmap() ;
137 pItem
->UpdateItemStatus() ;
141 if ( pos
== (size_t)-1 )
143 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu
), wxT("a") , wxFont::GetDefaultEncoding() );
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"), wxFont::GetDefaultEncoding(), 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 GetMenuBar()->Refresh();
174 void wxMenu::EndRadioGroup()
176 // we're not inside a radio group any longer
177 m_startRadioGroup
= -1;
180 wxMenuItem
* wxMenu::DoAppend(wxMenuItem
*item
)
182 wxCHECK_MSG( item
, NULL
, _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 wxMenuItem
* wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
239 if (wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
))
245 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
247 // we need to find the items position in the child list
249 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
250 for ( pos
= 0; node
; pos
++ )
252 if ( node
->GetData() == item
)
255 node
= node
->GetNext();
258 // DoRemove() (unlike Remove) can only be called for existing item!
259 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
261 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu
) , pos
+ 1);
265 // otherwise, the change won't be visible
266 GetMenuBar()->Refresh();
269 // and from internal data structures
270 return wxMenuBase::DoRemove(item
);
273 void wxMenu::SetTitle(const wxString
& label
)
276 UMASetMenuTitle(MAC_WXHMENU(m_hMenu
) , label
, wxFont::GetDefaultEncoding() ) ;
278 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
280 bool processed
= FALSE
;
282 // Try the menu's event handler
283 if ( !processed
&& GetEventHandler())
285 processed
= GetEventHandler()->ProcessEvent(event
);
288 // Try the window the menu was popped up from (and up through the
290 wxWindow
*win
= GetInvokingWindow();
291 if ( !processed
&& win
)
292 processed
= win
->GetEventHandler()->ProcessEvent(event
);
298 // ---------------------------------------------------------------------------
300 // ---------------------------------------------------------------------------
302 wxWindow
*wxMenu::GetWindow() const
304 if ( m_invokingWindow
!= NULL
)
305 return m_invokingWindow
;
306 else if ( GetMenuBar() != NULL
)
307 return (wxWindow
*) GetMenuBar()->GetFrame();
312 // helper functions returning the mac menu position for a certain item, note that this is
313 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
315 int wxMenu::MacGetIndexFromId( int id
)
318 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
319 for ( pos
= 0; node
; pos
++ )
321 if ( node
->GetData()->GetId() == id
)
324 node
= node
->GetNext();
333 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
336 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
337 for ( pos
= 0; node
; pos
++ )
339 if ( node
->GetData() == pItem
)
342 node
= node
->GetNext();
351 void wxMenu::MacEnableMenu( bool bDoEnable
)
353 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu
) , 0 , bDoEnable
) ;
358 // MacOS needs to know about submenus somewhere within this menu
359 // before it can be displayed , also hide special menu items like preferences
360 // that are handled by the OS
361 void wxMenu::MacBeforeDisplay( bool isSubMenu
)
363 wxMenuItem
* previousItem
= NULL
;
365 wxMenuItemList::Node
*node
;
367 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
369 item
= (wxMenuItem
*)node
->GetData();
370 wxMenu
* subMenu
= item
->GetSubMenu() ;
373 subMenu
->MacBeforeDisplay( true ) ;
378 if ( UMAGetSystemVersion() >= 0x1000 )
380 if ( item
->GetId() == wxApp::s_macPreferencesMenuItemId
|| item
->GetId() == wxApp::s_macExitMenuItemId
)
382 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
+ 1, kMenuItemAttrHidden
, 0 );
383 if ( GetMenuItems().GetCount() == pos
+ 1 &&
384 previousItem
!= NULL
&&
385 previousItem
->IsSeparator() )
387 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ) , pos
, kMenuItemAttrHidden
, 0 );
393 previousItem
= item
;
397 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
400 // undo all changes from the MacBeforeDisplay call
401 void wxMenu::MacAfterDisplay( bool isSubMenu
)
404 ::DeleteMenu(MacGetMenuId());
406 wxMenuItem
* previousItem
= NULL
;
408 wxMenuItemList::Node
*node
;
410 for (pos
= 0, node
= GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
412 item
= (wxMenuItem
*)node
->GetData();
413 wxMenu
* subMenu
= item
->GetSubMenu() ;
416 subMenu
->MacAfterDisplay( true ) ;
420 // no need to undo hidings
422 previousItem
= item
;
430 Mac Implementation note :
432 The Mac has only one global menubar, so we attempt to install the currently
433 active menubar from a frame, we currently don't take into account mdi-frames
434 which would ask for menu-merging
436 Secondly there is no mac api for changing a menubar that is not the current
437 menubar, so we have to wait for preparing the actual menubar until the
438 wxMenubar is to be used
440 We can in subsequent versions use MacInstallMenuBar to provide some sort of
441 auto-merge for MDI in case this will be necessary
445 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
446 wxMenuBar
* wxMenuBar::s_macCommonMenuBar
= NULL
;
448 void wxMenuBar::Init()
450 m_eventHandler
= this;
451 m_menuBarFrame
= NULL
;
452 m_invokingWindow
= (wxWindow
*) NULL
;
455 wxMenuBar::wxMenuBar()
460 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
466 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
470 m_titles
.Alloc(count
);
472 for ( int i
= 0; i
< count
; i
++ )
474 m_menus
.Append(menus
[i
]);
475 m_titles
.Add(titles
[i
]);
477 menus
[i
]->Attach(this);
481 wxMenuBar::~wxMenuBar()
483 if (s_macCommonMenuBar
== this)
484 s_macCommonMenuBar
= NULL
;
485 if (s_macInstalledMenuBar
== this)
488 s_macInstalledMenuBar
= NULL
;
493 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
495 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
500 void wxMenuBar::MacInstallMenuBar()
502 if ( s_macInstalledMenuBar
== this )
505 wxStAppResource resload
;
507 MenuBarHandle menubar
= NULL
;
508 #if TARGET_API_MAC_OSX
509 menubar
= NewHandleClear( 6 /* sizeof( MenuBarHeader ) */ ) ;
511 menubar
= NewHandleClear( 12 ) ;
512 (*menubar
)[3] = 0x0a ;
514 ::SetMenuBar( menubar
) ;
515 DisposeMenuBar( menubar
) ;
516 MenuHandle appleMenu
= NULL
;
517 char appleMenuTitle
[3] = { 01 , kMenuAppleLogoFilledGlyph
, 0 } ;
519 verify_noerr( CreateNewMenu( kwxMacAppleMenuId
, 0 , &appleMenu
) ) ;
520 verify_noerr( SetMenuTitle( appleMenu
, (ConstStr255Param
) appleMenuTitle
) );
521 MacInsertMenuItem( appleMenu
, "\pAbout..." , 0 ) ;
522 MacInsertMenu( appleMenu
, 0 ) ;
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 bool wxMenuBar::Enable( bool enable
)
621 wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") );
623 for (i
= 0; i
< GetMenuCount(); i
++)
625 EnableTop(i
, enable
);
630 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
632 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
634 m_titles
[pos
] = label
;
641 m_menus
[pos
]->SetTitle( label
) ;
642 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
644 ::SetMenuBar( GetMenuBar() ) ;
649 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
651 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
652 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
654 return m_titles
[pos
];
657 int wxMenuBar::FindMenu(const wxString
& title
)
659 wxString menuTitle
= wxStripMenuCodes(title
);
661 size_t count
= GetMenuCount();
662 for ( size_t i
= 0; i
< count
; i
++ )
664 wxString title
= wxStripMenuCodes(m_titles
[i
]);
665 if ( menuTitle
== title
)
674 // ---------------------------------------------------------------------------
675 // wxMenuBar construction
676 // ---------------------------------------------------------------------------
678 // ---------------------------------------------------------------------------
679 // wxMenuBar construction
680 // ---------------------------------------------------------------------------
682 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
684 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
687 m_titles
[pos
] = title
;
691 if (s_macInstalledMenuBar
== this)
693 menuOld
->MacAfterDisplay( false ) ;
694 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
696 menu
->MacBeforeDisplay( false ) ;
697 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
698 if ( pos
== m_menus
.GetCount() - 1)
700 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
704 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
715 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
717 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
720 m_titles
.Insert(title
, pos
);
722 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
724 if ( IsAttached() && s_macInstalledMenuBar
== this )
726 if (s_macInstalledMenuBar
== this)
728 menu
->MacBeforeDisplay( false ) ;
729 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
731 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
735 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
744 wxMenu
*wxMenuBar::Remove(size_t pos
)
746 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
752 if (s_macInstalledMenuBar
== this)
754 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
760 m_titles
.RemoveAt(pos
);
765 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
767 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
768 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
770 if ( !wxMenuBarBase::Append(menu
, title
) )
775 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
, m_font
.GetEncoding() ) ;
779 if (s_macInstalledMenuBar
== this)
781 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
787 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
788 // adding menu later on.
789 if (m_invokingWindow
)
790 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
795 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
797 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
799 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
802 wxMenuItem
*menuitem
= node
->GetData();
803 if (menuitem
->IsSubMenu())
804 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
805 node
= node
->GetNext();
809 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
811 menu
->SetInvokingWindow( win
);
813 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
816 wxMenuItem
*menuitem
= node
->GetData();
817 if (menuitem
->IsSubMenu())
818 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
819 node
= node
->GetNext();
823 void wxMenuBar::UnsetInvokingWindow()
825 m_invokingWindow
= (wxWindow
*) NULL
;
826 wxMenuList::Node
*node
= m_menus
.GetFirst();
829 wxMenu
*menu
= node
->GetData();
830 wxMenubarUnsetInvokingWindow( menu
);
831 node
= node
->GetNext();
835 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
837 m_invokingWindow
= frame
;
838 wxMenuList::Node
*node
= m_menus
.GetFirst();
841 wxMenu
*menu
= node
->GetData();
842 wxMenubarSetInvokingWindow( menu
, frame
);
843 node
= node
->GetNext();
847 void wxMenuBar::Detach()
849 wxMenuBarBase::Detach() ;
852 void wxMenuBar::Attach(wxFrame
*frame
)
854 wxMenuBarBase::Attach( frame
) ;
856 // ---------------------------------------------------------------------------
857 // wxMenuBar searching for menu items
858 // ---------------------------------------------------------------------------
860 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
861 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
862 const wxString
& itemString
) const
864 wxString menuLabel
= wxStripMenuCodes(menuString
);
865 size_t count
= GetMenuCount();
866 for ( size_t i
= 0; i
< count
; i
++ )
868 wxString title
= wxStripMenuCodes(m_titles
[i
]);
869 if ( menuLabel
== title
)
870 return m_menus
[i
]->FindItem(itemString
);
876 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
881 wxMenuItem
*item
= NULL
;
882 size_t count
= GetMenuCount();
883 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
885 item
= m_menus
[i
]->FindItem(id
, itemMenu
);