]> git.saurik.com Git - wxWidgets.git/blob - src/mac/carbon/menu.cpp
avoiding double firing of visibility state changed for certain controls like multilin...
[wxWidgets.git] / src / mac / 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 if (wxMenuBar::MacGetInstalledMenuBar() == GetMenuBar())
227 pSubMenu->MacBeforeDisplay( true ) ;
228
229 if ( pos == (size_t)-1 )
230 UMAAppendSubMenuItem(MAC_WXHMENU(m_hMenu), wxStripMenuCodes(pItem->GetItemLabel()), wxFont::GetDefaultEncoding(), pSubMenu->m_macMenuId);
231 else
232 UMAInsertSubMenuItem(MAC_WXHMENU(m_hMenu), wxStripMenuCodes(pItem->GetItemLabel()), wxFont::GetDefaultEncoding(), pos, pSubMenu->m_macMenuId);
233
234 pItem->UpdateItemBitmap() ;
235 pItem->UpdateItemStatus() ;
236 }
237 else
238 {
239 if ( pos == (size_t)-1 )
240 {
241 UMAAppendMenuItem(MAC_WXHMENU(m_hMenu), wxT("a") , wxFont::GetDefaultEncoding() );
242 pos = CountMenuItems(MAC_WXHMENU(m_hMenu)) ;
243 }
244 else
245 {
246 // MacOS counts menu items from 1 and inserts after, therefore having the
247 // same effect as wx 0 based and inserting before, we must correct pos
248 // after however for updates to be correct
249 UMAInsertMenuItem(MAC_WXHMENU(m_hMenu), wxT("a"), wxFont::GetDefaultEncoding(), pos);
250 pos += 1 ;
251 }
252
253 SetMenuItemCommandID( MAC_WXHMENU(m_hMenu) , pos , wxIdToMacCommand ( pItem->GetId() ) ) ;
254 SetMenuItemRefCon( MAC_WXHMENU(m_hMenu) , pos , (URefCon) pItem ) ;
255 pItem->UpdateItemText() ;
256 pItem->UpdateItemBitmap() ;
257 pItem->UpdateItemStatus() ;
258
259 if ( pItem->GetId() == idMenuTitle )
260 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu) , pos , false ) ;
261 }
262 }
263
264 // if we're already attached to the menubar, we must update it
265 if ( IsAttached() && GetMenuBar()->IsAttached() )
266 GetMenuBar()->Refresh();
267
268 return true ;
269 }
270
271 void wxMenu::EndRadioGroup()
272 {
273 // we're not inside a radio group any longer
274 m_startRadioGroup = -1;
275 }
276
277 wxMenuItem* wxMenu::DoAppend(wxMenuItem *item)
278 {
279 wxCHECK_MSG( item, NULL, _T("NULL item in wxMenu::DoAppend") );
280
281 bool check = false;
282
283 if ( item->GetKind() == wxITEM_RADIO )
284 {
285 int count = GetMenuItemCount();
286
287 if ( m_startRadioGroup == -1 )
288 {
289 // start a new radio group
290 m_startRadioGroup = count;
291
292 // for now it has just one element
293 item->SetAsRadioGroupStart();
294 item->SetRadioGroupEnd(m_startRadioGroup);
295
296 // ensure that we have a checked item in the radio group
297 check = true;
298 }
299 else // extend the current radio group
300 {
301 // we need to update its end item
302 item->SetRadioGroupStart(m_startRadioGroup);
303 wxMenuItemList::compatibility_iterator node = GetMenuItems().Item(m_startRadioGroup);
304
305 if ( node )
306 {
307 node->GetData()->SetRadioGroupEnd(count);
308 }
309 else
310 {
311 wxFAIL_MSG( _T("where is the radio group start item?") );
312 }
313 }
314 }
315 else // not a radio item
316 {
317 EndRadioGroup();
318 }
319
320 if ( !wxMenuBase::DoAppend(item) || !DoInsertOrAppend(item) )
321 return NULL;
322
323 if ( check )
324 // check the item initially
325 item->Check(true);
326
327 return item;
328 }
329
330 wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
331 {
332 if (wxMenuBase::DoInsert(pos, item) && DoInsertOrAppend(item, pos))
333 return item;
334
335 return NULL;
336 }
337
338 wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
339 {
340 // we need to find the items position in the child list
341 size_t pos;
342 wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
343
344 for ( pos = 0; node; pos++ )
345 {
346 if ( node->GetData() == item )
347 break;
348
349 node = node->GetNext();
350 }
351
352 // DoRemove() (unlike Remove) can only be called for existing item!
353 wxCHECK_MSG( node, NULL, wxT("bug in wxMenu::Remove logic") );
354
355 ::DeleteMenuItem(MAC_WXHMENU(m_hMenu) , pos + 1);
356
357 if ( IsAttached() && GetMenuBar()->IsAttached() )
358 // otherwise, the change won't be visible
359 GetMenuBar()->Refresh();
360
361 // and from internal data structures
362 return wxMenuBase::DoRemove(item);
363 }
364
365 void wxMenu::SetTitle(const wxString& label)
366 {
367 m_title = label ;
368 UMASetMenuTitle(MAC_WXHMENU(m_hMenu) , label , wxFont::GetDefaultEncoding() ) ;
369 }
370
371 bool wxMenu::ProcessCommand(wxCommandEvent & event)
372 {
373 bool processed = false;
374
375 // Try the menu's event handler
376 if ( /* !processed && */ GetEventHandler())
377 processed = GetEventHandler()->SafelyProcessEvent(event);
378
379 // Try the window the menu was popped up from
380 // (and up through the hierarchy)
381 wxWindow *win = GetInvokingWindow();
382 if ( !processed && win )
383 processed = win->HandleWindowEvent(event);
384
385 return processed;
386 }
387
388 // ---------------------------------------------------------------------------
389 // other
390 // ---------------------------------------------------------------------------
391
392 wxWindow *wxMenu::GetWindow() const
393 {
394 if ( m_invokingWindow != NULL )
395 return m_invokingWindow;
396 else if ( GetMenuBar() != NULL)
397 return (wxWindow *) GetMenuBar()->GetFrame();
398
399 return NULL;
400 }
401
402 // helper functions returning the mac menu position for a certain item, note that this is
403 // mac-wise 1 - based, i.e. the first item has index 1 whereas on MSWin it has pos 0
404
405 int wxMenu::MacGetIndexFromId( int id )
406 {
407 size_t pos;
408 wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
409 for ( pos = 0; node; pos++ )
410 {
411 if ( node->GetData()->GetId() == id )
412 break;
413
414 node = node->GetNext();
415 }
416
417 if (!node)
418 return 0;
419
420 return pos + 1 ;
421 }
422
423 int wxMenu::MacGetIndexFromItem( wxMenuItem *pItem )
424 {
425 size_t pos;
426 wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
427 for ( pos = 0; node; pos++ )
428 {
429 if ( node->GetData() == pItem )
430 break;
431
432 node = node->GetNext();
433 }
434
435 if (!node)
436 return 0;
437
438 return pos + 1 ;
439 }
440
441 void wxMenu::MacEnableMenu( bool bDoEnable )
442 {
443 UMAEnableMenuItem(MAC_WXHMENU(m_hMenu) , 0 , bDoEnable ) ;
444
445 ::DrawMenuBar() ;
446 }
447
448 // MacOS needs to know about submenus somewhere within this menu
449 // before it can be displayed, also hide special menu items
450 // like preferences that are handled by the OS
451 void wxMenu::MacBeforeDisplay( bool isSubMenu )
452 {
453 wxMenuItem* previousItem = NULL ;
454 size_t pos ;
455 wxMenuItemList::compatibility_iterator node;
456 wxMenuItem *item;
457
458 for (pos = 0, node = GetMenuItems().GetFirst(); node; node = node->GetNext(), pos++)
459 {
460 item = (wxMenuItem *)node->GetData();
461 wxMenu* subMenu = item->GetSubMenu() ;
462 if (subMenu)
463 {
464 subMenu->MacBeforeDisplay( true ) ;
465 }
466 else // normal item
467 {
468 // what we do here is to hide the special items which are
469 // shown in the application menu anyhow -- it doesn't make
470 // sense to show them in their normal place as well
471 if ( item->GetId() == wxApp::s_macAboutMenuItemId ||
472 item->GetId() == wxApp::s_macPreferencesMenuItemId ||
473 item->GetId() == wxApp::s_macExitMenuItemId )
474
475 {
476 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
477 pos + 1, kMenuItemAttrHidden, 0 );
478
479 // also check for a separator which was used just to
480 // separate this item from the others, so don't leave
481 // separator at the menu start or end nor 2 consecutive
482 // separators
483 wxMenuItemList::compatibility_iterator nextNode = node->GetNext();
484 wxMenuItem *next = nextNode ? nextNode->GetData() : NULL;
485
486 size_t posSeptoHide;
487 if ( !previousItem && next && next->IsSeparator() )
488 {
489 // next (i.e. second as we must be first) item is
490 // the separator to hide
491 wxASSERT_MSG( pos == 0, _T("should be the menu start") );
492 posSeptoHide = 2;
493 }
494 else if ( GetMenuItems().GetCount() == pos + 1 &&
495 previousItem != NULL &&
496 previousItem->IsSeparator() )
497 {
498 // prev item is a trailing separator we want to hide
499 posSeptoHide = pos;
500 }
501 else if ( previousItem && previousItem->IsSeparator() &&
502 next && next->IsSeparator() )
503 {
504 // two consecutive separators, this is one too many
505 posSeptoHide = pos;
506 }
507 else // no separators to hide
508 {
509 posSeptoHide = 0;
510 }
511
512 if ( posSeptoHide )
513 {
514 // hide the separator as well
515 ChangeMenuItemAttributes( MAC_WXHMENU( GetHMenu() ),
516 posSeptoHide,
517 kMenuItemAttrHidden,
518 0 );
519 }
520 }
521 }
522
523 previousItem = item ;
524 }
525
526 if ( isSubMenu )
527 ::InsertMenu(MAC_WXHMENU( GetHMenu()), -1);
528 }
529
530 // undo all changes from the MacBeforeDisplay call
531 void wxMenu::MacAfterDisplay( bool isSubMenu )
532 {
533 if ( isSubMenu )
534 ::DeleteMenu(MacGetMenuId());
535
536 wxMenuItemList::compatibility_iterator node;
537 wxMenuItem *item;
538
539 for (node = GetMenuItems().GetFirst(); node; node = node->GetNext())
540 {
541 item = (wxMenuItem *)node->GetData();
542 wxMenu* subMenu = item->GetSubMenu() ;
543 if (subMenu)
544 {
545 subMenu->MacAfterDisplay( true ) ;
546 }
547 else
548 {
549 // no need to undo hidings
550 }
551 }
552 }
553
554 wxInt32 wxMenu::MacHandleCommandProcess( wxMenuItem* item, int id, wxWindow* targetWindow )
555 {
556 OSStatus result = eventNotHandledErr ;
557 if (item->IsCheckable())
558 item->Check( !item->IsChecked() ) ;
559
560 if ( SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) )
561 result = noErr ;
562 else
563 {
564 if ( targetWindow != NULL )
565 {
566 wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED , id);
567 event.SetEventObject(targetWindow);
568 event.SetInt(item->IsCheckable() ? item->IsChecked() : -1);
569
570 if ( targetWindow->HandleWindowEvent(event) )
571 result = noErr ;
572 }
573 }
574 return result;
575 }
576
577 wxInt32 wxMenu::MacHandleCommandUpdateStatus(wxMenuItem* WXUNUSED(item),
578 int id,
579 wxWindow* targetWindow)
580 {
581 OSStatus result = eventNotHandledErr ;
582 wxUpdateUIEvent event(id);
583 event.SetEventObject( this );
584
585 bool processed = false;
586
587 // Try the menu's event handler
588 {
589 wxEvtHandler *handler = GetEventHandler();
590 if ( handler )
591 processed = handler->ProcessEvent(event);
592 }
593
594 // Try the window the menu was popped up from
595 // (and up through the hierarchy)
596 if ( !processed )
597 {
598 const wxMenuBase *menu = this;
599 while ( menu )
600 {
601 wxWindow *win = menu->GetInvokingWindow();
602 if ( win )
603 {
604 processed = win->HandleWindowEvent(event);
605 break;
606 }
607
608 menu = menu->GetParent();
609 }
610 }
611
612 if ( !processed && targetWindow != NULL)
613 {
614 processed = targetWindow->HandleWindowEvent(event);
615 }
616
617 if ( processed )
618 {
619 // if anything changed, update the changed attribute
620 if (event.GetSetText())
621 SetLabel(id, event.GetText());
622 if (event.GetSetChecked())
623 Check(id, event.GetChecked());
624 if (event.GetSetEnabled())
625 Enable(id, event.GetEnabled());
626
627 result = noErr ;
628 }
629 return result;
630 }
631
632 // Menu Bar
633
634 /*
635
636 Mac Implementation note :
637
638 The Mac has only one global menubar, so we attempt to install the currently
639 active menubar from a frame, we currently don't take into account mdi-frames
640 which would ask for menu-merging
641
642 Secondly there is no mac api for changing a menubar that is not the current
643 menubar, so we have to wait for preparing the actual menubar until the
644 wxMenubar is to be used
645
646 We can in subsequent versions use MacInstallMenuBar to provide some sort of
647 auto-merge for MDI in case this will be necessary
648
649 */
650
651 wxMenuBar* wxMenuBar::s_macInstalledMenuBar = NULL ;
652 wxMenuBar* wxMenuBar::s_macCommonMenuBar = NULL ;
653 bool wxMenuBar::s_macAutoWindowMenu = true ;
654 WXHMENU wxMenuBar::s_macWindowMenuHandle = NULL ;
655
656 void wxMenuBar::Init()
657 {
658 m_eventHandler = this;
659 m_menuBarFrame = NULL;
660 m_invokingWindow = (wxWindow*) NULL;
661 }
662
663 wxMenuBar::wxMenuBar()
664 {
665 Init();
666 }
667
668 wxMenuBar::wxMenuBar( long WXUNUSED(style) )
669 {
670 Init();
671 }
672
673 wxMenuBar::wxMenuBar(size_t count, wxMenu *menus[], const wxString titles[], long WXUNUSED(style))
674 {
675 Init();
676
677 m_titles.Alloc(count);
678
679 for ( size_t i = 0; i < count; i++ )
680 {
681 m_menus.Append(menus[i]);
682 m_titles.Add(titles[i]);
683
684 menus[i]->Attach(this);
685 }
686 }
687
688 wxMenuBar::~wxMenuBar()
689 {
690 if (s_macCommonMenuBar == this)
691 s_macCommonMenuBar = NULL;
692
693 if (s_macInstalledMenuBar == this)
694 {
695 ::ClearMenuBar();
696 s_macInstalledMenuBar = NULL;
697 }
698 }
699
700 void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground), const wxRect *WXUNUSED(rect))
701 {
702 wxCHECK_RET( IsAttached(), wxT("can't refresh unatteched menubar") );
703
704 DrawMenuBar();
705 }
706
707 void wxMenuBar::MacInstallMenuBar()
708 {
709 if ( s_macInstalledMenuBar == this )
710 return ;
711
712 MenuBarHandle menubar = NULL ;
713
714 menubar = NewHandleClear( 6 /* sizeof( MenuBarHeader ) */ ) ;
715
716 ::SetMenuBar( menubar ) ;
717 DisposeMenuBar( menubar ) ;
718 MenuHandle appleMenu = NULL ;
719
720 verify_noerr( CreateNewMenu( kwxMacAppleMenuId , 0 , &appleMenu ) ) ;
721 verify_noerr( SetMenuTitleWithCFString( appleMenu , CFSTR( "\x14" ) ) );
722
723 // Add About/Preferences separator only on OS X
724 // KH/RN: Separator is always present on 10.3 but not on 10.2
725 // However, the change from 10.2 to 10.3 suggests it is preferred
726 InsertMenuItemTextWithCFString( appleMenu,
727 CFSTR(""), 0, kMenuItemAttrSeparator, 0);
728 InsertMenuItemTextWithCFString( appleMenu,
729 CFSTR("About..."), 0, 0, 0);
730 MacInsertMenu( appleMenu , 0 ) ;
731
732 // if we have a mac help menu, clean it up before adding new items
733 MenuHandle helpMenuHandle ;
734 MenuItemIndex firstUserHelpMenuItem ;
735
736 if ( UMAGetHelpMenuDontCreate( &helpMenuHandle , &firstUserHelpMenuItem) == noErr )
737 {
738 for ( int i = CountMenuItems( helpMenuHandle ) ; i >= firstUserHelpMenuItem ; --i )
739 DeleteMenuItem( helpMenuHandle , i ) ;
740 }
741 else
742 {
743 helpMenuHandle = NULL ;
744 }
745
746 if ( wxApp::s_macPreferencesMenuItemId)
747 {
748 wxMenuItem *item = FindItem( wxApp::s_macPreferencesMenuItemId , NULL ) ;
749 if ( item == NULL || !(item->IsEnabled()) )
750 DisableMenuCommand( NULL , kHICommandPreferences ) ;
751 else
752 EnableMenuCommand( NULL , kHICommandPreferences ) ;
753 }
754
755 // Unlike preferences which may or may not exist, the Quit item should be always
756 // enabled unless it is added by the application and then disabled, otherwise
757 // a program would be required to add an item with wxID_EXIT in order to get the
758 // Quit menu item to be enabled, which seems a bit burdensome.
759 if ( wxApp::s_macExitMenuItemId)
760 {
761 wxMenuItem *item = FindItem( wxApp::s_macExitMenuItemId , NULL ) ;
762 if ( item != NULL && !(item->IsEnabled()) )
763 DisableMenuCommand( NULL , kHICommandQuit ) ;
764 else
765 EnableMenuCommand( NULL , kHICommandQuit ) ;
766 }
767
768 wxString strippedHelpMenuTitle = wxStripMenuCodes( wxApp::s_macHelpMenuTitleName ) ;
769 wxString strippedTranslatedHelpMenuTitle = wxStripMenuCodes( wxString( _("&Help") ) ) ;
770 wxMenuList::compatibility_iterator menuIter = m_menus.GetFirst();
771 for (size_t i = 0; i < m_menus.GetCount(); i++, menuIter = menuIter->GetNext())
772 {
773 wxMenuItemList::compatibility_iterator node;
774 wxMenuItem *item;
775 wxMenu* menu = menuIter->GetData() , *subMenu = NULL ;
776 wxString strippedMenuTitle = wxStripMenuCodes(m_titles[i]);
777
778 if ( strippedMenuTitle == wxT("?") || strippedMenuTitle == strippedHelpMenuTitle || strippedMenuTitle == strippedTranslatedHelpMenuTitle )
779 {
780 for (node = menu->GetMenuItems().GetFirst(); node; node = node->GetNext())
781 {
782 item = (wxMenuItem *)node->GetData();
783 subMenu = item->GetSubMenu() ;
784 if (subMenu)
785 {
786 // we don't support hierarchical menus in the help menu yet
787 }
788 else
789 {
790 if ( item->GetId() != wxApp::s_macAboutMenuItemId )
791 {
792 // we have found a user help menu and an item other than the about item,
793 // so we can create the mac help menu now, if we haven't created it yet
794 if ( helpMenuHandle == NULL )
795 {
796 if ( UMAGetHelpMenu( &helpMenuHandle , &firstUserHelpMenuItem) != noErr )
797 {
798 helpMenuHandle = NULL ;
799 break ;
800 }
801 }
802 }
803
804 if ( item->IsSeparator() )
805 {
806 if ( helpMenuHandle )
807 AppendMenuItemTextWithCFString( helpMenuHandle,
808 CFSTR(""), kMenuItemAttrSeparator, 0,NULL);
809 }
810 else
811 {
812 wxAcceleratorEntry*
813 entry = wxAcceleratorEntry::Create( item->GetItemLabel() ) ;
814
815 if ( item->GetId() == wxApp::s_macAboutMenuItemId )
816 {
817 // this will be taken care of below
818 }
819 else
820 {
821 if ( helpMenuHandle )
822 {
823 UMAAppendMenuItem(helpMenuHandle, wxStripMenuCodes(item->GetItemLabel()) , wxFont::GetDefaultEncoding(), entry);
824 SetMenuItemCommandID( helpMenuHandle , CountMenuItems(helpMenuHandle) , wxIdToMacCommand ( item->GetId() ) ) ;
825 SetMenuItemRefCon( helpMenuHandle , CountMenuItems(helpMenuHandle) , (URefCon) item ) ;
826 }
827 }
828
829 delete entry ;
830 }
831 }
832 }
833 }
834
835 else if ( ( m_titles[i] == wxT("Window") || m_titles[i] == wxT("&Window") )
836 && GetAutoWindowMenu() )
837 {
838 if ( MacGetWindowMenuHMenu() == NULL )
839 {
840 CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
841 }
842
843 MenuRef wm = (MenuRef)MacGetWindowMenuHMenu();
844 if ( wm == NULL )
845 break;
846
847 // get the insertion point in the standard menu
848 MenuItemIndex winListStart;
849 GetIndMenuItemWithCommandID(wm,
850 kHICommandWindowListSeparator, 1, NULL, &winListStart);
851
852 // add a separator so that the standard items and the custom items
853 // aren't mixed together, but only if this is the first run
854 OSStatus err = GetIndMenuItemWithCommandID(wm,
855 'WXWM', 1, NULL, NULL);
856
857 if ( err == menuItemNotFoundErr )
858 {
859 InsertMenuItemTextWithCFString( wm,
860 CFSTR(""), winListStart-1, kMenuItemAttrSeparator, 'WXWM');
861 }
862
863 wxInsertMenuItemsInMenu(menu, wm, winListStart);
864 }
865 else
866 {
867 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , m_titles[i], m_font.GetEncoding() ) ;
868 menu->MacBeforeDisplay(false) ;
869
870 ::InsertMenu(MAC_WXHMENU(_wxMenuAt(m_menus, i)->GetHMenu()), 0);
871 }
872 }
873
874 // take care of the about menu item wherever it is
875 {
876 wxMenu* aboutMenu ;
877 wxMenuItem *aboutMenuItem = FindItem(wxApp::s_macAboutMenuItemId , &aboutMenu) ;
878 if ( aboutMenuItem )
879 {
880 wxAcceleratorEntry*
881 entry = wxAcceleratorEntry::Create( aboutMenuItem->GetItemLabel() ) ;
882 UMASetMenuItemText( GetMenuHandle( kwxMacAppleMenuId ) , 1 , wxStripMenuCodes ( aboutMenuItem->GetItemLabel() ) , wxFont::GetDefaultEncoding() );
883 UMAEnableMenuItem( GetMenuHandle( kwxMacAppleMenuId ) , 1 , true );
884 SetMenuItemCommandID( GetMenuHandle( kwxMacAppleMenuId ) , 1 , kHICommandAbout ) ;
885 SetMenuItemRefCon(GetMenuHandle( kwxMacAppleMenuId ) , 1 , (URefCon)aboutMenuItem ) ;
886 UMASetMenuItemShortcut( GetMenuHandle( kwxMacAppleMenuId ) , 1 , entry ) ;
887 }
888 }
889
890 if ( GetAutoWindowMenu() )
891 {
892 if ( MacGetWindowMenuHMenu() == NULL )
893 CreateStandardWindowMenu( 0 , (MenuHandle*) &s_macWindowMenuHandle ) ;
894
895 InsertMenu( (MenuHandle) MacGetWindowMenuHMenu() , 0 ) ;
896 }
897
898 ::DrawMenuBar() ;
899 s_macInstalledMenuBar = this;
900 }
901
902 void wxMenuBar::EnableTop(size_t pos, bool enable)
903 {
904 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
905
906 _wxMenuAt(m_menus, pos)->MacEnableMenu( enable ) ;
907 Refresh();
908 }
909
910 bool wxMenuBar::Enable(bool enable)
911 {
912 wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") );
913
914 size_t i;
915 for (i = 0; i < GetMenuCount(); i++)
916 EnableTop(i, enable);
917
918 return true;
919 }
920
921 void wxMenuBar::SetMenuLabel(size_t pos, const wxString& label)
922 {
923 wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
924
925 m_titles[pos] = label;
926
927 if ( !IsAttached() )
928 return;
929
930 _wxMenuAt(m_menus, pos)->SetTitle( label ) ;
931
932 if (wxMenuBar::s_macInstalledMenuBar == this) // are we currently installed ?
933 {
934 ::SetMenuBar( GetMenuBar() ) ;
935 ::InvalMenuBar() ;
936 }
937 }
938
939 wxString wxMenuBar::GetMenuLabel(size_t pos) const
940 {
941 wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
942 wxT("invalid menu index in wxMenuBar::GetMenuLabel") );
943
944 return m_titles[pos];
945 }
946
947 int wxMenuBar::FindMenu(const wxString& title)
948 {
949 wxString menuTitle = wxStripMenuCodes(title);
950
951 size_t count = GetMenuCount();
952 for ( size_t i = 0; i < count; i++ )
953 {
954 wxString title = wxStripMenuCodes(m_titles[i]);
955 if ( menuTitle == title )
956 return i;
957 }
958
959 return wxNOT_FOUND;
960 }
961
962 // ---------------------------------------------------------------------------
963 // wxMenuBar construction
964 // ---------------------------------------------------------------------------
965
966 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
967 {
968 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
969 if ( !menuOld )
970 return NULL;
971
972 m_titles[pos] = title;
973
974 if ( IsAttached() )
975 {
976 if (s_macInstalledMenuBar == this)
977 {
978 menuOld->MacAfterDisplay( false ) ;
979 ::DeleteMenu( menuOld->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
980
981 menu->MacBeforeDisplay( false ) ;
982 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , m_font.GetEncoding() ) ;
983 if ( pos == m_menus.GetCount() - 1)
984 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
985 else
986 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos + 1)->MacGetMenuId() ) ;
987 }
988
989 Refresh();
990 }
991
992 if (m_invokingWindow)
993 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
994
995 return menuOld;
996 }
997
998 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
999 {
1000 if ( !wxMenuBarBase::Insert(pos, menu, title) )
1001 return false;
1002
1003 m_titles.Insert(title, pos);
1004
1005 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , m_font.GetEncoding() ) ;
1006
1007 if ( IsAttached() && s_macInstalledMenuBar == this )
1008 {
1009 if (s_macInstalledMenuBar == this)
1010 {
1011 menu->MacBeforeDisplay( false ) ;
1012
1013 if ( pos == (size_t) -1 || pos + 1 == m_menus.GetCount() )
1014 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1015 else
1016 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , _wxMenuAt(m_menus, pos+1)->MacGetMenuId() ) ;
1017 }
1018
1019 Refresh();
1020 }
1021
1022 if (m_invokingWindow)
1023 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1024
1025 return true;
1026 }
1027
1028 wxMenu *wxMenuBar::Remove(size_t pos)
1029 {
1030 wxMenu *menu = wxMenuBarBase::Remove(pos);
1031 if ( !menu )
1032 return NULL;
1033
1034 if ( IsAttached() )
1035 {
1036 if (s_macInstalledMenuBar == this)
1037 ::DeleteMenu( menu->MacGetMenuId() /* m_menus[pos]->MacGetMenuId() */ ) ;
1038
1039 Refresh();
1040 }
1041
1042 m_titles.RemoveAt(pos);
1043
1044 return menu;
1045 }
1046
1047 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1048 {
1049 WXHMENU submenu = menu ? menu->GetHMenu() : 0;
1050 wxCHECK_MSG( submenu, false, wxT("can't append invalid menu to menubar") );
1051
1052 if ( !wxMenuBarBase::Append(menu, title) )
1053 return false;
1054
1055 m_titles.Add(title);
1056
1057 UMASetMenuTitle( MAC_WXHMENU(menu->GetHMenu()) , title , m_font.GetEncoding() ) ;
1058
1059 if ( IsAttached() )
1060 {
1061 if (s_macInstalledMenuBar == this)
1062 {
1063 menu->MacBeforeDisplay( false ) ;
1064 ::InsertMenu( MAC_WXHMENU(menu->GetHMenu()) , 0 ) ;
1065 }
1066
1067 Refresh();
1068 }
1069
1070 // m_invokingWindow is set after wxFrame::SetMenuBar(). This call enables
1071 // adding menu later on.
1072 if (m_invokingWindow)
1073 wxMenubarSetInvokingWindow( menu, m_invokingWindow );
1074
1075 return true;
1076 }
1077
1078 static void wxMenubarUnsetInvokingWindow( wxMenu *menu )
1079 {
1080 menu->SetInvokingWindow( (wxWindow*) NULL );
1081 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1082
1083 while (node)
1084 {
1085 wxMenuItem *menuitem = node->GetData();
1086 if (menuitem->IsSubMenu())
1087 wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu() );
1088
1089 node = node->GetNext();
1090 }
1091 }
1092
1093 static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win )
1094 {
1095 menu->SetInvokingWindow( win );
1096 wxMenuItem *menuitem;
1097 wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
1098
1099 while (node)
1100 {
1101 menuitem = node->GetData();
1102 if (menuitem->IsSubMenu())
1103 wxMenubarSetInvokingWindow( menuitem->GetSubMenu() , win );
1104
1105 node = node->GetNext();
1106 }
1107 }
1108
1109 void wxMenuBar::UnsetInvokingWindow()
1110 {
1111 m_invokingWindow = (wxWindow*) NULL;
1112 wxMenu *menu;
1113 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1114
1115 while (node)
1116 {
1117 menu = node->GetData();
1118 wxMenubarUnsetInvokingWindow( menu );
1119
1120 node = node->GetNext();
1121 }
1122 }
1123
1124 void wxMenuBar::SetInvokingWindow(wxFrame *frame)
1125 {
1126 m_invokingWindow = frame;
1127 wxMenu *menu;
1128 wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1129
1130 while (node)
1131 {
1132 menu = node->GetData();
1133 wxMenubarSetInvokingWindow( menu, frame );
1134
1135 node = node->GetNext();
1136 }
1137 }
1138
1139 void wxMenuBar::Detach()
1140 {
1141 wxMenuBarBase::Detach() ;
1142 }
1143
1144 void wxMenuBar::Attach(wxFrame *frame)
1145 {
1146 wxMenuBarBase::Attach( frame ) ;
1147 }
1148
1149 // ---------------------------------------------------------------------------
1150 // wxMenuBar searching for menu items
1151 // ---------------------------------------------------------------------------
1152
1153 // Find the itemString in menuString, and return the item id or wxNOT_FOUND
1154 int wxMenuBar::FindMenuItem(const wxString& menuString,
1155 const wxString& itemString) const
1156 {
1157 wxString menuLabel = wxStripMenuCodes(menuString);
1158 size_t count = GetMenuCount();
1159 for ( size_t i = 0; i < count; i++ )
1160 {
1161 wxString title = wxStripMenuCodes(m_titles[i]);
1162 if ( menuLabel == title )
1163 return _wxMenuAt(m_menus, i)->FindItem(itemString);
1164 }
1165
1166 return wxNOT_FOUND;
1167 }
1168
1169 wxMenuItem *wxMenuBar::FindItem(int id, wxMenu **itemMenu) const
1170 {
1171 if ( itemMenu )
1172 *itemMenu = NULL;
1173
1174 wxMenuItem *item = NULL;
1175 size_t count = GetMenuCount();
1176 for ( size_t i = 0; !item && (i < count); i++ )
1177 item = _wxMenuAt(m_menus, i)->FindItem(id, itemMenu);
1178
1179 return item;
1180 }