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
, "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
, "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
), "a" );
144 pos
= CountMenuItems(MAC_WXHMENU(m_hMenu
)) ;
148 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu
), "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 && previousItem
!= NULL
&& 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
;
447 void wxMenuBar::Init()
449 m_eventHandler
= this;
450 m_menuBarFrame
= NULL
;
451 m_invokingWindow
= (wxWindow
*) NULL
;
454 wxMenuBar::wxMenuBar()
459 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
465 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
469 m_titles
.Alloc(count
);
471 for ( int i
= 0; i
< count
; i
++ )
473 m_menus
.Append(menus
[i
]);
474 m_titles
.Add(titles
[i
]);
476 menus
[i
]->Attach(this);
480 wxMenuBar::~wxMenuBar()
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
, "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
++)
548 wxMenuItemList::Node
*node
;
551 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
553 if( m_titles
[i
] == "?" || m_titles
[i
] == "&?" || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
560 for (pos
= 0 , node
= menu
->GetMenuItems().GetFirst(); node
; node
= node
->GetNext(), pos
++)
562 item
= (wxMenuItem
*)node
->GetData();
563 subMenu
= item
->GetSubMenu() ;
566 // we don't support hierarchical menus in the help menu yet
570 if ( item
->IsSeparator() )
573 MacAppendMenu(mh
, "\p-" );
577 wxAcceleratorEntry
* entry
= wxGetAccelFromString( item
->GetText() ) ;
579 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
581 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetText() );
582 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 , true );
583 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId
) , 1 , item
->GetId() ) ;
584 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId
) , 1 , entry
) ;
590 UMAAppendMenuItem(mh
, item
->GetText() , entry
);
591 SetMenuItemCommandID( mh
, CountMenuItems(mh
) , item
->GetId() ) ;
602 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , m_titles
[i
] ) ;
603 m_menus
[i
]->MacBeforeDisplay(false) ;
604 ::InsertMenu(MAC_WXHMENU(m_menus
[i
]->GetHMenu()), 0);
608 s_macInstalledMenuBar
= this;
611 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
613 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
614 m_menus
[pos
]->MacEnableMenu( enable
) ;
618 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
620 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
622 m_titles
[pos
] = label
;
629 m_menus
[pos
]->SetTitle( label
) ;
630 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
632 ::SetMenuBar( GetMenuBar() ) ;
637 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
639 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
640 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
642 return m_titles
[pos
];
645 int wxMenuBar::FindMenu(const wxString
& title
)
647 wxString menuTitle
= wxStripMenuCodes(title
);
649 size_t count
= GetMenuCount();
650 for ( size_t i
= 0; i
< count
; i
++ )
652 wxString title
= wxStripMenuCodes(m_titles
[i
]);
653 if ( menuTitle
== title
)
662 // ---------------------------------------------------------------------------
663 // wxMenuBar construction
664 // ---------------------------------------------------------------------------
666 // ---------------------------------------------------------------------------
667 // wxMenuBar construction
668 // ---------------------------------------------------------------------------
670 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
672 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
675 m_titles
[pos
] = title
;
679 if (s_macInstalledMenuBar
== this)
681 menuOld
->MacAfterDisplay( false ) ;
682 ::DeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
684 menu
->MacBeforeDisplay( false ) ;
685 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
686 if ( pos
== m_menus
.GetCount() - 1)
688 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
692 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
703 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
705 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
708 m_titles
.Insert(title
, pos
);
710 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
712 if ( IsAttached() && s_macInstalledMenuBar
== this )
714 if (s_macInstalledMenuBar
== this)
716 menu
->MacBeforeDisplay( false ) ;
717 if ( pos
== (size_t) -1 || pos
+ 1 == m_menus
.GetCount() )
719 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
723 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , m_menus
[pos
+1]->MacGetMenuId() ) ;
732 wxMenu
*wxMenuBar::Remove(size_t pos
)
734 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
740 if (s_macInstalledMenuBar
== this)
742 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
750 m_titles
.Remove(pos
);
755 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
757 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
758 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
760 if ( !wxMenuBarBase::Append(menu
, title
) )
765 UMASetMenuTitle( MAC_WXHMENU(menu
->GetHMenu()) , title
) ;
769 if (s_macInstalledMenuBar
== this)
771 ::InsertMenu( MAC_WXHMENU(menu
->GetHMenu()) , 0 ) ;
777 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
778 // adding menu later on.
779 if (m_invokingWindow
)
780 wxMenubarSetInvokingWindow( menu
, m_invokingWindow
);
785 static void wxMenubarUnsetInvokingWindow( wxMenu
*menu
)
787 menu
->SetInvokingWindow( (wxWindow
*) NULL
);
789 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
792 wxMenuItem
*menuitem
= node
->GetData();
793 if (menuitem
->IsSubMenu())
794 wxMenubarUnsetInvokingWindow( menuitem
->GetSubMenu() );
795 node
= node
->GetNext();
799 static void wxMenubarSetInvokingWindow( wxMenu
*menu
, wxWindow
*win
)
801 menu
->SetInvokingWindow( win
);
803 wxMenuItemList::Node
*node
= menu
->GetMenuItems().GetFirst();
806 wxMenuItem
*menuitem
= node
->GetData();
807 if (menuitem
->IsSubMenu())
808 wxMenubarSetInvokingWindow( menuitem
->GetSubMenu() , win
);
809 node
= node
->GetNext();
813 void wxMenuBar::UnsetInvokingWindow()
815 m_invokingWindow
= (wxWindow
*) NULL
;
816 wxMenuList::Node
*node
= m_menus
.GetFirst();
819 wxMenu
*menu
= node
->GetData();
820 wxMenubarUnsetInvokingWindow( menu
);
821 node
= node
->GetNext();
825 void wxMenuBar::SetInvokingWindow(wxFrame
*frame
)
827 m_invokingWindow
= frame
;
828 wxMenuList::Node
*node
= m_menus
.GetFirst();
831 wxMenu
*menu
= node
->GetData();
832 wxMenubarSetInvokingWindow( menu
, frame
);
833 node
= node
->GetNext();
837 void wxMenuBar::Detach()
839 wxMenuBarBase::Detach() ;
842 void wxMenuBar::Attach(wxFrame
*frame
)
844 wxMenuBarBase::Attach( frame
) ;
846 // ---------------------------------------------------------------------------
847 // wxMenuBar searching for menu items
848 // ---------------------------------------------------------------------------
850 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
851 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
852 const wxString
& itemString
) const
854 wxString menuLabel
= wxStripMenuCodes(menuString
);
855 size_t count
= GetMenuCount();
856 for ( size_t i
= 0; i
< count
; i
++ )
858 wxString title
= wxStripMenuCodes(m_titles
[i
]);
859 if ( menuString
== title
)
860 return m_menus
[i
]->FindItem(itemString
);
866 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
871 wxMenuItem
*item
= NULL
;
872 size_t count
= GetMenuCount();
873 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
875 item
= m_menus
[i
]->FindItem(id
, itemMenu
);