]> git.saurik.com Git - wxWidgets.git/blob - src/osx/carbon/menu.cpp
fixing #9910
[wxWidgets.git] / src / osx / carbon / menu.cpp
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$
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/osx/uma.h"
32
33 // other standard headers
34 // ----------------------
35 #include <string.h>
36
37 IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
38 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxEvtHandler)
39
40 // the (popup) menu title has this special id
41 static const int idMenuTitle = -3;
42
43 static const short kwxMacAppleMenuId = 1 ;
44
45
46 // Find an item given the Macintosh Menu Reference
47
48 WX_DECLARE_HASH_MAP(MenuRef, wxMenu*, wxPointerHash, wxPointerEqual, MacMenuMap);
49
50 static MacMenuMap wxWinMacMenuList;
51
52 wxMenu *wxFindMenuFromMacMenu(MenuRef inMenuRef)
53 {
54 MacMenuMap::iterator node = wxWinMacMenuList.find(inMenuRef);
55
56 return (node == wxWinMacMenuList.end()) ? NULL : node->second;
57 }
58
59 void wxAssociateMenuWithMacMenu(MenuRef inMenuRef, wxMenu *menu) ;
60 void 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
69 void wxRemoveMacMenuAssociation(wxMenu *menu) ;
70 void 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
84 void 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->GetItemLabel() ) ;
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->GetItemLabel()) , 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 // ============================================================================
135 static void wxMenubarUnsetInvokingWindow( wxMenu *menu ) ;
136 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win );
137
138 // Menus
139
140 // Construct a menu with optional title (then use append)
141
142 #ifdef __DARWIN__
143 short wxMenu::s_macNextMenuId = 3 ;
144 #else
145 short wxMenu::s_macNextMenuId = 2 ;
146 #endif
147
148 static
149 wxMenu *
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
160 void 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
184 wxMenu::~wxMenu()
185 {
186 wxRemoveMacMenuAssociation( this ) ;
187 if (MAC_WXHMENU(m_hMenu))
188 ::DisposeMenu(MAC_WXHMENU(m_hMenu));
189 }
190
191 void wxMenu::Break()
192 {
193 // not available on the mac platform
194 }
195
196 void 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
205 bool 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->GetItemLabel()), wxFont::GetDefaultEncoding(), pSubMenu->m_macMenuId);
234 else
235 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu), wxStripMenuCodes(pItem->GetItemLabel()), 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
274 void wxMenu::EndRadioGroup()
275 {
276 // we're not inside a radio group any longer
277 m_startRadioGroup = -1;
278 }
279
280 wxMenuItem* 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
333 wxMenuItem* 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
341 wxMenuItem *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
368 void wxMenu::SetTitle(const wxString& label)
369 {
370 m_title = label ;
371 UMASetMenuTitle(MAC_WXHMENU(m_hMenu) , label , wxFont::GetDefaultEncoding() ) ;
372 }
373
374 bool wxMenu::ProcessCommand(wxCommandEvent & event)
375 {
376 bool processed = false;
377
378 // Try the menu's event handler
379 if ( /* !processed && */ GetEventHandler())
380 processed = GetEventHandler()->SafelyProcessEvent(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->HandleWindowEvent(event);
387
388 return processed;
389 }
390
391 // ---------------------------------------------------------------------------
392 // other
393 // ---------------------------------------------------------------------------
394
395 wxWindow *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
408 int 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
426 int 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
444 void 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
454 void 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 // what we do here is to hide the special items which are
472 // shown in the application menu anyhow -- it doesn't make
473 // sense to show them in their normal place as well
474 if ( item->GetId() == wxApp::s_macAboutMenuItemId ||
475 item->GetId() == wxApp::s_macPreferencesMenuItemId ||
476 item->GetId() == wxApp::s_macExitMenuItemId )
477
478 {
479 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
480 pos + 1, kMenuItemAttrHidden, 0 );
481
482 // also check for a separator which was used just to
483 // separate this item from the others, so don't leave
484 // separator at the menu start or end nor 2 consecutive
485 // separators
486 wxMenuItemList::compatibility_iterator nextNode = node->GetNext();
487 wxMenuItem *next = nextNode ? nextNode->GetData() : NULL;
488
489 size_t posSeptoHide;
490 if ( !previousItem && next && next->IsSeparator() )
491 {
492 // next (i.e. second as we must be first) item is
493 // the separator to hide
494 wxASSERT_MSG( pos == 0, _T("should be the menu start") );
495 posSeptoHide = 2;
496 }
497 else if ( GetMenuItems().GetCount() == pos + 1 &&
498 previousItem != NULL &&
499 previousItem->IsSeparator() )
500 {
501 // prev item is a trailing separator we want to hide
502 posSeptoHide = pos;
503 }
504 else if ( previousItem && previousItem->IsSeparator() &&
505 next && next->IsSeparator() )
506 {
507 // two consecutive separators, this is one too many
508 posSeptoHide = pos;
509 }
510 else // no separators to hide
511 {
512 posSeptoHide = 0;
513 }
514
515 if ( posSeptoHide )
516 {
517 // hide the separator as well
518 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
519 posSeptoHide,
520 kMenuItemAttrHidden,
521 0 );
522 }
523 }
524 }
525
526 previousItem = item ;
527 }
528
529 if ( isSubMenu )
530 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
531 }
532
533 // undo all changes from the MacBeforeDisplay call
534 void wxMenu::MacAfterDisplay( bool isSubMenu )
535 {
536 if ( isSubMenu )
537 ::DeleteMenu(MacGetMenuId());
538
539 wxMenuItemList::compatibility_iterator node;
540 wxMenuItem *item;
541
542 for (node = GetMenuItems().GetFirst(); node; node = node->GetNext())
543 {
544 item = (wxMenuItem *)node->GetData();
545 wxMenu* subMenu = item->GetSubMenu() ;
546 if (subMenu)
547 {
548 subMenu->MacAfterDisplay( true ) ;
549 }
550 else
551 {
552 // no need to undo hidings
553 }
554 }
555 }
556
557 wxInt32 wxMenu::MacHandleCommandProcess( wxMenuItem* item, int id, wxWindow* targetWindow )
558 {
559 OSStatus result = eventNotHandledErr ;
560 if (item->IsCheckable())
561 item->Check( !item->IsChecked() ) ;
562
563 if ( SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) )
564 result = noErr ;
565 else
566 {
567 if ( targetWindow != NULL )
568 {
569 wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED , id);
570 event.SetEventObject(targetWindow);
571 event.SetInt(item->IsCheckable() ? item->IsChecked() : -1);
572
573 if ( targetWindow->HandleWindowEvent(event) )
574 result = noErr ;
575 }
576 }
577 return result;
578 }
579
580 wxInt32 wxMenu::MacHandleCommandUpdateStatus(wxMenuItem* WXUNUSED(item),
581 int id,
582 wxWindow* targetWindow)
583 {
584 OSStatus result = eventNotHandledErr ;
585 wxUpdateUIEvent event(id);
586 event.SetEventObject( this );
587
588 bool processed = false;
589
590 // Try the menu's event handler
591 {
592 wxEvtHandler *handler = GetEventHandler();
593 if ( handler )
594 processed = handler->ProcessEvent(event);
595 }
596
597 // Try the window the menu was popped up from
598 // (and up through the hierarchy)
599 if ( !processed )
600 {
601 const wxMenuBase *menu = this;
602 while ( menu )
603 {
604 wxWindow *win = menu->GetInvokingWindow();
605 if ( win )
606 {
607 processed = win->HandleWindowEvent(event);
608 break;
609 }
610
611 menu = menu->GetParent();
612 }
613 }
614
615 if ( !processed && targetWindow != NULL)
616 {
617 processed = targetWindow->HandleWindowEvent(event);
618 }
619
620 if ( processed )
621 {
622 // if anything changed, update the changed attribute
623 if (event.GetSetText())
624 SetLabel(id, event.GetText());
625 if (event.GetSetChecked())
626 Check(id, event.GetChecked());
627 if (event.GetSetEnabled())
628 Enable(id, event.GetEnabled());
629
630 result = noErr ;
631 }
632 return result;
633 }
634
635 // Menu Bar
636
637 /*
638
639 Mac Implementation note :
640
641 The Mac has only one global menubar, so we attempt to install the currently
642 active menubar from a frame, we currently don't take into account mdi-frames
643 which would ask for menu-merging
644
645 Secondly there is no mac api for changing a menubar that is not the current
646 menubar, so we have to wait for preparing the actual menubar until the
647 wxMenubar is to be used
648
649 We can in subsequent versions use MacInstallMenuBar to provide some sort of
650 auto-merge for MDI in case this will be necessary
651
652 */
653
654 wxMenuBar* wxMenuBar::s_macInstalledMenuBar = NULL ;
655 wxMenuBar* wxMenuBar::s_macCommonMenuBar = NULL ;
656 bool wxMenuBar::s_macAutoWindowMenu = true ;
657 WXHMENU wxMenuBar::s_macWindowMenuHandle = NULL ;
658
659 void wxMenuBar::Init()
660 {
661 m_eventHandler = this;
662 m_menuBarFrame = NULL;
663 m_invokingWindow = (wxWindow*) NULL;
664 }
665
666 wxMenuBar::wxMenuBar()
667 {
668 Init();
669 }
670
671 wxMenuBar::wxMenuBar( long WXUNUSED(style) )
672 {
673 Init();
674 }
675
676 wxMenuBar::wxMenuBar(size_t count, wxMenu *menus[], const wxString titles[], long WXUNUSED(style))
677 {
678 Init();
679
680 m_titles.Alloc(count);
681
682 for ( size_t i = 0; i < count; i++ )
683 {
684 m_menus.Append(menus[i]);
685 m_titles.Add(titles[i]);
686
687 menus[i]->Attach(this);
688 }
689 }
690
691 wxMenuBar::~wxMenuBar()
692 {
693 if (s_macCommonMenuBar == this)
694 s_macCommonMenuBar = NULL;
695
696 if (s_macInstalledMenuBar == this)
697 {
698 ::ClearMenuBar();
699 s_macInstalledMenuBar = NULL;
700 }
701 }
702
703 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground), const wxRect *WXUNUSED(rect))
704 {
705 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
706
707 DrawMenuBar();
708 }
709
710 void wxMenuBar::MacInstallMenuBar()
711 {
712 if ( s_macInstalledMenuBar == this )
713 return ;
714
715 MenuBarHandle menubar = NULL ;
716
717 menubar = NewHandleClear( 6 /* sizeof( MenuBarHeader ) */ ) ;
718
719 ::SetMenuBar( menubar ) ;
720 DisposeMenuBar( menubar ) ;
721 MenuHandle appleMenu = NULL ;
722
723 verify_noerr( CreateNewMenu( kwxMacAppleMenuId , 0 , &appleMenu ) ) ;
724 verify_noerr( SetMenuTitleWithCFString( appleMenu , CFSTR( "\x14" ) ) );
725
726 // Add About/Preferences separator only on OS X
727 // KH/RN: Separator is always present on 10.3 but not on 10.2
728 // However, the change from 10.2 to 10.3 suggests it is preferred
729 InsertMenuItemTextWithCFString( appleMenu,
730 CFSTR(""), 0, kMenuItemAttrSeparator, 0);
731 InsertMenuItemTextWithCFString( appleMenu,
732 CFSTR("About..."), 0, 0, 0);
733 MacInsertMenu( appleMenu , 0 ) ;
734
735 // if we have a mac help menu, clean it up before adding new items
736 MenuHandle helpMenuHandle ;
737 MenuItemIndex firstUserHelpMenuItem ;
738
739 if ( UMAGetHelpMenuDontCreate( &helpMenuHandle , &firstUserHelpMenuItem) == noErr )
740 {
741 for ( int i = CountMenuItems( helpMenuHandle ) ; i >= firstUserHelpMenuItem ; --i )
742 DeleteMenuItem( helpMenuHandle , i ) ;
743 }
744 else
745 {
746 helpMenuHandle = NULL ;
747 }
748
749 if ( wxApp::s_macPreferencesMenuItemId)
750 {
751 wxMenuItem *item = FindItem( wxApp::s_macPreferencesMenuItemId , NULL ) ;
752 if ( item == NULL || !(item->IsEnabled()) )
753 DisableMenuCommand( NULL , kHICommandPreferences ) ;
754 else
755 EnableMenuCommand( NULL , kHICommandPreferences ) ;
756 }
757
758 // Unlike preferences which may or may not exist, the Quit item should be always
759 // enabled unless it is added by the application and then disabled, otherwise
760 // a program would be required to add an item with wxID_EXIT in order to get the
761 // Quit menu item to be enabled, which seems a bit burdensome.
762 if ( wxApp::s_macExitMenuItemId)
763 {
764 wxMenuItem *item = FindItem( wxApp::s_macExitMenuItemId , NULL ) ;
765 if ( item != NULL && !(item->IsEnabled()) )
766 DisableMenuCommand( NULL , kHICommandQuit ) ;
767 else
768 EnableMenuCommand( NULL , kHICommandQuit ) ;
769 }
770
771 wxString strippedHelpMenuTitle = wxStripMenuCodes( wxApp::s_macHelpMenuTitleName ) ;
772 wxString strippedTranslatedHelpMenuTitle = wxStripMenuCodes( wxString( _("&Help") ) ) ;
773 wxMenuList::compatibility_iterator menuIter = m_menus.GetFirst();
774 for (size_t i = 0; i < m_menus.GetCount(); i++, menuIter = menuIter->GetNext())
775 {
776 wxMenuItemList::compatibility_iterator node;
777 wxMenuItem *item;
778 wxMenu* menu = menuIter->GetData() , *subMenu = NULL ;
779 wxString strippedMenuTitle = wxStripMenuCodes(m_titles[i]);
780
781 if ( strippedMenuTitle == wxT("?") || strippedMenuTitle == strippedHelpMenuTitle || strippedMenuTitle == strippedTranslatedHelpMenuTitle )
782 {
783 for (node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext())
784 {
785 item = (wxMenuItem *)node->GetData();
786 subMenu = item->GetSubMenu() ;
787 if (subMenu)
788 {
789 // we don't support hierarchical menus in the help menu yet
790 }
791 else
792 {
793 if ( item->GetId() != wxApp::s_macAboutMenuItemId )
794 {
795 // we have found a user help menu and an item other than the about item,
796 // so we can create the mac help menu now, if we haven't created it yet
797 if ( helpMenuHandle == NULL )
798 {
799 if ( UMAGetHelpMenu( &helpMenuHandle , &firstUserHelpMenuItem) != noErr )
800 {
801 helpMenuHandle = NULL ;
802 break ;
803 }
804 }
805 }
806
807 if ( item->IsSeparator() )
808 {
809 if ( helpMenuHandle )
810 AppendMenuItemTextWithCFString( helpMenuHandle,
811 CFSTR(""), kMenuItemAttrSeparator, 0,NULL);
812 }
813 else
814 {
815 wxAcceleratorEntry*
816 entry = wxAcceleratorEntry::Create( item->GetItemLabel() ) ;
817
818 if ( item->GetId() == wxApp::s_macAboutMenuItemId )
819 {
820 // this will be taken care of below
821 }
822 else
823 {
824 if ( helpMenuHandle )
825 {
826 UMAAppendMenuItem(helpMenuHandle, wxStripMenuCodes(item->GetItemLabel()) , wxFont::GetDefaultEncoding(), entry);
827 SetMenuItemCommandID( helpMenuHandle , CountMenuItems(helpMenuHandle) , wxIdToMacCommand ( item->GetId() ) ) ;
828 SetMenuItemRefCon( helpMenuHandle , CountMenuItems(helpMenuHandle) , (URefCon) item ) ;
829 }
830 }
831
832 delete entry ;
833 }
834 }
835 }
836 }
837
838 else if ( ( m_titles[i] == wxT("Window") || m_titles[i] == wxT("&Window") )
839 && GetAutoWindowMenu() )
840 {
841 if ( MacGetWindowMenuHMenu() == NULL )
842 {
843 CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
844 }
845
846 MenuRef wm = (MenuRef)MacGetWindowMenuHMenu();
847 if ( wm == NULL )
848 break;
849
850 // get the insertion point in the standard menu
851 MenuItemIndex winListStart;
852 GetIndMenuItemWithCommandID(wm,
853 kHICommandWindowListSeparator, 1, NULL, &winListStart);
854
855 // add a separator so that the standard items and the custom items
856 // aren't mixed together, but only if this is the first run
857 OSStatus err = GetIndMenuItemWithCommandID(wm,
858 'WXWM', 1, NULL, NULL);
859
860 if ( err == menuItemNotFoundErr )
861 {
862 InsertMenuItemTextWithCFString( wm,
863 CFSTR(""), winListStart-1, kMenuItemAttrSeparator, 'WXWM');
864 }
865
866 wxInsertMenuItemsInMenu(menu, wm, winListStart);
867 }
868 else
869 {
870 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , m_titles[i], GetFont().GetEncoding() ) ;
871 menu->MacBeforeDisplay(false) ;
872
873 ::InsertMenu(MAC_WXHMENU(_wxMenuAt(m_menus, i)->GetHMenu()), 0);
874 }
875 }
876
877 // take care of the about menu item wherever it is
878 {
879 wxMenu* aboutMenu ;
880 wxMenuItem *aboutMenuItem = FindItem(wxApp::s_macAboutMenuItemId , &aboutMenu) ;
881 if ( aboutMenuItem )
882 {
883 wxAcceleratorEntry*
884 entry = wxAcceleratorEntry::Create( aboutMenuItem->GetItemLabel() ) ;
885 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId ) , 1 , wxStripMenuCodes ( aboutMenuItem->GetItemLabel() ) , wxFont::GetDefaultEncoding() );
886 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId ) , 1 , true );
887 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId ) , 1 , kHICommandAbout ) ;
888 SetMenuItemRefCon(GetMenuHandle( kwxMacAppleMenuId ) , 1 , (URefCon)aboutMenuItem ) ;
889 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId ) , 1 , entry ) ;
890
891 delete entry;
892 }
893 }
894
895 if ( GetAutoWindowMenu() )
896 {
897 if ( MacGetWindowMenuHMenu() == NULL )
898 CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
899
900 InsertMenu( (MenuHandle) MacGetWindowMenuHMenu() , 0 ) ;
901 }
902
903 ::DrawMenuBar() ;
904 s_macInstalledMenuBar = this;
905 }
906
907 void wxMenuBar::EnableTop(size_t pos, bool enable)
908 {
909 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
910
911 _wxMenuAt(m_menus, pos)->MacEnableMenu( enable ) ;
912 Refresh();
913 }
914
915 bool wxMenuBar::Enable(bool enable)
916 {
917 wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") );
918
919 size_t i;
920 for (i = 0; i < GetMenuCount(); i++)
921 EnableTop(i, enable);
922
923 return true;
924 }
925
926 void wxMenuBar::SetMenuLabel(size_t pos, const wxString& label)
927 {
928 wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
929
930 m_titles[pos] = label;
931
932 if ( !IsAttached() )
933 return;
934
935 _wxMenuAt(m_menus, pos)->SetTitle( label ) ;
936
937 if (wxMenuBar::s_macInstalledMenuBar == this) // are we currently installed ?
938 {
939 ::SetMenuBar( GetMenuBar() ) ;
940 ::InvalMenuBar() ;
941 }
942 }
943
944 wxString wxMenuBar::GetMenuLabel(size_t pos) const
945 {
946 wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
947 wxT("invalid menu index in wxMenuBar::GetMenuLabel") );
948
949 return m_titles[pos];
950 }
951
952 int wxMenuBar::FindMenu(const wxString& title)
953 {
954 wxString menuTitle = wxStripMenuCodes(title);
955
956 size_t count = GetMenuCount();
957 for ( size_t i = 0; i < count; i++ )
958 {
959 wxString title = wxStripMenuCodes(m_titles[i]);
960 if ( menuTitle == title )
961 return i;
962 }
963
964 return wxNOT_FOUND;
965 }
966
967 // ---------------------------------------------------------------------------
968 // wxMenuBar construction
969 // ---------------------------------------------------------------------------
970
971 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
972 {
973 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
974 if ( !menuOld )
975 return NULL;
976
977 m_titles[pos] = title;
978
979 if ( IsAttached() )
980 {
981 if (s_macInstalledMenuBar == this)
982 {
983 menuOld->MacAfterDisplay( false ) ;
984 ::DeleteMenu( menuOld->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
985
986 menu->MacBeforeDisplay( false ) ;
987 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
988 if ( pos == m_menus.GetCount() - 1)
989 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
990 else
991 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos + 1)->MacGetMenuId() ) ;
992 }
993
994 Refresh();
995 }
996
997 if (m_invokingWindow)
998 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
999
1000 return menuOld;
1001 }
1002
1003 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1004 {
1005 if ( !wxMenuBarBase::Insert(pos, menu, title) )
1006 return false;
1007
1008 m_titles.Insert(title, pos);
1009
1010 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1011
1012 if ( IsAttached() && s_macInstalledMenuBar == this )
1013 {
1014 if (s_macInstalledMenuBar == this)
1015 {
1016 menu->MacBeforeDisplay( false ) ;
1017
1018 if ( pos == (size_t) -1 || pos + 1 == m_menus.GetCount() )
1019 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1020 else
1021 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos+1)->MacGetMenuId() ) ;
1022 }
1023
1024 Refresh();
1025 }
1026
1027 if (m_invokingWindow)
1028 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1029
1030 return true;
1031 }
1032
1033 wxMenu *wxMenuBar::Remove(size_t pos)
1034 {
1035 wxMenu *menu = wxMenuBarBase::Remove(pos);
1036 if ( !menu )
1037 return NULL;
1038
1039 if ( IsAttached() )
1040 {
1041 if (s_macInstalledMenuBar == this)
1042 ::DeleteMenu( menu->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
1043
1044 Refresh();
1045 }
1046
1047 m_titles.RemoveAt(pos);
1048
1049 return menu;
1050 }
1051
1052 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1053 {
1054 WXHMENU submenu = menu ? menu->GetHMenu() : 0;
1055 wxCHECK_MSG( submenu, false, wxT("can't append invalid menu to menubar") );
1056
1057 if ( !wxMenuBarBase::Append(menu, title) )
1058 return false;
1059
1060 m_titles.Add(title);
1061
1062 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1063
1064 if ( IsAttached() )
1065 {
1066 if (s_macInstalledMenuBar == this)
1067 {
1068 menu->MacBeforeDisplay( false ) ;
1069 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1070 }
1071
1072 Refresh();
1073 }
1074
1075 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
1076 // adding menu later on.
1077 if (m_invokingWindow)
1078 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1079
1080 return true;
1081 }
1082
1083 static void wxMenubarUnsetInvokingWindow( wxMenu *menu )
1084 {
1085 menu->SetInvokingWindow( (wxWindow*) NULL );
1086 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1087
1088 while (node)
1089 {
1090 wxMenuItem *menuitem = node->GetData();
1091 if (menuitem->IsSubMenu())
1092 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu() );
1093
1094 node = node->GetNext();
1095 }
1096 }
1097
1098 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
1099 {
1100 menu->SetInvokingWindow( win );
1101 wxMenuItem *menuitem;
1102 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1103
1104 while (node)
1105 {
1106 menuitem = node->GetData();
1107 if (menuitem->IsSubMenu())
1108 wxMenubarSetInvokingWindow( menuitem->GetSubMenu() , win );
1109
1110 node = node->GetNext();
1111 }
1112 }
1113
1114 void wxMenuBar::UnsetInvokingWindow()
1115 {
1116 m_invokingWindow = (wxWindow*) NULL;
1117 wxMenu *menu;
1118 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1119
1120 while (node)
1121 {
1122 menu = node->GetData();
1123 wxMenubarUnsetInvokingWindow( menu );
1124
1125 node = node->GetNext();
1126 }
1127 }
1128
1129 void wxMenuBar::SetInvokingWindow(wxFrame *frame)
1130 {
1131 m_invokingWindow = frame;
1132 wxMenu *menu;
1133 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1134
1135 while (node)
1136 {
1137 menu = node->GetData();
1138 wxMenubarSetInvokingWindow( menu, frame );
1139
1140 node = node->GetNext();
1141 }
1142 }
1143
1144 void wxMenuBar::Detach()
1145 {
1146 wxMenuBarBase::Detach() ;
1147 }
1148
1149 void wxMenuBar::Attach(wxFrame *frame)
1150 {
1151 wxMenuBarBase::Attach( frame ) ;
1152 }
1153
1154 // ---------------------------------------------------------------------------
1155 // wxMenuBar searching for menu items
1156 // ---------------------------------------------------------------------------
1157
1158 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
1159 int wxMenuBar::FindMenuItem(const wxString& menuString,
1160 const wxString& itemString) const
1161 {
1162 wxString menuLabel = wxStripMenuCodes(menuString);
1163 size_t count = GetMenuCount();
1164 for ( size_t i = 0; i < count; i++ )
1165 {
1166 wxString title = wxStripMenuCodes(m_titles[i]);
1167 if ( menuLabel == title )
1168 return _wxMenuAt(m_menus, i)->FindItem(itemString);
1169 }
1170
1171 return wxNOT_FOUND;
1172 }
1173
1174 wxMenuItem *wxMenuBar::FindItem(int id, wxMenu **itemMenu) const
1175 {
1176 if ( itemMenu )
1177 *itemMenu = NULL;
1178
1179 wxMenuItem *item = NULL;
1180 size_t count = GetMenuCount();
1181 for ( size_t i = 0; !item && (i < count); i++ )
1182 item = _wxMenuAt(m_menus, i)->FindItem(id, itemMenu);
1183
1184 return item;
1185 }