1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/mac/carbon/menu.cpp
3// Purpose:     wxMenu, wxMenuBar, wxMenuItem
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// RCS-ID:      $Id: menu.cpp 62127 2009-09-25 15:07:53Z JS $
8// Copyright:   (c) Stefan Csomor
9// Licence:     wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// headers & declarations
14// ============================================================================
15
16// wxWidgets headers
17// -----------------
18
19#include "wx/wxprec.h"
20
21#include "wx/menu.h"
22
23#ifndef WX_PRECOMP
24    #include "wx/log.h"
25    #include "wx/app.h"
26    #include "wx/utils.h"
27    #include "wx/frame.h"
28    #include "wx/menuitem.h"
29#endif
30
31#include "wx/mac/uma.h"
32
33// other standard headers
34// ----------------------
35#include <string.h>
36
37IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
38IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxEvtHandler)
39
40// the (popup) menu title has this special id
41static const int idMenuTitle = -3;
42
43static const short kwxMacAppleMenuId = 1 ;
44
45
46// Find an item given the Macintosh Menu Reference
47
48WX_DECLARE_HASH_MAP(MenuRef, wxMenu*, wxPointerHash, wxPointerEqual, MacMenuMap);
49
50static MacMenuMap wxWinMacMenuList;
51
52wxMenu *wxFindMenuFromMacMenu(MenuRef inMenuRef)
53{
54    MacMenuMap::iterator node = wxWinMacMenuList.find(inMenuRef);
55
56    return (node == wxWinMacMenuList.end()) ? NULL : node->second;
57}
58
59void wxAssociateMenuWithMacMenu(MenuRef inMenuRef, wxMenu *menu) ;
60void wxAssociateMenuWithMacMenu(MenuRef inMenuRef, wxMenu *menu)
61{
62    // adding NULL MenuRef is (first) surely a result of an error and
63    // (secondly) breaks menu command processing
64    wxCHECK_RET( inMenuRef != (MenuRef) NULL, wxT("attempt to add a NULL MenuRef to menu list") );
65
66    wxWinMacMenuList[inMenuRef] = menu;
67}
68
69void wxRemoveMacMenuAssociation(wxMenu *menu) ;
70void wxRemoveMacMenuAssociation(wxMenu *menu)
71{
72   // iterate over all the elements in the class
73    MacMenuMap::iterator it;
74    for ( it = wxWinMacMenuList.begin(); it != wxWinMacMenuList.end(); ++it )
75    {
76        if ( it->second == menu )
77        {
78            wxWinMacMenuList.erase(it);
79            break;
80        }
81    }
82}
83
84void wxInsertMenuItemsInMenu(wxMenu* menu, MenuRef wm, MenuItemIndex insertAfter)
85{
86    wxMenuItemList::compatibility_iterator node;
87    wxMenuItem *item;
88    wxMenu *subMenu = NULL ;
89    bool newItems = false;
90
91    for (node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext())
92    {
93        item = (wxMenuItem *)node->GetData();
94        subMenu = item->GetSubMenu() ;
95        if (subMenu)
96        {
97            wxInsertMenuItemsInMenu(subMenu, (MenuRef)subMenu->GetHMenu(), 0);
98        }
99        if ( item->IsSeparator() )
100        {
101            if ( wm && newItems)
102                InsertMenuItemTextWithCFString( wm,
103                    CFSTR(""), insertAfter, kMenuItemAttrSeparator, 0);
104
105            newItems = false;
106        }
107        else
108        {
109            wxAcceleratorEntry*
110                entry = wxAcceleratorEntry::Create( item->GetText() ) ;
111
112            MenuItemIndex winListPos = (MenuItemIndex)-1;
113            OSStatus err = GetIndMenuItemWithCommandID(wm,
114                        wxIdToMacCommand ( item->GetId() ), 1, NULL, &winListPos);
115
116            if ( wm && err == menuItemNotFoundErr )
117            {
118                // NB: the only way to determine whether or not we should add
119                // a separator is to know if we've added menu items to the menu
120                // before the separator.
121                newItems = true;
122                UMAInsertMenuItem(wm, wxStripMenuCodes(item->GetText()) , wxFont::GetDefaultEncoding(), insertAfter, entry);
123                SetMenuItemCommandID( wm , insertAfter+1 , wxIdToMacCommand ( item->GetId() ) ) ;
124                SetMenuItemRefCon( wm , insertAfter+1 , (URefCon) item ) ;
125            }
126
127            delete entry ;
128        }
129    }
130}
131
132// ============================================================================
133// implementation
134// ============================================================================
135static void wxMenubarUnsetInvokingWindow( wxMenu *menu ) ;
136static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win );
137
138// Menus
139
140// Construct a menu with optional title (then use append)
141
142#ifdef __DARWIN__
143short wxMenu::s_macNextMenuId = 3 ;
144#else
145short wxMenu::s_macNextMenuId = 2 ;
146#endif
147
148static
149wxMenu *
150_wxMenuAt(const wxMenuList &menuList, size_t pos)
151{
152    wxMenuList::compatibility_iterator menuIter = menuList.GetFirst();
153
154    while (pos-- > 0)
155        menuIter = menuIter->GetNext();
156
157    return menuIter->GetData() ;
158}
159
160void wxMenu::Init()
161{
162    m_doBreak = false;
163    m_startRadioGroup = -1;
164
165    // create the menu
166    m_macMenuId = s_macNextMenuId++;
167    m_hMenu = UMANewMenu(m_macMenuId, m_title, wxFont::GetDefaultEncoding() );
168
169    if ( !m_hMenu )
170    {
171        wxLogLastError(wxT("UMANewMenu failed"));
172    }
173
174    wxAssociateMenuWithMacMenu( (MenuRef)m_hMenu , this ) ;
175
176    // if we have a title, insert it in the beginning of the menu
177    if ( !m_title.empty() )
178    {
179        Append(idMenuTitle, m_title) ;
180        AppendSeparator() ;
181    }
182}
183
184wxMenu::~wxMenu()
185{
186    wxRemoveMacMenuAssociation( this ) ;
187    if (MAC_WXHMENU(m_hMenu))
188        ::DisposeMenu(MAC_WXHMENU(m_hMenu));
189}
190
191void wxMenu::Break()
192{
193    // not available on the mac platform
194}
195
196void wxMenu::Attach(wxMenuBarBase *menubar)
197{
198    wxMenuBase::Attach(menubar);
199
200    EndRadioGroup();
201}
202
203// function appends a new item or submenu to the menu
204// append a new item or submenu to the menu
205bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos)
206{
207    wxASSERT_MSG( pItem != NULL, wxT("can't append NULL item to the menu") );
208
209    if ( pItem->IsSeparator() )
210    {
211        if ( pos == (size_t)-1 )
212            AppendMenuItemTextWithCFString( MAC_WXHMENU(m_hMenu),
213                CFSTR(""), kMenuItemAttrSeparator, 0,NULL);
214        else
215            InsertMenuItemTextWithCFString( MAC_WXHMENU(m_hMenu),
216                CFSTR(""), pos, kMenuItemAttrSeparator, 0);
217    }
218    else
219    {
220        wxMenu *pSubMenu = pItem->GetSubMenu() ;
221        if ( pSubMenu != NULL )
222        {
223            wxASSERT_MSG( pSubMenu->m_hMenu != NULL , wxT("invalid submenu added"));
224            pSubMenu->m_menuParent = this ;
225
226            // We need the !GetMenuBar() check to make sure we run MacBeforeDisplay()
227            // for popup menus and other menus which may not be part of the main
228            // menu bar.
229            if (!GetMenuBar() || wxMenuBar::MacGetInstalledMenuBar() == GetMenuBar())
230                pSubMenu->MacBeforeDisplay( true ) ;
231
232            if ( pos == (size_t)-1 )
233                UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu), wxStripMenuCodes(pItem->GetText()), wxFont::GetDefaultEncoding(), pSubMenu->m_macMenuId);
234            else
235                UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu), wxStripMenuCodes(pItem->GetText()), wxFont::GetDefaultEncoding(), pos, pSubMenu->m_macMenuId);
236
237            pItem->UpdateItemBitmap() ;
238            pItem->UpdateItemStatus() ;
239        }
240        else
241        {
242            if ( pos == (size_t)-1 )
243            {
244                UMAAppendMenuItem(MAC_WXHMENU(m_hMenu), wxT("a") , wxFont::GetDefaultEncoding() );
245                pos = CountMenuItems(MAC_WXHMENU(m_hMenu)) ;
246            }
247            else
248            {
249                // MacOS counts menu items from 1 and inserts after, therefore having the
250                // same effect as wx 0 based and inserting before, we must correct pos
251                // after however for updates to be correct
252                UMAInsertMenuItem(MAC_WXHMENU(m_hMenu), wxT("a"), wxFont::GetDefaultEncoding(), pos);
253                pos += 1 ;
254            }
255
256            SetMenuItemCommandID( MAC_WXHMENU(m_hMenu) , pos , wxIdToMacCommand ( pItem->GetId() ) ) ;
257            SetMenuItemRefCon( MAC_WXHMENU(m_hMenu) , pos , (URefCon) pItem ) ;
258            pItem->UpdateItemText() ;
259            pItem->UpdateItemBitmap() ;
260            pItem->UpdateItemStatus() ;
261
262            if ( pItem->GetId() == idMenuTitle )
263                UMAEnableMenuItem(MAC_WXHMENU(m_hMenu) , pos , false ) ;
264        }
265    }
266
267    // if we're already attached to the menubar, we must update it
268    if ( IsAttached() && GetMenuBar()->IsAttached() )
269        GetMenuBar()->Refresh();
270
271    return true ;
272}
273
274void wxMenu::EndRadioGroup()
275{
276    // we're not inside a radio group any longer
277    m_startRadioGroup = -1;
278}
279
280wxMenuItem* wxMenu::DoAppend(wxMenuItem *item)
281{
282    wxCHECK_MSG( item, NULL, _T("NULL item in wxMenu::DoAppend") );
283
284    bool check = false;
285
286    if ( item->GetKind() == wxITEM_RADIO )
287    {
288        int count = GetMenuItemCount();
289
290        if ( m_startRadioGroup == -1 )
291        {
292            // start a new radio group
293            m_startRadioGroup = count;
294
295            // for now it has just one element
296            item->SetAsRadioGroupStart();
297            item->SetRadioGroupEnd(m_startRadioGroup);
298
299            // ensure that we have a checked item in the radio group
300            check = true;
301        }
302        else // extend the current radio group
303        {
304            // we need to update its end item
305            item->SetRadioGroupStart(m_startRadioGroup);
306            wxMenuItemList::compatibility_iterator node = GetMenuItems().Item(m_startRadioGroup);
307
308            if ( node )
309            {
310                node->GetData()->SetRadioGroupEnd(count);
311            }
312            else
313            {
314                wxFAIL_MSG( _T("where is the radio group start item?") );
315            }
316        }
317    }
318    else // not a radio item
319    {
320        EndRadioGroup();
321    }
322
323    if ( !wxMenuBase::DoAppend(item) || !DoInsertOrAppend(item) )
324        return NULL;
325
326    if ( check )
327        // check the item initially
328        item->Check(true);
329
330    return item;
331}
332
333wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
334{
335    if (wxMenuBase::DoInsert(pos, item) && DoInsertOrAppend(item, pos))
336        return item;
337
338    return NULL;
339}
340
341wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
342{
343    // we need to find the items position in the child list
344    size_t pos;
345    wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
346
347    for ( pos = 0; node; pos++ )
348    {
349        if ( node->GetData() == item )
350            break;
351
352        node = node->GetNext();
353    }
354
355    // DoRemove() (unlike Remove) can only be called for existing item!
356    wxCHECK_MSG( node, NULL, wxT("bug in wxMenu::Remove logic") );
357
358    ::DeleteMenuItem(MAC_WXHMENU(m_hMenu) , pos + 1);
359
360    if ( IsAttached() && GetMenuBar()->IsAttached() )
361        // otherwise, the change won't be visible
362        GetMenuBar()->Refresh();
363
364    // and from internal data structures
365    return wxMenuBase::DoRemove(item);
366}
367
368void wxMenu::SetTitle(const wxString& label)
369{
370    m_title = label ;
371    UMASetMenuTitle(MAC_WXHMENU(m_hMenu) , label , wxFont::GetDefaultEncoding() ) ;
372}
373
374bool wxMenu::ProcessCommand(wxCommandEvent & event)
375{
376    bool processed = false;
377
378    // Try the menu's event handler
379    if ( /* !processed && */ GetEventHandler())
380        processed = GetEventHandler()->ProcessEvent(event);
381
382    // Try the window the menu was popped up from
383    // (and up through the hierarchy)
384    wxWindow *win = GetInvokingWindow();
385    if ( !processed && win )
386        processed = win->GetEventHandler()->ProcessEvent(event);
387
388    return processed;
389}
390
391// ---------------------------------------------------------------------------
392// other
393// ---------------------------------------------------------------------------
394
395wxWindow *wxMenu::GetWindow() const
396{
397    if ( m_invokingWindow != NULL )
398        return m_invokingWindow;
399    else if ( GetMenuBar() != NULL)
400        return (wxWindow *) GetMenuBar()->GetFrame();
401
402    return NULL;
403}
404
405// helper functions returning the mac menu position for a certain item, note that this is
406// mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
407
408int wxMenu::MacGetIndexFromId( int id )
409{
410    size_t pos;
411    wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
412    for ( pos = 0; node; pos++ )
413    {
414        if ( node->GetData()->GetId() == id )
415            break;
416
417        node = node->GetNext();
418    }
419
420    if (!node)
421        return 0;
422
423    return pos + 1 ;
424}
425
426int wxMenu::MacGetIndexFromItem( wxMenuItem *pItem )
427{
428    size_t pos;
429    wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
430    for ( pos = 0; node; pos++ )
431    {
432        if ( node->GetData() == pItem )
433            break;
434
435        node = node->GetNext();
436    }
437
438    if (!node)
439        return 0;
440
441    return pos + 1 ;
442}
443
444void wxMenu::MacEnableMenu( bool bDoEnable )
445{
446    UMAEnableMenuItem(MAC_WXHMENU(m_hMenu) , 0 , bDoEnable ) ;
447
448    ::DrawMenuBar() ;
449}
450
451// MacOS needs to know about submenus somewhere within this menu
452// before it can be displayed, also hide special menu items
453// like preferences that are handled by the OS
454void wxMenu::MacBeforeDisplay( bool isSubMenu )
455{
456    wxMenuItem* previousItem = NULL ;
457    size_t pos ;
458    wxMenuItemList::compatibility_iterator node;
459    wxMenuItem *item;
460
461    for (pos = 0, node = GetMenuItems().GetFirst(); node; node = node->GetNext(), pos++)
462    {
463        item = (wxMenuItem *)node->GetData();
464        wxMenu* subMenu = item->GetSubMenu() ;
465        if (subMenu)
466        {
467            subMenu->MacBeforeDisplay( true ) ;
468        }
469        else // normal item
470        {
471#if TARGET_CARBON
472            // what we do here is to hide the special items which are
473            // shown in the application menu anyhow -- it doesn't make
474            // sense to show them in their normal place as well
475            if ( item->GetId() == wxApp::s_macAboutMenuItemId ||
476                ( UMAGetSystemVersion() >= 0x1000 && (
477                    item->GetId() == wxApp::s_macPreferencesMenuItemId ||
478                    item->GetId() == wxApp::s_macExitMenuItemId ) ) )
479
480            {
481                ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
482                                          pos + 1, kMenuItemAttrHidden, 0 );
483
484                // also check for a separator which was used just to
485                // separate this item from the others, so don't leave
486                // separator at the menu start or end nor 2 consecutive
487                // separators
488                wxMenuItemList::compatibility_iterator nextNode = node->GetNext();
489                wxMenuItem *next = nextNode ? nextNode->GetData() : NULL;
490
491                size_t posSeptoHide;
492                if ( !previousItem && next && next->IsSeparator() )
493                {
494                    // next (i.e. second as we must be first) item is
495                    // the separator to hide
496                    wxASSERT_MSG( pos == 0, _T("should be the menu start") );
497                    posSeptoHide = 2;
498                }
499                else if ( GetMenuItems().GetCount() == pos + 1 &&
500                            previousItem != NULL &&
501                                previousItem->IsSeparator() )
502                {
503                    // prev item is a trailing separator we want to hide
504                    posSeptoHide = pos;
505                }
506                else if ( previousItem && previousItem->IsSeparator() &&
507                            next && next->IsSeparator() )
508                {
509                    // two consecutive separators, this is one too many
510                    posSeptoHide = pos;
511                }
512                else // no separators to hide
513                {
514                    posSeptoHide = 0;
515                }
516
517                if ( posSeptoHide )
518                {
519                    // hide the separator as well
520                    ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
521                                              posSeptoHide,
522                                              kMenuItemAttrHidden,
523                                              0 );
524                }
525            }
526#endif // TARGET_CARBON
527        }
528
529        previousItem = item ;
530    }
531
532    if ( isSubMenu )
533        ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
534}
535
536// undo all changes from the MacBeforeDisplay call
537void wxMenu::MacAfterDisplay( bool isSubMenu )
538{
539    if ( isSubMenu )
540        ::DeleteMenu(MacGetMenuId());
541
542    wxMenuItemList::compatibility_iterator node;
543    wxMenuItem *item;
544
545    for (node = GetMenuItems().GetFirst(); node; node = node->GetNext())
546    {
547        item = (wxMenuItem *)node->GetData();
548        wxMenu* subMenu = item->GetSubMenu() ;
549        if (subMenu)
550        {
551            subMenu->MacAfterDisplay( true ) ;
552        }
553        else
554        {
555            // no need to undo hidings
556        }
557    }
558}
559
560wxInt32 wxMenu::MacHandleCommandProcess( wxMenuItem* item, int id, wxWindow* targetWindow )
561{
562    OSStatus result = eventNotHandledErr ;
563    if (item->IsCheckable())
564        item->Check( !item->IsChecked() ) ;
565
566    if ( SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) )
567        result = noErr ;
568    else
569    {
570        if ( targetWindow != NULL )
571        {
572            wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED , id);
573            event.SetEventObject(targetWindow);
574            event.SetInt(item->IsCheckable() ? item->IsChecked() : -1);
575
576            if ( targetWindow->GetEventHandler()->ProcessEvent(event) )
577                result = noErr ;
578        }
579    }
580    return result;
581}
582
583wxInt32 wxMenu::MacHandleCommandUpdateStatus( wxMenuItem* item, int id, wxWindow* targetWindow )
584{
585    OSStatus result = eventNotHandledErr ;
586    wxUpdateUIEvent event(id);
587    event.SetEventObject( this );
588
589    bool processed = false;
590
591    // Try the menu's event handler
592    {
593        wxEvtHandler *handler = GetEventHandler();
594        if ( handler )
595            processed = handler->ProcessEvent(event);
596    }
597
598    // Try the window the menu was popped up from
599    // (and up through the hierarchy)
600    if ( !processed )
601    {
602        const wxMenuBase *menu = this;
603        while ( menu )
604        {
605            wxWindow *win = menu->GetInvokingWindow();
606            if ( win )
607            {
608                processed = win->GetEventHandler()->ProcessEvent(event);
609                break;
610            }
611
612            menu = menu->GetParent();
613        }
614    }
615
616    if ( !processed && targetWindow != NULL)
617    {
618        processed = targetWindow->GetEventHandler()->ProcessEvent(event);
619    }
620
621    if ( processed )
622    {
623        // if anything changed, update the changed attribute
624        if (event.GetSetText())
625            SetLabel(id, event.GetText());
626        if (event.GetSetChecked())
627            Check(id, event.GetChecked());
628        if (event.GetSetEnabled())
629            Enable(id, event.GetEnabled());
630
631        result = noErr ;
632    }
633    return result;
634}
635
636// Menu Bar
637
638/*
639
640Mac Implementation note :
641
642The Mac has only one global menubar, so we attempt to install the currently
643active menubar from a frame, we currently don't take into account mdi-frames
644which would ask for menu-merging
645
646Secondly there is no mac api for changing a menubar that is not the current
647menubar, so we have to wait for preparing the actual menubar until the
648wxMenubar is to be used
649
650We can in subsequent versions use MacInstallMenuBar to provide some sort of
651auto-merge for MDI in case this will be necessary
652
653*/
654
655wxMenuBar* wxMenuBar::s_macInstalledMenuBar = NULL ;
656wxMenuBar* wxMenuBar::s_macCommonMenuBar = NULL ;
657bool     wxMenuBar::s_macAutoWindowMenu = true ;
658WXHMENU  wxMenuBar::s_macWindowMenuHandle = NULL ;
659
660void wxMenuBar::Init()
661{
662    m_eventHandler = this;
663    m_menuBarFrame = NULL;
664    m_invokingWindow = (wxWindow*) NULL;
665}
666
667wxMenuBar::wxMenuBar()
668{
669    Init();
670}
671
672wxMenuBar::wxMenuBar( long WXUNUSED(style) )
673{
674    Init();
675}
676
677wxMenuBar::wxMenuBar(size_t count, wxMenu *menus[], const wxString titles[], long WXUNUSED(style))
678{
679    Init();
680
681    m_titles.Alloc(count);
682
683    for ( size_t i = 0; i < count; i++ )
684    {
685        m_menus.Append(menus[i]);
686        m_titles.Add(titles[i]);
687
688        menus[i]->Attach(this);
689    }
690}
691
692wxMenuBar::~wxMenuBar()
693{
694    if (s_macCommonMenuBar == this)
695        s_macCommonMenuBar = NULL;
696
697    if (s_macInstalledMenuBar == this)
698    {
699        ::ClearMenuBar();
700        s_macInstalledMenuBar = NULL;
701    }
702}
703
704void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground), const wxRect *WXUNUSED(rect))
705{
706    wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
707
708    DrawMenuBar();
709}
710
711void wxMenuBar::MacInstallMenuBar()
712{
713    if ( s_macInstalledMenuBar == this )
714        return ;
715
716    MenuBarHandle menubar = NULL ;
717
718#if TARGET_API_MAC_OSX
719    menubar = NewHandleClear( 6 /* sizeof( MenuBarHeader ) */ ) ;
720#else
721    menubar = NewHandleClear( 12 ) ;
722    (*menubar)[3] = 0x0a ;
723#endif
724
725    ::SetMenuBar( menubar ) ;
726    DisposeMenuBar( menubar ) ;
727    MenuHandle appleMenu = NULL ;
728
729    verify_noerr( CreateNewMenu( kwxMacAppleMenuId , 0 , &appleMenu ) ) ;
730    verify_noerr( SetMenuTitleWithCFString( appleMenu , CFSTR( "\x14" ) ) );
731
732    // Add About/Preferences separator only on OS X
733    // KH/RN: Separator is always present on 10.3 but not on 10.2
734    // However, the change from 10.2 to 10.3 suggests it is preferred
735#if TARGET_API_MAC_OSX
736    InsertMenuItemTextWithCFString( appleMenu,
737                CFSTR(""), 0, kMenuItemAttrSeparator, 0);
738#endif
739    InsertMenuItemTextWithCFString( appleMenu,
740                CFSTR("About..."), 0, 0, 0);
741    MacInsertMenu( appleMenu , 0 ) ;
742
743    // clean-up the help menu before adding new items
744    static MenuHandle mh = NULL ;
745
746    if ( mh != NULL )
747    {
748        MenuItemIndex firstUserHelpMenuItem ;
749        if ( UMAGetHelpMenu( &mh , &firstUserHelpMenuItem) == noErr )
750        {
751            for ( int i = CountMenuItems( mh ) ; i >= firstUserHelpMenuItem ; --i )
752                DeleteMenuItem( mh , i ) ;
753        }
754        else
755        {
756            mh = NULL ;
757        }
758    }
759
760#if TARGET_CARBON
761    if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macPreferencesMenuItemId)
762    {
763        wxMenuItem *item = FindItem( wxApp::s_macPreferencesMenuItemId , NULL ) ;
764        if ( item == NULL || !(item->IsEnabled()) )
765            DisableMenuCommand( NULL , kHICommandPreferences ) ;
766        else
767            EnableMenuCommand( NULL , kHICommandPreferences ) ;
768    }
769
770    // Unlike preferences which may or may not exist, the Quit item should be always
771    // enabled unless it is added by the application and then disabled, otherwise
772    // a program would be required to add an item with wxID_EXIT in order to get the
773    // Quit menu item to be enabled, which seems a bit burdensome.
774    if ( UMAGetSystemVersion() >= 0x1000 && wxApp::s_macExitMenuItemId)
775    {
776        wxMenuItem *item = FindItem( wxApp::s_macExitMenuItemId , NULL ) ;
777        if ( item != NULL && !(item->IsEnabled()) )
778            DisableMenuCommand( NULL , kHICommandQuit ) ;
779        else
780            EnableMenuCommand( NULL , kHICommandQuit ) ;
781    }
782#endif
783    wxString strippedHelpMenuTitle = wxStripMenuCodes( wxApp::s_macHelpMenuTitleName ) ;
784    wxString strippedTranslatedHelpMenuTitle = wxStripMenuCodes( wxString( _("&Help") ) ) ;
785
786    wxString strippedWindowMenuTitle = wxStripMenuCodes( wxString(wxT("&Window")) /* wxApp::s_macWindowMenuTitleName */ ) ;
787    wxString strippedTranslatedWindowMenuTitle = wxStripMenuCodes( wxString( _("&Window") ) ) ;
788
789    wxMenuList::compatibility_iterator menuIter = m_menus.GetFirst();
790    for (size_t i = 0; i < m_menus.GetCount(); i++, menuIter = menuIter->GetNext())
791    {
792        wxMenuItemList::compatibility_iterator node;
793        wxMenuItem *item;
794        wxMenu* menu = menuIter->GetData() , *subMenu = NULL ;
795        wxString strippedMenuTitle = wxStripMenuCodes(m_titles[i]);
796
797        if ( strippedMenuTitle == wxT("?") || strippedMenuTitle == strippedHelpMenuTitle || strippedMenuTitle == strippedTranslatedHelpMenuTitle )
798        {
799            for (node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext())
800            {
801                item = (wxMenuItem *)node->GetData();
802                subMenu = item->GetSubMenu() ;
803                if (subMenu)
804                {
805                    UMAAppendMenuItem(mh, wxStripMenuCodes(item->GetText()) , wxFont::GetDefaultEncoding() );
806                    MenuItemIndex position = CountMenuItems(mh);
807                    ::SetMenuItemHierarchicalMenu(mh, position, MAC_WXHMENU(subMenu->GetHMenu()));
808                }
809                else
810                {
811                    if ( item->GetId() != wxApp::s_macAboutMenuItemId )
812                    {
813                        if ( mh == NULL )
814                        {
815                            MenuItemIndex firstUserHelpMenuItem ;
816                            if ( UMAGetHelpMenu( &mh , &firstUserHelpMenuItem) != noErr )
817                            {
818                                mh = NULL ;
819                                break ;
820                            }
821                        }
822                    }
823
824                    if ( item->IsSeparator() )
825                    {
826                        if ( mh )
827                            AppendMenuItemTextWithCFString( mh,
828                                CFSTR(""), kMenuItemAttrSeparator, 0,NULL);
829                    }
830                    else
831                    {
832                        wxAcceleratorEntry*
833                            entry = wxAcceleratorEntry::Create( item->GetText() ) ;
834
835                        if ( item->GetId() == wxApp::s_macAboutMenuItemId )
836                        {
837                            // this will be taken care of below
838                        }
839                        else
840                        {
841                            if ( mh )
842                            {
843                                UMAAppendMenuItem(mh, wxStripMenuCodes(item->GetText()) , wxFont::GetDefaultEncoding(), entry);
844                                MenuItemIndex position = CountMenuItems(mh);
845                                SetMenuItemCommandID( mh , position, wxIdToMacCommand ( item->GetId() ) );
846                                SetMenuItemRefCon( mh , position, (URefCon) item );
847                                item->DoUpdateItemBitmap( mh, position );
848                            }
849                        }
850
851                        delete entry ;
852                    }
853                }
854            }
855        }
856        else if ( (strippedMenuTitle == wxT("Window") || strippedMenuTitle == strippedWindowMenuTitle || strippedMenuTitle == strippedTranslatedWindowMenuTitle )
857                && GetAutoWindowMenu() )
858        {
859            if ( MacGetWindowMenuHMenu() == NULL )
860            {
861                CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
862            }
863
864            MenuRef wm = (MenuRef)MacGetWindowMenuHMenu();
865            if ( wm == NULL )
866                break;
867
868            // get the insertion point in the standard menu
869            MenuItemIndex winListStart;
870            GetIndMenuItemWithCommandID(wm,
871                        kHICommandWindowListSeparator, 1, NULL, &winListStart);
872
873            // add a separator so that the standard items and the custom items
874            // aren't mixed together, but only if this is the first run
875            OSStatus err = GetIndMenuItemWithCommandID(wm,
876                        'WXWM', 1, NULL, NULL);
877
878            if ( err == menuItemNotFoundErr )
879            {
880                InsertMenuItemTextWithCFString( wm,
881                        CFSTR(""), winListStart-1, kMenuItemAttrSeparator, 'WXWM');
882            }
883
884            wxInsertMenuItemsInMenu(menu, wm, winListStart);
885        }
886        else
887        {
888            UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , m_titles[i], GetFont().GetEncoding()  ) ;
889            menu->MacBeforeDisplay(false) ;
890
891            ::InsertMenu(MAC_WXHMENU(_wxMenuAt(m_menus, i)->GetHMenu()), 0);
892        }
893    }
894
895    // take care of the about menu item wherever it is
896    {
897        wxMenu* aboutMenu ;
898        wxMenuItem *aboutMenuItem = FindItem(wxApp::s_macAboutMenuItemId , &aboutMenu) ;
899        if ( aboutMenuItem )
900        {
901            wxAcceleratorEntry*
902                entry = wxAcceleratorEntry::Create( aboutMenuItem->GetText() ) ;
903            UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId ) , 1 , wxStripMenuCodes ( aboutMenuItem->GetText() ) , wxFont::GetDefaultEncoding() );
904            UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId ) , 1 , true );
905            SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId ) , 1 , kHICommandAbout ) ;
906            SetMenuItemRefCon(GetMenuHandle( kwxMacAppleMenuId ) , 1 , (URefCon)aboutMenuItem ) ;
907            UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId ) , 1 , entry ) ;
908
909            delete entry;
910        }
911    }
912
913    if ( GetAutoWindowMenu() )
914    {
915        if ( MacGetWindowMenuHMenu() == NULL )
916            CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
917
918        InsertMenu( (MenuHandle) MacGetWindowMenuHMenu() , 0 ) ;
919    }
920
921    ::DrawMenuBar() ;
922    s_macInstalledMenuBar = this;
923}
924
925void wxMenuBar::EnableTop(size_t pos, bool enable)
926{
927    wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
928
929    _wxMenuAt(m_menus, pos)->MacEnableMenu( enable ) ;
930    Refresh();
931}
932
933bool wxMenuBar::Enable(bool enable)
934{
935    wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") );
936
937    size_t i;
938    for (i = 0; i < GetMenuCount(); i++)
939        EnableTop(i, enable);
940
941    return true;
942}
943
944void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
945{
946    wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
947
948    m_titles[pos] = label;
949
950    if ( !IsAttached() )
951        return;
952
953    _wxMenuAt(m_menus, pos)->SetTitle( label ) ;
954
955    if (wxMenuBar::s_macInstalledMenuBar == this) // are we currently installed ?
956    {
957        ::SetMenuBar( GetMenuBar() ) ;
958        ::InvalMenuBar() ;
959    }
960}
961
962wxString wxMenuBar::GetLabelTop(size_t pos) const
963{
964    wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
965                 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
966
967    return wxStripMenuCodes(m_titles[pos]);
968}
969
970// Gets the original label at the top-level of the menubar
971wxString wxMenuBar::GetMenuLabel(size_t pos) const
972{
973    wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
974                 wxT("invalid menu index in wxMenuBar::GetMenuLabel") );
975
976    return m_titles[pos];
977}
978
979int wxMenuBar::FindMenu(const wxString& title)
980{
981    wxString menuTitle = wxStripMenuCodes(title);
982
983    size_t count = GetMenuCount();
984    for ( size_t i = 0; i < count; i++ )
985    {
986        wxString title = wxStripMenuCodes(m_titles[i]);
987        if ( menuTitle == title )
988            return i;
989    }
990
991    return wxNOT_FOUND;
992}
993
994// ---------------------------------------------------------------------------
995// wxMenuBar construction
996// ---------------------------------------------------------------------------
997
998wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
999{
1000    wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
1001    if ( !menuOld )
1002        return NULL;
1003
1004    m_titles[pos] = title;
1005
1006    if ( IsAttached() )
1007    {
1008        if (s_macInstalledMenuBar == this)
1009        {
1010            menuOld->MacAfterDisplay( false ) ;
1011            ::DeleteMenu( menuOld->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
1012
1013            menu->MacBeforeDisplay( false ) ;
1014            UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1015            if ( pos == m_menus.GetCount() - 1)
1016                ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1017            else
1018                ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos + 1)->MacGetMenuId() ) ;
1019        }
1020
1021        Refresh();
1022    }
1023
1024    if (m_invokingWindow)
1025        wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1026
1027    return menuOld;
1028}
1029
1030bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1031{
1032    if ( !wxMenuBarBase::Insert(pos, menu, title) )
1033        return false;
1034
1035    m_titles.Insert(title, pos);
1036
1037    UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1038
1039    if ( IsAttached() && s_macInstalledMenuBar == this )
1040    {
1041        if (s_macInstalledMenuBar == this)
1042        {
1043            menu->MacBeforeDisplay( false ) ;
1044
1045            if ( pos == (size_t) -1  || pos + 1 == m_menus.GetCount() )
1046                ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1047            else
1048                ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos+1)->MacGetMenuId() ) ;
1049        }
1050
1051        Refresh();
1052    }
1053
1054    if (m_invokingWindow)
1055        wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1056
1057    return true;
1058}
1059
1060wxMenu *wxMenuBar::Remove(size_t pos)
1061{
1062    wxMenu *menu = wxMenuBarBase::Remove(pos);
1063    if ( !menu )
1064        return NULL;
1065
1066    if ( IsAttached() )
1067    {
1068        if (s_macInstalledMenuBar == this)
1069            ::DeleteMenu( menu->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
1070
1071        Refresh();
1072    }
1073
1074    m_titles.RemoveAt(pos);
1075
1076    return menu;
1077}
1078
1079bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1080{
1081    WXHMENU submenu = menu ? menu->GetHMenu() : 0;
1082        wxCHECK_MSG( submenu, false, wxT("can't append invalid menu to menubar") );
1083
1084    if ( !wxMenuBarBase::Append(menu, title) )
1085        return false;
1086
1087    m_titles.Add(title);
1088
1089    UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1090
1091    if ( IsAttached() )
1092    {
1093        if (s_macInstalledMenuBar == this)
1094        {
1095            menu->MacBeforeDisplay( false ) ;
1096            ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1097        }
1098
1099        Refresh();
1100    }
1101
1102    // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
1103    // adding menu later on.
1104    if (m_invokingWindow)
1105        wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1106
1107    return true;
1108}
1109
1110static void wxMenubarUnsetInvokingWindow( wxMenu *menu )
1111{
1112    menu->SetInvokingWindow( (wxWindow*) NULL );
1113    wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1114
1115    while (node)
1116    {
1117        wxMenuItem *menuitem = node->GetData();
1118        if (menuitem->IsSubMenu())
1119            wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu() );
1120
1121        node = node->GetNext();
1122    }
1123}
1124
1125static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
1126{
1127    menu->SetInvokingWindow( win );
1128    wxMenuItem *menuitem;
1129    wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1130
1131    while (node)
1132    {
1133        menuitem = node->GetData();
1134        if (menuitem->IsSubMenu())
1135            wxMenubarSetInvokingWindow( menuitem->GetSubMenu() , win );
1136
1137        node = node->GetNext();
1138    }
1139}
1140
1141void wxMenuBar::UnsetInvokingWindow()
1142{
1143    m_invokingWindow = (wxWindow*) NULL;
1144    wxMenu *menu;
1145    wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1146
1147    while (node)
1148    {
1149        menu = node->GetData();
1150        wxMenubarUnsetInvokingWindow( menu );
1151
1152        node = node->GetNext();
1153    }
1154}
1155
1156void wxMenuBar::SetInvokingWindow(wxFrame *frame)
1157{
1158    m_invokingWindow = frame;
1159    wxMenu *menu;
1160    wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1161
1162    while (node)
1163    {
1164        menu = node->GetData();
1165        wxMenubarSetInvokingWindow( menu, frame );
1166
1167        node = node->GetNext();
1168    }
1169}
1170
1171void wxMenuBar::Detach()
1172{
1173    wxMenuBarBase::Detach() ;
1174}
1175
1176void wxMenuBar::Attach(wxFrame *frame)
1177{
1178    wxMenuBarBase::Attach( frame ) ;
1179}
1180
1181// ---------------------------------------------------------------------------
1182// wxMenuBar searching for menu items
1183// ---------------------------------------------------------------------------
1184
1185// Find the itemString in menuString, and return the item id or wxNOT_FOUND
1186int wxMenuBar::FindMenuItem(const wxString& menuString,
1187                            const wxString& itemString) const
1188{
1189    wxString menuLabel = wxStripMenuCodes(menuString);
1190    size_t count = GetMenuCount();
1191    for ( size_t i = 0; i < count; i++ )
1192    {
1193        wxString title = wxStripMenuCodes(m_titles[i]);
1194        if ( menuLabel == title )
1195            return _wxMenuAt(m_menus, i)->FindItem(itemString);
1196    }
1197
1198    return wxNOT_FOUND;
1199}
1200
1201wxMenuItem *wxMenuBar::FindItem(int id, wxMenu **itemMenu) const
1202{
1203    if ( itemMenu )
1204        *itemMenu = NULL;
1205
1206    wxMenuItem *item = NULL;
1207    size_t count = GetMenuCount();
1208    for ( size_t i = 0; !item && (i < count); i++ )
1209        item = _wxMenuAt(m_menus, i)->FindItem(id, itemMenu);
1210
1211    return item;
1212}
1213