X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/aa3e374197e99d6362468dd821935050e5074065..cc4d5638c66a409e421420ed7110917755a66788:/src/osx/menu_osx.cpp diff --git a/src/osx/menu_osx.cpp b/src/osx/menu_osx.cpp index 54b2d6aa91..2bf5cede9e 100644 --- a/src/osx/menu_osx.cpp +++ b/src/osx/menu_osx.cpp @@ -4,7 +4,6 @@ // Author: Stefan Csomor // Modified by: // Created: 1998-01-01 -// RCS-ID: $Id$ // Copyright: (c) Stefan Csomor // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -43,7 +42,7 @@ wxMenuImpl::~wxMenuImpl() { } -// the (popup) menu title has this special id +// the (popup) menu title has this special menuid static const int idMenuTitle = -3; // ============================================================================ @@ -57,7 +56,6 @@ static const int idMenuTitle = -3; void wxMenu::Init() { m_doBreak = false; - m_startRadioGroup = -1; m_allowRearrange = true; m_noEventsMode = false; @@ -89,13 +87,6 @@ void wxMenu::Break() // not available on the mac platform } -void wxMenu::Attach(wxMenuBarBase *menubar) -{ - wxMenuBase::Attach(menubar); - - EndRadioGroup(); -} - void wxMenu::SetAllowRearrange( bool allow ) { m_allowRearrange = allow; @@ -108,18 +99,20 @@ void wxMenu::SetNoEventsMode( bool noEvents ) // function appends a new item or submenu to the menu // append a new item or submenu to the menu -bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos) +bool wxMenu::DoInsertOrAppend(wxMenuItem *item, size_t pos) { - wxASSERT_MSG( pItem != NULL, wxT("can't append NULL item to the menu") ); - m_peer->InsertOrAppend( pItem, pos ); + wxASSERT_MSG( item != NULL, wxT("can't append NULL item to the menu") ); + GetPeer()->InsertOrAppend( item, pos ); - if ( pItem->IsSeparator() ) + bool check = false; + + if ( item->IsSeparator() ) { // nothing to do here } else { - wxMenu *pSubMenu = pItem->GetSubMenu() ; + wxMenu *pSubMenu = item->GetSubMenu() ; if ( pSubMenu != NULL ) { wxASSERT_MSG( pSubMenu->GetHMenu() != NULL , wxT("invalid submenu added")); @@ -127,77 +120,131 @@ bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos) pSubMenu->DoRearrange(); } - else + else if ( item->IsRadio() ) { - if ( pItem->GetId() == idMenuTitle ) - pItem->GetMenu()->Enable( idMenuTitle, false ); - } - } - - // if we're already attached to the menubar, we must update it - if ( IsAttached() && GetMenuBar()->IsAttached() ) - GetMenuBar()->Refresh(); + // If a previous or next item is a radio button, add this radio + // button to the existing radio group. Otherwise start a new one + // for it. + wxMenuItemList& items = GetMenuItems(); - return true ; -} + size_t const + posItem = pos == (size_t)-1 ? items.GetCount() - 1 : pos; -void wxMenu::EndRadioGroup() -{ - // we're not inside a radio group any longer - m_startRadioGroup = -1; -} + wxMenuItemList::compatibility_iterator node = items.Item(posItem); + wxCHECK_MSG( node, false, wxS("New item must have been inserted") ); -wxMenuItem* wxMenu::DoAppend(wxMenuItem *item) -{ - wxCHECK_MSG( item, NULL, wxT("NULL item in wxMenu::DoAppend") ); + bool foundGroup = false; + if ( node->GetPrevious() ) + { + wxMenuItem* const prev = node->GetPrevious()->GetData(); - bool check = false; + if ( prev->IsRadio() ) + { + // This item is in the same group as the preceding one so + // we should use the same starting item, but getting it is + // a bit difficult as we can't query the start radio group + // item for it. + const int groupStart = prev->IsRadioGroupStart() + ? posItem - 1 + : prev->GetRadioGroupStart(); + item->SetRadioGroupStart(groupStart); + + // We must also account for the new item by incrementing + // the index of the last item in this group. + wxMenuItem* const first = items.Item(groupStart)->GetData(); + first->SetRadioGroupEnd(first->GetRadioGroupEnd() + 1); + + foundGroup = true; + } + } - if ( item->GetKind() == wxITEM_RADIO ) - { - int count = GetMenuItemCount(); + if ( !foundGroup && node->GetNext() ) + { + wxMenuItem* const next = node->GetNext()->GetData(); - if ( m_startRadioGroup == -1 ) - { - // start a new radio group - m_startRadioGroup = count; + if ( next->IsRadio() ) + { + // This item is the new starting item of this group as the + // previous item is not a radio item. + wxASSERT_MSG( next->IsRadioGroupStart(), + wxS("Where is the start of this group?") ); - // for now it has just one element - item->SetAsRadioGroupStart(); - item->SetRadioGroupEnd(m_startRadioGroup); + // The index of the last item of the radio group must be + // incremented to account for the new item. + item->SetAsRadioGroupStart(); + item->SetRadioGroupEnd(next->GetRadioGroupEnd() + 1); - // ensure that we have a checked item in the radio group - check = true; - } - else // extend the current radio group - { - // we need to update its end item - item->SetRadioGroupStart(m_startRadioGroup); - wxMenuItemList::compatibility_iterator node = GetMenuItems().Item(m_startRadioGroup); + // And the previous start item is not one any longer. + next->SetAsRadioGroupStart(false); - if ( node ) - { - node->GetData()->SetRadioGroupEnd(count); + foundGroup = true; + } } - else + + if ( !foundGroup ) { - wxFAIL_MSG( wxT("where is the radio group start item?") ); + // start a new radio group + item->SetAsRadioGroupStart(); + item->SetRadioGroupEnd(posItem); + + // ensure that we have a checked item in the radio group + check = true; } } + else + { + if ( item->GetId() == idMenuTitle ) + item->GetMenu()->Enable( idMenuTitle, false ); + } } - else // not a radio item + + // We also need to update the indices of radio group start and end we store + // in any existing radio items after this item. + if ( pos < GetMenuItemCount() - 1 ) // takes into account pos == -1 case { - EndRadioGroup(); + for ( wxMenuItemList::compatibility_iterator + node = GetMenuItems().Item(pos + 1); + node; + node = node->GetNext() ) + { + wxMenuItem* const item = node->GetData(); + if ( item->IsRadio() ) + { + if ( item->IsRadioGroupStart() ) + { + // If the starting item is after the just inserted one, + // then the end one must be after it too and needs to be + // updated. + item->SetRadioGroupEnd(item->GetRadioGroupEnd() + 1); + } + else // Not the first radio group item. + { + // We need to update the start item index only if it is + // after the just inserted item. + const int groupStart = item->GetRadioGroupStart(); + if ( (size_t)groupStart > pos ) + item->SetRadioGroupStart(groupStart + 1); + } + } + } } - if ( !wxMenuBase::DoAppend(item) || !DoInsertOrAppend(item) ) - return NULL; + // if we're already attached to the menubar, we must update it + if ( IsAttached() && GetMenuBar()->IsAttached() ) + GetMenuBar()->Refresh(); if ( check ) - // check the item initially item->Check(true); - return item; + return true ; +} + +wxMenuItem* wxMenu::DoAppend(wxMenuItem *item) +{ + if (wxMenuBase::DoAppend(item) && DoInsertOrAppend(item) ) + return item; + + return NULL; } wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item) @@ -210,6 +257,36 @@ wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item) wxMenuItem *wxMenu::DoRemove(wxMenuItem *item) { + if ( item->IsRadio() ) + { + // Check if we're removing the item starting the radio group + if ( item->IsRadioGroupStart() ) + { + // Yes, we do, update the next radio group item, if any, to be the + // start one now. + const int endGroup = item->GetRadioGroupEnd(); + + wxMenuItemList::compatibility_iterator + node = GetMenuItems().Item(endGroup); + wxASSERT_MSG( node, wxS("Should have valid radio group end") ); + + while ( node->GetData() != item ) + { + const wxMenuItemList::compatibility_iterator + prevNode = node->GetPrevious(); + wxMenuItem* const prevItem = prevNode->GetData(); + if ( prevItem == item ) + { + prevItem->SetAsRadioGroupStart(); + prevItem->SetRadioGroupEnd(endGroup); + break; + } + + node = prevNode; + } + } + } + /* // we need to find the items position in the child list size_t pos; @@ -228,7 +305,7 @@ wxMenuItem *wxMenu::DoRemove(wxMenuItem *item) wxOSXMenuRemoveItem(m_hMenu , pos ); */ - m_peer->Remove( item ); + GetPeer()->Remove( item ); // and from internal data structures return wxMenuBase::DoRemove(item); } @@ -236,7 +313,7 @@ wxMenuItem *wxMenu::DoRemove(wxMenuItem *item) void wxMenu::SetTitle(const wxString& label) { m_title = label ; - m_peer->SetTitle( wxStripMenuCodes( label ) ); + GetPeer()->SetTitle( wxStripMenuCodes( label ) ); } bool wxMenu::ProcessCommand(wxCommandEvent & event) @@ -337,8 +414,8 @@ void wxMenu::DoRearrange() bool wxMenu::HandleCommandUpdateStatus( wxMenuItem* item, wxWindow* senderWindow ) { - int id = item ? item->GetId() : 0; - wxUpdateUIEvent event(id); + int menuid = item ? item->GetId() : 0; + wxUpdateUIEvent event(menuid); event.SetEventObject( this ); bool processed = false; @@ -368,11 +445,11 @@ bool wxMenu::HandleCommandUpdateStatus( wxMenuItem* item, wxWindow* senderWindow { // if anything changed, update the changed attribute if (event.GetSetText()) - SetLabel(id, event.GetText()); + SetLabel(menuid, event.GetText()); if (event.GetSetChecked()) - Check(id, event.GetChecked()); + Check(menuid, event.GetChecked()); if (event.GetSetEnabled()) - Enable(id, event.GetEnabled()); + Enable(menuid, event.GetEnabled()); } else { @@ -381,11 +458,11 @@ bool wxMenu::HandleCommandUpdateStatus( wxMenuItem* item, wxWindow* senderWindow // always reset them ourselves UInt32 cmd = 0; - if ( id == wxApp::s_macExitMenuItemId ) + if ( menuid == wxApp::s_macExitMenuItemId ) { cmd = kHICommandQuit; } - else if (id == wxApp::s_macPreferencesMenuItemId ) + else if (menuid == wxApp::s_macPreferencesMenuItemId ) { cmd = kHICommandPreferences; } @@ -406,19 +483,19 @@ bool wxMenu::HandleCommandUpdateStatus( wxMenuItem* item, wxWindow* senderWindow bool wxMenu::HandleCommandProcess( wxMenuItem* item, wxWindow* senderWindow ) { - int id = item ? item->GetId() : 0; + int menuid = item ? item->GetId() : 0; bool processed = false; if (item->IsCheckable()) item->Check( !item->IsChecked() ) ; - if ( SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) ) + if ( SendEvent( menuid , item->IsCheckable() ? item->IsChecked() : -1 ) ) processed = true ; else { if ( senderWindow != NULL ) { - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED , id); - event.SetEventObject(senderWindow); + wxCommandEvent event(wxEVT_MENU , menuid); + event.SetEventObject(this); event.SetInt(item->IsCheckable() ? item->IsChecked() : -1); if ( senderWindow->HandleWindowEvent(event) ) @@ -436,21 +513,30 @@ bool wxMenu::HandleCommandProcess( wxMenuItem* item, wxWindow* senderWindow ) void wxMenu::HandleMenuItemHighlighted( wxMenuItem* item ) { - int id = item ? item->GetId() : 0; - wxMenuEvent wxevent(wxEVT_MENU_HIGHLIGHT, id, this); + int menuid = item ? item->GetId() : 0; + wxMenuEvent wxevent(wxEVT_MENU_HIGHLIGHT, menuid, this); DoHandleMenuEvent( wxevent ); } -void wxMenu::HandleMenuOpened() +void wxMenu::DoHandleMenuOpenedOrClosed(wxEventType evtType) { - wxMenuEvent wxevent(wxEVT_MENU_OPEN, 0, this); + // Popup menu being currently shown or NULL, defined in wincmn.cpp. + extern wxMenu *wxCurrentPopupMenu; + + // Set the id to allow wxMenuEvent::IsPopup() to work correctly. + int menuid = this == wxCurrentPopupMenu ? wxID_ANY : 0; + wxMenuEvent wxevent(evtType, menuid, this); DoHandleMenuEvent( wxevent ); } +void wxMenu::HandleMenuOpened() +{ + DoHandleMenuOpenedOrClosed(wxEVT_MENU_OPEN); +} + void wxMenu::HandleMenuClosed() { - wxMenuEvent wxevent(wxEVT_MENU_CLOSE, 0, this); - DoHandleMenuEvent( wxevent ); + DoHandleMenuOpenedOrClosed(wxEVT_MENU_CLOSE); } bool wxMenu::DoHandleMenuEvent(wxEvent& wxevent) @@ -497,8 +583,37 @@ wxMenuBar* wxMenuBar::s_macCommonMenuBar = NULL ; bool wxMenuBar::s_macAutoWindowMenu = true ; WXHMENU wxMenuBar::s_macWindowMenuHandle = NULL ; + +wxMenu* emptyMenuBar = NULL; + +const int firstMenuPos = 1; // to account for the 0th application menu on mac + void wxMenuBar::Init() { + if ( emptyMenuBar == NULL ) + { + emptyMenuBar = new wxMenu(); + + wxMenu* appleMenu = new wxMenu(); + appleMenu->SetAllowRearrange(false); +#if !wxOSX_USE_CARBON + // standard menu items, handled in wxMenu::HandleCommandProcess(), see above: + wxString hideLabel; + hideLabel = wxString::Format(_("Hide %s"), wxTheApp ? wxTheApp->GetAppDisplayName() : _("Application")); + appleMenu->Append( wxID_OSX_HIDE, hideLabel + "\tCtrl+H" ); + appleMenu->Append( wxID_OSX_HIDEOTHERS, _("Hide Others")+"\tAlt+Ctrl+H" ); + appleMenu->Append( wxID_OSX_SHOWALL, _("Show All") ); + appleMenu->AppendSeparator(); + + // Do always add "Quit" item unconditionally however, it can't be disabled. + wxString quitLabel; + quitLabel = wxString::Format(_("Quit %s"), wxTheApp ? wxTheApp->GetAppDisplayName() : _("Application")); + appleMenu->Append( wxApp::s_macExitMenuItemId, quitLabel + "\tCtrl+Q" ); +#endif // !wxOSX_USE_CARBON + + emptyMenuBar->AppendSubMenu(appleMenu, "\x14") ; + } + m_eventHandler = this; m_menuBarFrame = NULL; m_rootMenu = new wxMenu(); @@ -530,21 +645,17 @@ void wxMenuBar::Init() } // standard menu items, handled in wxMenu::HandleCommandProcess(), see above: - wxString hideLabel(_("Hide")); - if ( wxTheApp ) - hideLabel << ' ' << wxTheApp->GetAppDisplayName(); - hideLabel << "\tCtrl+H"; - m_appleMenu->Append( wxID_OSX_HIDE, hideLabel ); + wxString hideLabel; + hideLabel = wxString::Format(_("Hide %s"), wxTheApp ? wxTheApp->GetAppDisplayName() : _("Application")); + m_appleMenu->Append( wxID_OSX_HIDE, hideLabel + "\tCtrl+H" ); m_appleMenu->Append( wxID_OSX_HIDEOTHERS, _("Hide Others")+"\tAlt+Ctrl+H" ); m_appleMenu->Append( wxID_OSX_SHOWALL, _("Show All") ); m_appleMenu->AppendSeparator(); // Do always add "Quit" item unconditionally however, it can't be disabled. - wxString quitLabel(_("Quit")); - if ( wxTheApp ) - quitLabel << ' ' << wxTheApp->GetAppDisplayName(); - quitLabel << "\tCtrl+Q"; - m_appleMenu->Append( wxApp::s_macExitMenuItemId, quitLabel ); + wxString quitLabel; + quitLabel = wxString::Format(_("Quit %s"), wxTheApp ? wxTheApp->GetAppDisplayName() : _("Application")); + m_appleMenu->Append( wxApp::s_macExitMenuItemId, quitLabel + "\tCtrl+Q" ); #endif // !wxOSX_USE_CARBON m_rootMenu->AppendSubMenu(m_appleMenu, "\x14") ; @@ -580,8 +691,16 @@ wxMenuBar::~wxMenuBar() if (s_macInstalledMenuBar == this) { + emptyMenuBar->GetPeer()->MakeRoot(); s_macInstalledMenuBar = NULL; } + wxDELETE( m_rootMenu ); + // apple menu is a submenu, therefore we don't have to delete it + m_appleMenu = NULL; + + // deleting the root menu also removes all its wxMenu* submenus, therefore + // we must avoid double deleting them in the superclass destructor + m_menus.clear(); } void wxMenuBar::Refresh(bool WXUNUSED(eraseBackground), const wxRect *WXUNUSED(rect)) @@ -598,13 +717,12 @@ void wxMenuBar::MacInstallMenuBar() // hide items in the apple menu that don't exist in the wx menubar - int id = 0; wxMenuItem* appleItem = NULL; wxMenuItem* wxItem = NULL; - id = wxApp::s_macAboutMenuItemId; - appleItem = m_appleMenu->FindItem(id); - wxItem = FindItem(id); + int menuid = wxApp::s_macAboutMenuItemId; + appleItem = m_appleMenu->FindItem(menuid); + wxItem = FindItem(menuid); if ( appleItem != NULL ) { if ( wxItem == NULL ) @@ -613,9 +731,9 @@ void wxMenuBar::MacInstallMenuBar() appleItem->SetItemLabel(wxItem->GetItemLabel()); } - id = wxApp::s_macPreferencesMenuItemId; - appleItem = m_appleMenu->FindItem(id); - wxItem = FindItem(id); + menuid = wxApp::s_macPreferencesMenuItemId; + appleItem = m_appleMenu->FindItem(menuid); + wxItem = FindItem(menuid); if ( appleItem != NULL ) { if ( wxItem == NULL ) @@ -627,26 +745,6 @@ void wxMenuBar::MacInstallMenuBar() #if 0 - MenuBarHandle menubar = NULL ; - - menubar = NewHandleClear( 6 /* sizeof( MenuBarHeader ) */ ) ; - - ::SetMenuBar( menubar ) ; - DisposeMenuBar( menubar ) ; - MenuHandle appleMenu = NULL ; - - verify_noerr( CreateNewMenu( kwxMacAppleMenuId , 0 , &appleMenu ) ) ; - verify_noerr( SetMenuTitleWithCFString( appleMenu , CFSTR( "\x14" ) ) ); - - // Add About/Preferences separator only on OS X - // KH/RN: Separator is always present on 10.3 but not on 10.2 - // However, the change from 10.2 to 10.3 suggests it is preferred - InsertMenuItemTextWithCFString( appleMenu, - CFSTR(""), 0, kMenuItemAttrSeparator, 0); - InsertMenuItemTextWithCFString( appleMenu, - CFSTR("About..."), 0, 0, 0); - MacInsertMenu( appleMenu , 0 ) ; - // if we have a mac help menu, clean it up before adding new items MenuHandle helpMenuHandle ; MenuItemIndex firstUserHelpMenuItem ; @@ -826,11 +924,22 @@ void wxMenuBar::EnableTop(size_t pos, bool enable) { wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") ); - m_rootMenu->FindItemByPosition( pos )->Enable(enable); + m_rootMenu->FindItemByPosition(pos+firstMenuPos)->Enable(enable); Refresh(); } +bool wxMenuBar::IsEnabledTop(size_t pos) const +{ + wxCHECK_MSG( IsAttached(), true, + wxT("doesn't work with unattached menubars") ); + + wxMenuItem* const item = m_rootMenu->FindItemByPosition(pos+firstMenuPos); + wxCHECK_MSG( item, false, wxT("invalid menu index") ); + + return item->IsEnabled(); +} + bool wxMenuBar::Enable(bool enable) { wxCHECK_MSG( IsAttached(), false, wxT("doesn't work with unattached menubars") ); @@ -861,8 +970,6 @@ wxString wxMenuBar::GetMenuLabel(size_t pos) const // wxMenuBar construction // --------------------------------------------------------------------------- -const int firstMenuPos = 1; // to account for the 0th application menu on mac - wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title) { wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title); @@ -882,6 +989,7 @@ bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title) return false; m_rootMenu->Insert( pos+firstMenuPos, wxMenuItem::New( m_rootMenu, wxID_ANY, title, "", wxITEM_NORMAL, menu ) ); + menu->SetTitle(title); return true; }