1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
8 // Copyright: (c) AUTHOR
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
13 // ============================================================================
14 // headers & declarations
15 // ============================================================================
21 #pragma implementation "menu.h"
22 #pragma implementation "menuitem.h"
26 #include "wx/menuitem.h"
30 #include "wx/mac/uma.h"
32 // other standard headers
33 // ----------------------
36 #if !USE_SHARED_LIBRARY
37 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
38 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxEvtHandler
)
41 // the (popup) menu title has this special id
42 static const int idMenuTitle
= -2;
43 static int formerHelpMenuItems
= 0 ;
45 const short kwxMacMenuBarResource
= 1 ;
46 const short kwxMacAppleMenuId
= 1 ;
48 // ============================================================================
50 // ============================================================================
55 // Construct a menu with optional title (then use append)
57 short wxMenu::s_macNextMenuId
= 2 ;
65 wxMenuItem::MacBuildMenuString( label
, NULL
, NULL
, m_title
, false );
66 m_macMenuId
= s_macNextMenuId
++;
67 wxCHECK_RET( s_macNextMenuId
< 236 , "menu ids > 235 cannot be used for submenus on mac" );
68 m_hMenu
= UMANewMenu(m_macMenuId
, label
);
72 wxLogLastError("CreatePopupMenu");
75 // if we have a title, insert it in the beginning of the menu
78 Append(idMenuTitle
, m_title
) ;
86 UMADisposeMenu(m_hMenu
);
90 WX_CLEAR_ARRAY(m_accels
);
96 // not available on the mac platform
101 int wxMenu::FindAccel(int id
) const
103 size_t n
, count
= m_accels
.GetCount();
104 for ( n
= 0; n
< count
; n
++ )
106 if ( m_accels
[n
]->m_command
== id
)
113 void wxMenu::UpdateAccel(wxMenuItem
*item
)
115 // find the (new) accel for this item
116 wxAcceleratorEntry
*accel
= wxGetAccelFromString(item
->GetText());
118 accel
->m_command
= item
->GetId();
121 int n
= FindAccel(item
->GetId());
122 if ( n
== wxNOT_FOUND
)
124 // no old, add new if any
128 return; // skipping RebuildAccelTable() below
132 // replace old with new or just remove the old one if no new
142 m_menuBar
->RebuildAccelTable();
146 #endif // wxUSE_ACCEL
148 // function appends a new item or submenu to the menu
149 // append a new item or submenu to the menu
150 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
152 wxASSERT_MSG( pItem
!= NULL
, "can't append NULL item to the menu" );
155 #endif // wxUSE_ACCEL
157 if ( pItem
->IsSeparator() )
159 if ( pos
== (size_t)-1 )
161 MacAppendMenu(m_hMenu
, "\p-");
165 MacInsertMenuItem(m_hMenu
, "\p-" , pos
);
170 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
171 if ( pSubMenu
!= NULL
)
174 wxASSERT_MSG( pSubMenu
->m_hMenu
!= NULL
, "invalid submenu added");
175 pSubMenu
->m_menuParent
= this ;
176 wxMenuItem::MacBuildMenuString( label
, NULL
, NULL
, pItem
->GetText() ,false);
178 if (wxMenuBar::MacGetInstalledMenuBar() == m_menuBar
)
180 UMAInsertMenu( pSubMenu
->m_hMenu
, -1 ) ;
183 if ( pos
== (size_t)-1 )
185 UMAAppendSubMenuItem(m_hMenu
, label
, pSubMenu
->m_macMenuId
);
189 UMAInsertSubMenuItem(m_hMenu
, label
, pos
, pSubMenu
->m_macMenuId
);
197 wxMenuItem::MacBuildMenuString( label
, &key
, &modifiers
, pItem
->GetText(), pItem
->GetId() == wxApp::s_macAboutMenuItemId
);
200 // we cannot add empty menus on mac
204 if ( pos
== (size_t)-1 )
206 UMAAppendMenuItem(m_hMenu
, label
,key
,modifiers
);
210 UMAInsertMenuItem(m_hMenu
, label
, pos
,key
,modifiers
);
212 if ( pItem
->GetId() == idMenuTitle
)
214 if ( pos
== (size_t)-1 )
216 UMADisableMenuItem( m_hMenu
, CountMenuItems( m_hMenu
) ) ;
220 UMADisableMenuItem( m_hMenu
, pos
+ 1 ) ;
225 // if we're already attached to the menubar, we must update it
228 m_menuBar
->Refresh();
233 bool wxMenu::DoAppend(wxMenuItem
*item
)
235 return wxMenuBase::DoAppend(item
) && DoInsertOrAppend(item
);
238 bool wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
240 return wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
);
243 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
245 // we need to find the items position in the child list
247 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
248 for ( pos
= 0; node
; pos
++ )
250 if ( node
->GetData() == item
)
253 node
= node
->GetNext();
256 // DoRemove() (unlike Remove) can only be called for existing item!
257 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
260 // remove the corresponding accel from the accel table
261 int n
= FindAccel(item
->GetId());
262 if ( n
!= wxNOT_FOUND
)
268 //else: this item doesn't have an accel, nothing to do
269 #endif // wxUSE_ACCEL
271 ::DeleteMenuItem( m_hMenu
, pos
+ 1);
275 // otherwise, the chane won't be visible
276 m_menuBar
->Refresh();
279 // and from internal data structures
280 return wxMenuBase::DoRemove(item
);
283 // ---------------------------------------------------------------------------
284 // accelerator helpers
285 // ---------------------------------------------------------------------------
289 // create the wxAcceleratorEntries for our accels and put them into provided
290 // array - return the number of accels we have
291 size_t wxMenu::CopyAccels(wxAcceleratorEntry
*accels
) const
293 size_t count
= GetAccelCount();
294 for ( size_t n
= 0; n
< count
; n
++ )
296 *accels
++ = *m_accels
[n
];
302 #endif // wxUSE_ACCEL
304 void wxMenu::SetTitle(const wxString
& label
)
308 wxMenuItem::MacBuildMenuString( title
, NULL
, NULL
, label
, false );
309 UMASetMenuTitle( m_hMenu
, title
) ;
311 bool wxMenu::ProcessCommand(wxCommandEvent
& event
)
313 bool processed
= FALSE
;
315 #if WXWIN_COMPATIBILITY
319 (void)(*(m_callback
))(*this, event
);
322 #endif WXWIN_COMPATIBILITY
324 // Try the menu's event handler
325 if ( !processed
&& GetEventHandler())
327 processed
= GetEventHandler()->ProcessEvent(event
);
330 // Try the window the menu was popped up from (and up through the
332 wxWindow
*win
= GetInvokingWindow();
333 if ( !processed
&& win
)
334 processed
= win
->GetEventHandler()->ProcessEvent(event
);
340 // ---------------------------------------------------------------------------
342 // ---------------------------------------------------------------------------
344 void wxMenu::Attach(wxMenuBar
*menubar
)
346 // menu can be in at most one menubar because otherwise they would both
347 // delete the menu pointer
348 wxASSERT_MSG( !m_menuBar
, wxT("menu belongs to 2 menubars, expect a crash") );
353 void wxMenu::Detach()
355 wxASSERT_MSG( m_menuBar
, wxT("can't detach menu if it's not attached") );
360 wxWindow
*wxMenu::GetWindow() const
362 if ( m_invokingWindow
!= NULL
)
363 return m_invokingWindow
;
364 else if ( m_menuBar
!= NULL
)
365 return m_menuBar
->GetFrame();
370 // helper functions returning the mac menu position for a certain item, note that this is
371 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
373 int wxMenu::MacGetIndexFromId( int id
)
376 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
377 for ( pos
= 0; node
; pos
++ )
379 if ( node
->GetData()->GetId() == id
)
382 node
= node
->GetNext();
391 int wxMenu::MacGetIndexFromItem( wxMenuItem
*pItem
)
394 wxMenuItemList::Node
*node
= GetMenuItems().GetFirst();
395 for ( pos
= 0; node
; pos
++ )
397 if ( node
->GetData() == pItem
)
400 node
= node
->GetNext();
409 void wxMenu::MacEnableMenu( bool bDoEnable
)
412 UMAEnableMenuItem( m_hMenu
, 0 ) ;
414 UMADisableMenuItem( m_hMenu
, 0 ) ;
419 bool wxMenu::MacMenuSelect( wxEvtHandler
* handler
, long when
, int macMenuId
, int macMenuItemNum
)
424 if ( m_macMenuId
== macMenuId
)
426 node
= GetMenuItems().Nth(macMenuItemNum
-1);
429 wxMenuItem
*pItem
= (wxMenuItem
*)node
->Data();
431 if (pItem
->IsCheckable())
432 pItem
->Check(! pItem
->IsChecked());
434 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, pItem
->GetId());
435 event
.m_timeStamp
= when
;
436 event
.SetEventObject(handler
);
437 event
.SetInt( pItem
->GetId() );
439 bool processed
= false ;
441 #if WXWIN_COMPATIBILITY
445 (void) (*(m_callback
)) (*this, event
);
449 // Try the menu's event handler
450 if ( !processed
&& handler
)
452 processed
= handler
->ProcessEvent(event
);
455 // Try the window the menu was popped up from (and up
456 // through the hierarchy)
457 if ( !processed
&& GetInvokingWindow())
458 processed
= GetInvokingWindow()->GetEventHandler()->ProcessEvent(event
);
463 else if ( macMenuId
== kHMHelpMenuID
)
465 int menuItem
= formerHelpMenuItems
;
466 for (pos
= 0, node
= GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
468 wxMenuItem
* pItem
= (wxMenuItem
*) node
->Data() ;
470 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
471 if ( pSubMenu
!= NULL
)
476 if ( pItem
->GetId() != wxApp::s_macAboutMenuItemId
)
479 if ( menuItem
== macMenuItemNum
)
481 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, pItem
->GetId());
482 event
.m_timeStamp
= when
;
483 event
.SetEventObject(handler
);
484 event
.SetInt( pItem
->GetId() );
486 bool processed
= false ;
487 #if WXWIN_COMPATIBILITY
491 (void) (*(m_callback
)) (*this, event
);
495 // Try the menu's event handler
496 if ( !processed
&& handler
)
498 processed
= handler
->ProcessEvent(event
);
501 // Try the window the menu was popped up from (and up
502 // through the hierarchy)
503 if ( !processed
&& GetInvokingWindow())
504 processed
= GetInvokingWindow()->GetEventHandler()->ProcessEvent(event
);
512 for (pos
= 0, node
= GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
514 wxMenuItem
* pItem
= (wxMenuItem
*) node
->Data() ;
516 wxMenu
*pSubMenu
= pItem
->GetSubMenu() ;
517 if ( pSubMenu
!= NULL
)
519 if ( pSubMenu
->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )
531 Mac Implementation note :
533 The Mac has only one global menubar, so we attempt to install the currently
534 active menubar from a frame, we currently don't take into account mdi-frames
535 which would ask for menu-merging
537 Secondly there is no mac api for changing a menubar that is not the current
538 menubar, so we have to wait for preparing the actual menubar until the
539 wxMenubar is to be used
541 We can in subsequent versions use MacInstallMenuBar to provide some sort of
542 auto-merge for MDI in case this will be necessary
546 wxMenuBar
* wxMenuBar::s_macInstalledMenuBar
= NULL
;
548 void wxMenuBar::Init()
550 m_eventHandler
= this;
551 m_menuBarFrame
= NULL
;
554 wxMenuBar::wxMenuBar()
559 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
565 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
569 m_titles
.Alloc(count
);
571 for ( int i
= 0; i
< count
; i
++ )
573 m_menus
.Append(menus
[i
]);
574 m_titles
.Add(titles
[i
]);
576 menus
[i
]->Attach(this);
580 wxMenuBar::~wxMenuBar()
582 if (s_macInstalledMenuBar
== this)
585 s_macInstalledMenuBar
= NULL
;
590 void wxMenuBar::Refresh()
592 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
599 void wxMenuBar::RebuildAccelTable()
601 // merge the accelerators of all menus into one accel table
602 size_t nAccelCount
= 0;
603 size_t i
, count
= GetMenuCount();
604 for ( i
= 0; i
< count
; i
++ )
606 nAccelCount
+= m_menus
[i
]->GetAccelCount();
611 wxAcceleratorEntry
*accelEntries
= new wxAcceleratorEntry
[nAccelCount
];
614 for ( i
= 0; i
< count
; i
++ )
616 nAccelCount
+= m_menus
[i
]->CopyAccels(&accelEntries
[nAccelCount
]);
619 m_accelTable
= wxAcceleratorTable(nAccelCount
, accelEntries
);
621 delete [] accelEntries
;
625 #endif // wxUSE_ACCEL
628 void wxMenuBar::MacInstallMenuBar()
630 Handle menubar
= ::GetNewMBar( kwxMacMenuBarResource
) ;
632 wxCHECK_RET( menubar
!= NULL
, "can't read MBAR resource" );
633 ::SetMenuBar( menubar
) ;
634 ::DisposeHandle( menubar
) ;
636 MenuHandle menu
= ::GetMenuHandle( kwxMacAppleMenuId
) ;
637 ::AppendResMenu(menu
, 'DRVR');
639 for (int i
= 0; i
< m_menus
.GetCount(); i
++)
645 wxMenu
* menu
= m_menus
[i
] , *subMenu
= NULL
;
648 /* the help menu does not exist in CARBON anymore */
649 if( m_titles
[i
] == "?" || m_titles
[i
] == "&?" || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
)
651 MenuHandle mh
= NULL
;
652 if ( HMGetHelpMenuHandle( &mh
) != noErr
)
656 if ( formerHelpMenuItems
== 0 )
659 formerHelpMenuItems
= CountMenuItems( mh
) ;
662 for (pos
= 0 , node
= menu
->GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
664 item
= (wxMenuItem
*)node
->Data();
665 subMenu
= item
->GetSubMenu() ;
668 // we don't support hierarchical menus in the help menu yet
672 if ( item
->IsSeparator() )
675 UMAAppendMenuItem(mh
, "\p-" );
682 wxMenuItem::MacBuildMenuString( label
, &key
, &modifiers
, item
->GetText(), item
->GetId() != wxApp::s_macAboutMenuItemId
); // no shortcut in about menu
685 // we cannot add empty menus on mac
689 if ( item
->GetId() == wxApp::s_macAboutMenuItemId
)
691 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId
) , 1 , label
);
692 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId
) , 1 );
697 UMAAppendMenuItem(mh
, label
, key
, modifiers
);
706 wxMenuItem::MacBuildMenuString( label
, NULL
, NULL
, m_titles
[i
] , false );
707 UMASetMenuTitle( menu
->GetHMenu() , label
) ;
708 for (pos
= 0, node
= menu
->GetMenuItems().First(); node
; node
= node
->Next(), pos
++)
710 item
= (wxMenuItem
*)node
->Data();
711 subMenu
= item
->GetSubMenu() ;
714 UMAInsertMenu( subMenu
->GetHMenu() , -1 ) ;
717 UMAInsertMenu(m_menus
[i
]->GetHMenu(), 0);
722 s_macInstalledMenuBar
= this;
725 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
727 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
728 m_menus
[pos
]->MacEnableMenu( enable
) ;
732 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
734 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
736 m_titles
[pos
] = label
;
743 m_menus
[pos
]->SetTitle( label
) ;
744 if (wxMenuBar::s_macInstalledMenuBar
== this) // are we currently installed ?
746 ::SetMenuBar( GetMenuBar() ) ;
751 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
753 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
754 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
756 return m_titles
[pos
];
759 int wxMenuBar::FindMenu(const wxString
& title
)
761 wxString menuTitle
= wxStripMenuCodes(title
);
763 size_t count
= GetMenuCount();
764 for ( size_t i
= 0; i
< count
; i
++ )
766 wxString title
= wxStripMenuCodes(m_titles
[i
]);
767 if ( menuTitle
== title
)
776 // ---------------------------------------------------------------------------
777 // wxMenuBar construction
778 // ---------------------------------------------------------------------------
780 // ---------------------------------------------------------------------------
781 // wxMenuBar construction
782 // ---------------------------------------------------------------------------
784 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
786 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
789 m_titles
[pos
] = title
;
793 if (s_macInstalledMenuBar
== this)
795 UMADeleteMenu( menuOld
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
798 wxMenuItem::MacBuildMenuString( label
, NULL
, NULL
, title
, false );
799 UMASetMenuTitle( menu
->GetHMenu() , label
) ;
800 if ( pos
== m_menus
.GetCount() - 1)
802 UMAInsertMenu( menu
->GetHMenu() , 0 ) ;
806 UMAInsertMenu( menu
->GetHMenu() , m_menus
[pos
+1]->MacGetMenuId() ) ;
813 if ( menuOld
->HasAccels() || menu
->HasAccels() )
815 // need to rebuild accell table
818 #endif // wxUSE_ACCEL
826 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
828 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
831 m_titles
.Insert(title
, pos
);
837 if ( pos
== (size_t) -1 )
839 ::InsertMenu( menu
->GetHMenu() , 0 ) ;
843 ::InsertMenu( menu
->GetHMenu() , m_menus
[pos
+1]->MacGetMenuId() ) ;
847 if ( menu
->HasAccels() )
849 // need to rebuild accell table
852 #endif // wxUSE_ACCEL
860 void wxMenuBar::MacMenuSelect(wxEvtHandler
* handler
, long when
, int macMenuId
, int macMenuItemNum
)
862 // first scan fast for direct commands, i.e. menus which have these commands directly in their own list
864 if ( macMenuId
== kwxMacAppleMenuId
&& macMenuItemNum
== 1 )
866 wxCommandEvent
event(wxEVT_COMMAND_MENU_SELECTED
, wxApp::s_macAboutMenuItemId
);
867 event
.m_timeStamp
= when
;
868 event
.SetEventObject(handler
);
869 event
.SetInt( wxApp::s_macAboutMenuItemId
);
870 handler
->ProcessEvent(event
);
874 for (int i
= 0; i
< m_menus
.GetCount() ; i
++)
876 if ( m_menus
[i
]->MacGetMenuId() == macMenuId
||
877 ( macMenuId
== kHMHelpMenuID
&& ( m_titles
[i
] == "?" || m_titles
[i
] == "&?" || m_titles
[i
] == wxApp::s_macHelpMenuTitleName
) )
880 if ( m_menus
[i
]->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )
884 //TODO flag this as an error since it must contain the item
890 for (int i
= 0; i
< m_menus
.GetCount(); i
++)
892 if ( m_menus
[i
]->MacMenuSelect( handler
, when
, macMenuId
, macMenuItemNum
) )
900 wxMenu
*wxMenuBar::Remove(size_t pos
)
902 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
908 if (s_macInstalledMenuBar
== this)
910 ::DeleteMenu( menu
->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
916 if ( menu
->HasAccels() )
918 // need to rebuild accell table
921 #endif // wxUSE_ACCEL
926 m_titles
.Remove(pos
);
931 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
933 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
934 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
936 if ( !wxMenuBarBase::Append(menu
, title
) )
945 if (s_macInstalledMenuBar
== this)
947 ::InsertMenu( menu
->GetHMenu() , 0 ) ;
951 if ( menu
->HasAccels() )
953 // need to rebuild accell table
956 #endif // wxUSE_ACCEL
964 void wxMenuBar::Attach(wxFrame
*frame
)
966 // wxASSERT_MSG( !IsAttached(), wxT("menubar already attached!") );
968 m_menuBarFrame
= frame
;
972 #endif // wxUSE_ACCEL
974 // ---------------------------------------------------------------------------
975 // wxMenuBar searching for menu items
976 // ---------------------------------------------------------------------------
978 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
979 int wxMenuBar::FindMenuItem(const wxString
& menuString
,
980 const wxString
& itemString
) const
982 wxString menuLabel
= wxStripMenuCodes(menuString
);
983 size_t count
= GetMenuCount();
984 for ( size_t i
= 0; i
< count
; i
++ )
986 wxString title
= wxStripMenuCodes(m_titles
[i
]);
987 if ( menuString
== title
)
988 return m_menus
[i
]->FindItem(itemString
);
994 wxMenuItem
*wxMenuBar::FindItem(int id
, wxMenu
**itemMenu
) const
999 wxMenuItem
*item
= NULL
;
1000 size_t count
= GetMenuCount();
1001 for ( size_t i
= 0; !item
&& (i
< count
); i
++ )
1003 item
= m_menus
[i
]->FindItem(id
, itemMenu
);