]> git.saurik.com Git - wxWidgets.git/blob - src/osx/carbon/menu.cpp
9311f4fdb63bfc176cc9055d836128bb5c76a39b
[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/mac/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 }
892
893 if ( GetAutoWindowMenu() )
894 {
895 if ( MacGetWindowMenuHMenu() == NULL )
896 CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
897
898 InsertMenu( (MenuHandle) MacGetWindowMenuHMenu() , 0 ) ;
899 }
900
901 ::DrawMenuBar() ;
902 s_macInstalledMenuBar = this;
903 }
904
905 void wxMenuBar::EnableTop(size_t pos, bool enable)
906 {
907 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
908
909 _wxMenuAt(m_menus, pos)->MacEnableMenu( enable ) ;
910 Refresh();
911 }
912
913 bool wxMenuBar::Enable(bool enable)
914 {
915 wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") );
916
917 size_t i;
918 for (i = 0; i < GetMenuCount(); i++)
919 EnableTop(i, enable);
920
921 return true;
922 }
923
924 void wxMenuBar::SetMenuLabel(size_t pos, const wxString& label)
925 {
926 wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
927
928 m_titles[pos] = label;
929
930 if ( !IsAttached() )
931 return;
932
933 _wxMenuAt(m_menus, pos)->SetTitle( label ) ;
934
935 if (wxMenuBar::s_macInstalledMenuBar == this) // are we currently installed ?
936 {
937 ::SetMenuBar( GetMenuBar() ) ;
938 ::InvalMenuBar() ;
939 }
940 }
941
942 wxString wxMenuBar::GetMenuLabel(size_t pos) const
943 {
944 wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
945 wxT("invalid menu index in wxMenuBar::GetMenuLabel") );
946
947 return m_titles[pos];
948 }
949
950 int wxMenuBar::FindMenu(const wxString& title)
951 {
952 wxString menuTitle = wxStripMenuCodes(title);
953
954 size_t count = GetMenuCount();
955 for ( size_t i = 0; i < count; i++ )
956 {
957 wxString title = wxStripMenuCodes(m_titles[i]);
958 if ( menuTitle == title )
959 return i;
960 }
961
962 return wxNOT_FOUND;
963 }
964
965 // ---------------------------------------------------------------------------
966 // wxMenuBar construction
967 // ---------------------------------------------------------------------------
968
969 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
970 {
971 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
972 if ( !menuOld )
973 return NULL;
974
975 m_titles[pos] = title;
976
977 if ( IsAttached() )
978 {
979 if (s_macInstalledMenuBar == this)
980 {
981 menuOld->MacAfterDisplay( false ) ;
982 ::DeleteMenu( menuOld->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
983
984 menu->MacBeforeDisplay( false ) ;
985 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
986 if ( pos == m_menus.GetCount() - 1)
987 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
988 else
989 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos + 1)->MacGetMenuId() ) ;
990 }
991
992 Refresh();
993 }
994
995 if (m_invokingWindow)
996 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
997
998 return menuOld;
999 }
1000
1001 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1002 {
1003 if ( !wxMenuBarBase::Insert(pos, menu, title) )
1004 return false;
1005
1006 m_titles.Insert(title, pos);
1007
1008 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1009
1010 if ( IsAttached() && s_macInstalledMenuBar == this )
1011 {
1012 if (s_macInstalledMenuBar == this)
1013 {
1014 menu->MacBeforeDisplay( false ) ;
1015
1016 if ( pos == (size_t) -1 || pos + 1 == m_menus.GetCount() )
1017 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1018 else
1019 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos+1)->MacGetMenuId() ) ;
1020 }
1021
1022 Refresh();
1023 }
1024
1025 if (m_invokingWindow)
1026 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1027
1028 return true;
1029 }
1030
1031 wxMenu *wxMenuBar::Remove(size_t pos)
1032 {
1033 wxMenu *menu = wxMenuBarBase::Remove(pos);
1034 if ( !menu )
1035 return NULL;
1036
1037 if ( IsAttached() )
1038 {
1039 if (s_macInstalledMenuBar == this)
1040 ::DeleteMenu( menu->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
1041
1042 Refresh();
1043 }
1044
1045 m_titles.RemoveAt(pos);
1046
1047 return menu;
1048 }
1049
1050 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1051 {
1052 WXHMENU submenu = menu ? menu->GetHMenu() : 0;
1053 wxCHECK_MSG( submenu, false, wxT("can't append invalid menu to menubar") );
1054
1055 if ( !wxMenuBarBase::Append(menu, title) )
1056 return false;
1057
1058 m_titles.Add(title);
1059
1060 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , GetFont().GetEncoding() ) ;
1061
1062 if ( IsAttached() )
1063 {
1064 if (s_macInstalledMenuBar == this)
1065 {
1066 menu->MacBeforeDisplay( false ) ;
1067 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1068 }
1069
1070 Refresh();
1071 }
1072
1073 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
1074 // adding menu later on.
1075 if (m_invokingWindow)
1076 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1077
1078 return true;
1079 }
1080
1081 static void wxMenubarUnsetInvokingWindow( wxMenu *menu )
1082 {
1083 menu->SetInvokingWindow( (wxWindow*) NULL );
1084 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1085
1086 while (node)
1087 {
1088 wxMenuItem *menuitem = node->GetData();
1089 if (menuitem->IsSubMenu())
1090 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu() );
1091
1092 node = node->GetNext();
1093 }
1094 }
1095
1096 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
1097 {
1098 menu->SetInvokingWindow( win );
1099 wxMenuItem *menuitem;
1100 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1101
1102 while (node)
1103 {
1104 menuitem = node->GetData();
1105 if (menuitem->IsSubMenu())
1106 wxMenubarSetInvokingWindow( menuitem->GetSubMenu() , win );
1107
1108 node = node->GetNext();
1109 }
1110 }
1111
1112 void wxMenuBar::UnsetInvokingWindow()
1113 {
1114 m_invokingWindow = (wxWindow*) NULL;
1115 wxMenu *menu;
1116 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1117
1118 while (node)
1119 {
1120 menu = node->GetData();
1121 wxMenubarUnsetInvokingWindow( menu );
1122
1123 node = node->GetNext();
1124 }
1125 }
1126
1127 void wxMenuBar::SetInvokingWindow(wxFrame *frame)
1128 {
1129 m_invokingWindow = frame;
1130 wxMenu *menu;
1131 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1132
1133 while (node)
1134 {
1135 menu = node->GetData();
1136 wxMenubarSetInvokingWindow( menu, frame );
1137
1138 node = node->GetNext();
1139 }
1140 }
1141
1142 void wxMenuBar::Detach()
1143 {
1144 wxMenuBarBase::Detach() ;
1145 }
1146
1147 void wxMenuBar::Attach(wxFrame *frame)
1148 {
1149 wxMenuBarBase::Attach( frame ) ;
1150 }
1151
1152 // ---------------------------------------------------------------------------
1153 // wxMenuBar searching for menu items
1154 // ---------------------------------------------------------------------------
1155
1156 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
1157 int wxMenuBar::FindMenuItem(const wxString& menuString,
1158 const wxString& itemString) const
1159 {
1160 wxString menuLabel = wxStripMenuCodes(menuString);
1161 size_t count = GetMenuCount();
1162 for ( size_t i = 0; i < count; i++ )
1163 {
1164 wxString title = wxStripMenuCodes(m_titles[i]);
1165 if ( menuLabel == title )
1166 return _wxMenuAt(m_menus, i)->FindItem(itemString);
1167 }
1168
1169 return wxNOT_FOUND;
1170 }
1171
1172 wxMenuItem *wxMenuBar::FindItem(int id, wxMenu **itemMenu) const
1173 {
1174 if ( itemMenu )
1175 *itemMenu = NULL;
1176
1177 wxMenuItem *item = NULL;
1178 size_t count = GetMenuCount();
1179 for ( size_t i = 0; !item && (i < count); i++ )
1180 item = _wxMenuAt(m_menus, i)->FindItem(id, itemMenu);
1181
1182 return item;
1183 }