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
;
453 void wxMenuBar::Init()
455 m_eventHandler
= this;
456 m_menuBarFrame
= NULL
;
457 m_invokingWindow
= (wxWindow
*) NULL
;
460 wxMenuBar::wxMenuBar()
465 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
471 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
475 m_titles
.Alloc(count
);
477 for ( int i
= 0; i
< count
; i
++ )
479 m_menus
.Append(menus
[i
]);
480 m_titles
.Add(titles
[i
]);
482 menus
[i
]->Attach(this);
486 wxMenuBar::~wxMenuBar()
488 if (s_macInstalledMenuBar
== this)
491 s_macInstalledMenuBar
= NULL
;
496 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground
), const wxRect
*WXUNUSED(rect
))
498 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
503 void wxMenuBar::MacInstallMenuBar()
505 if ( s_macInstalledMenuBar
== this )
508 wxStAppResource resload
;
510 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
512 wxCHECK_RET( menubar
!= NULL
, wxT("can't read MBAR resource") );
513 ::SetMenuBar( menubar
) ;
514 #if TARGET_API_MAC_CARBON
515 ::DisposeMenuBar( menubar
) ;
517 ::DisposeHandle( menubar
) ;
520 #if TARGET_API_MAC_OS8
521 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
522 if ( CountMenuItems( menu
) == 2 )
524 ::AppendResMenu(menu
, 'DRVR');
528 // clean-up the help menu before adding new items
529 MenuHandle mh
= NULL
;
530 if ( UMAGetHelpMenu( &mh
, &firstUserHelpMenuItem
) == noErr
)
532 for ( int i
= CountMenuItems( mh
) ; i
>= firstUserHelpMenuItem
; --i
)
534 DeleteMenuItem( mh
, i
) ;
542 if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId
)
544 wxMenuItem
*item
= FindItem( wxApp::s_macPreferencesMenuItemId
, NULL
) ;
545 if ( item
== NULL
|| !(item
->IsEnabled()) )
546 DisableMenuCommand( NULL
, kHICommandPreferences
) ;
548 EnableMenuCommand( NULL
, kHICommandPreferences
) ;
551 for (size_t i
= 0; i
< m_menus
.GetCount(); i
++)
553 wxMenuItemList::Node
*node
;
556 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
558 if( m_titles
[i
] == wxT("?") || m_titles
[i
] == wxT("&?") || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
565 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
567 item
= (wxMenuItem
*)node
->GetData();
568 subMenu
= item
->GetSubMenu() ;
571 // we don't support hierarchical menus in the help menu yet
575 if ( item
->IsSeparator() )
578 MacAppendMenu(mh
, "\p-" );
582 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
584 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
586 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() );
587 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
588 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
589 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
595 UMAAppendMenuItem(mh
, item
->GetText() , entry
);
596 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
607 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
] ) ;
608 m_menus
[i
]->MacBeforeDisplay(false) ;
609 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
613 s_macInstalledMenuBar
= this;
616 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
618 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
619 m_menus
[pos
]->MacEnableMenu( enable
) ;
623 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
625 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
627 m_titles
[pos
] = label
;
634 m_menus
[pos
]->SetTitle( label
) ;
635 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
637 ::SetMenuBar( GetMenuBar() ) ;
642 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
644 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
645 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
647 return m_titles
[pos
];
650 int wxMenuBar::FindMenu(const wxString
& title
)
652 wxString menuTitle
= wxStripMenuCodes(title
);
654 size_t count
= GetMenuCount();
655 for ( size_t i
= 0; i
< count
; i
++ )
657 wxString title
= wxStripMenuCodes(m_titles
[i
]);
658 if ( menuTitle
== title
)
667 // ---------------------------------------------------------------------------
668 // wxMenuBar construction
669 // ---------------------------------------------------------------------------
671 // ---------------------------------------------------------------------------
672 // wxMenuBar construction
673 // ---------------------------------------------------------------------------
675 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
677 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
680 m_titles
[pos
] = title
;
684 if (s_macInstalledMenuBar
== this)
686 menuOld
->MacAfterDisplay( false ) ;
687 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
689 menu
->MacBeforeDisplay( false ) ;
690 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
691 if ( pos
== m_menus
.GetCount() - 1)
693 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
697 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
708 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
710 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
713 m_titles
.Insert(title
, pos
);
715 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
717 if ( IsAttached() && s_macInstalledMenuBar
== this )
719 if (s_macInstalledMenuBar
== this)
721 menu
->MacBeforeDisplay( false ) ;
722 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
724 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
728 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
737 wxMenu
*wxMenuBar::Remove(size_t pos
)
739 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
745 if (s_macInstalledMenuBar
== this)
747 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
755 m_titles
.Remove(pos
);
760 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
762 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
763 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
765 if ( !wxMenuBarBase::Append(menu
, title
) )
770 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
774 if (s_macInstalledMenuBar
== this)
776 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
782 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
783 // adding menu later on.
784 if (m_invokingWindow
)
785 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
790 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
792 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
794 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
797 wxMenuItem
*menuitem
= node
->GetData();
798 if (menuitem
->IsSubMenu())
799 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
800 node
= node
->GetNext();
804 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
806 menu
->SetInvokingWindow( win
);
808 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
811 wxMenuItem
*menuitem
= node
->GetData();
812 if (menuitem
->IsSubMenu())
813 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
814 node
= node
->GetNext();
818 void wxMenuBar::UnsetInvokingWindow()
820 m_invokingWindow
= (wxWindow
*) NULL
;
821 wxMenuList::Node
*node
= m_menus
.GetFirst();
824 wxMenu
*menu
= node
->GetData();
825 wxMenubarUnsetInvokingWindow( menu
);
826 node
= node
->GetNext();
830 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
832 m_invokingWindow
= frame
;
833 wxMenuList::Node
*node
= m_menus
.GetFirst();
836 wxMenu
*menu
= node
->GetData();
837 wxMenubarSetInvokingWindow( menu
, frame
);
838 node
= node
->GetNext();
842 void wxMenuBar::Detach()
844 wxMenuBarBase::Detach() ;
847 void wxMenuBar::Attach(wxFrame
*frame
)
849 wxMenuBarBase::Attach( frame
) ;
851 // ---------------------------------------------------------------------------
852 // wxMenuBar searching for menu items
853 // ---------------------------------------------------------------------------
855 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
856 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
857 const wxString
& itemString
) const
859 wxString menuLabel
= wxStripMenuCodes(menuString
);
860 size_t count
= GetMenuCount();
861 for ( size_t i
= 0; i
< count
; i
++ )
863 wxString title
= wxStripMenuCodes(m_titles
[i
]);
864 if ( menuString
== title
)
865 return m_menus
[i
]->FindItem(itemString
);
871 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
876 wxMenuItem
*item
= NULL
;
877 size_t count
= GetMenuCount();
878 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
880 item
= m_menus
[i
]->FindItem(id
, itemMenu
);