From: Vadim Zeitlin Date: Thu, 21 Mar 2002 02:35:08 +0000 (+0000) Subject: implemented radio menu items for wxMSW X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/0472ece753aa3c018dd2cc9816d3cd755f28efe8 implemented radio menu items for wxMSW git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@14696 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/docs/latex/wx/menu.tex b/docs/latex/wx/menu.tex index e53ab4519e..4f04df6731 100644 --- a/docs/latex/wx/menu.tex +++ b/docs/latex/wx/menu.tex @@ -2,10 +2,30 @@ A menu is a popup (or pull down) list of items, one of which may be selected before the menu goes away (clicking elsewhere dismisses the -menu). Menus may be used to construct either menu bars or popup menus. +menu). Menus may be used to construct either menu bars or popup menus. A menu item has an integer ID associated with it which can be used to -identify the selection, or to change the menu item in some way. +identify the selection, or to change the menu item in some way. A menu item +with a special identifier $-1$ is a separator item and doesn't have an +associated command but just makes a separator line appear in the menu. + +Menu items may be either normal items, check items or radio items. Normal items +don't have any special properties while the check items have a boolean flag +associated to them and they show a checkmark in the menu when the flag is set. +wxWindows automatically togles the flag value when the item is clicked and its +value may be retrieved using either \helpref{IsChecked}{wxmenuischecked} method +of wxMenu or wxMenuBar itself or by using +\helpref{wxEvent::IsChecked}{wxcommandeventischecked} when you get the menu +notification for the item in question. + +The radio items are similar to the check items except that all the other items +in the same radio group are unchecked when a radio item is checked. The radio +group is formed by a contiguous range of radio items, i.e. it starts at the +first item of this kind and ends with the first item of a different kind (or +the end of the menu). Notice that because the radio groups are defined in terms +of the item positions inserting or removing the items in the menu containing +the radio items risks to not work correctly. Finally note that the radio items +are only supported under Windows and GTK+ currently. \wxheading{Derived from} diff --git a/include/wx/features.h b/include/wx/features.h index 096497184d..25eacf788f 100644 --- a/include/wx/features.h +++ b/include/wx/features.h @@ -13,8 +13,8 @@ #ifndef _WX_FEATURES_H_ #define _WX_FEATURES_H_ -// radio menu items are currently only implemented in wxGTK -#if defined(__WXGTK__) // || defined(__WXMSW__) +// radio menu items are currently only implemented in wxGTK and wxMSW +#if defined(__WXGTK__) || defined(__WXMSW__) #define wxHAS_RADIO_MENU_ITEMS #else #undef wxHAS_RADIO_MENU_ITEMS diff --git a/include/wx/menuitem.h b/include/wx/menuitem.h index dac0c55cf5..cf8b989bf6 100644 --- a/include/wx/menuitem.h +++ b/include/wx/menuitem.h @@ -72,7 +72,8 @@ public: wxItemKind GetKind() const { return m_kind; } virtual void SetCheckable(bool checkable) { m_kind = wxItem_Check; } - bool IsCheckable() const { return m_kind == wxItem_Check; } + bool IsCheckable() const + { return m_kind == wxItem_Check || m_kind == wxItem_Radio; } bool IsSubMenu() const { return m_subMenu != NULL; } void SetSubMenu(wxMenu *menu) { m_subMenu = menu; } diff --git a/include/wx/msw/menu.h b/include/wx/msw/menu.h index 29472ce14c..310586e498 100644 --- a/include/wx/msw/menu.h +++ b/include/wx/msw/menu.h @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: menu.h +// Name: wx/msw/menu.h // Purpose: wxMenu, wxMenuBar classes // Author: Julian Smart // Modified by: Vadim Zeitlin (wxMenuItem is now in separate file) @@ -63,6 +63,8 @@ public: // implementation only from now on // ------------------------------- + virtual void Attach(wxMenuBarBase *menubar); + bool MSWCommand(WXUINT param, WXWORD id); // semi-private accessors @@ -91,9 +93,15 @@ private: // common part of Append/Insert (behaves as Append is pos == (size_t)-1) bool DoInsertOrAppend(wxMenuItem *item, size_t pos = (size_t)-1); + // terminate the current radio group, if any + void EndRadioGroup(); + // if TRUE, insert a breal before appending the next item bool m_doBreak; + // the position of the first item in the current radio group or -1 + int m_startRadioGroup; + // the menu handle of this menu WXHMENU m_hMenu; diff --git a/include/wx/msw/menuitem.h b/include/wx/msw/menuitem.h index ee507f7399..dbc4478b3d 100644 --- a/include/wx/msw/menuitem.h +++ b/include/wx/msw/menuitem.h @@ -60,7 +60,19 @@ public: // menu handle depending on what we're int GetRealId() const; + // mark item as belonging to the given radio group + void SetRadioGroup(int start, int end) + { + m_startRadioGroup = start; + m_endRadioGroup = end; + } + private: + // the positions of the first and last items of the radio group this item + // belongs to or -1 + int m_startRadioGroup, + m_endRadioGroup; + DECLARE_DYNAMIC_CLASS(wxMenuItem) }; diff --git a/src/common/framecmn.cpp b/src/common/framecmn.cpp index 4bcb11d5d0..77d1c1e1c4 100644 --- a/src/common/framecmn.cpp +++ b/src/common/framecmn.cpp @@ -196,6 +196,7 @@ bool wxFrameBase::ProcessCommand(int id) if (item->IsCheckable()) { item->Toggle(); + // use the new value commandEvent.SetInt(item->IsChecked()); } diff --git a/src/gtk/menu.cpp b/src/gtk/menu.cpp index 901d40e156..8474b6382e 100644 --- a/src/gtk/menu.cpp +++ b/src/gtk/menu.cpp @@ -840,7 +840,12 @@ void wxMenuItem::Check( bool check ) return; wxMenuItemBase::Check( check ); - gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check ); + + // GTK+ does it itself for the radio item + if ( GetKind() == wxItem_Check ) + { + gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check ); + } } void wxMenuItem::Enable( bool enable ) diff --git a/src/gtk1/menu.cpp b/src/gtk1/menu.cpp index 901d40e156..8474b6382e 100644 --- a/src/gtk1/menu.cpp +++ b/src/gtk1/menu.cpp @@ -840,7 +840,12 @@ void wxMenuItem::Check( bool check ) return; wxMenuItemBase::Check( check ); - gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check ); + + // GTK+ does it itself for the radio item + if ( GetKind() == wxItem_Check ) + { + gtk_check_menu_item_set_state( (GtkCheckMenuItem*)m_menuItem, (gint)check ); + } } void wxMenuItem::Enable( bool enable ) diff --git a/src/msw/menu.cpp b/src/msw/menu.cpp index e4299163f2..ec96dc7bc8 100644 --- a/src/msw/menu.cpp +++ b/src/msw/menu.cpp @@ -61,16 +61,31 @@ extern wxMenu *wxCurrentPopupMenu; static const int idMenuTitle = -2; // ---------------------------------------------------------------------------- -// macros +// private functions // ---------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler) -IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow) +// make the given menu item default +static void SetDefaultMenuItem(HMENU hmenu, UINT id) +{ + MENUITEMINFO mii; + wxZeroMemory(mii); + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STATE; + mii.fState = MFS_DEFAULT; + + if ( !::SetMenuItemInfo(hmenu, id, FALSE, &mii) ) + { + wxLogLastError(wxT("SetMenuItemInfo")); + } +} // ============================================================================ // implementation // ============================================================================ +IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler) +IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow) + // --------------------------------------------------------------------------- // wxMenu construction, adding and removing menu items // --------------------------------------------------------------------------- @@ -79,6 +94,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow) void wxMenu::Init() { m_doBreak = FALSE; + m_startRadioGroup = -1; // create the menu m_hMenu = (WXHMENU)CreatePopupMenu(); @@ -121,6 +137,13 @@ void wxMenu::Break() m_doBreak = TRUE; } +void wxMenu::Attach(wxMenuBarBase *menubar) +{ + wxMenuBase::Attach(menubar); + + EndRadioGroup(); +} + #if wxUSE_ACCEL int wxMenu::FindAccel(int id) const @@ -255,37 +278,70 @@ bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos) return FALSE; } - else - { - // if we just appended the title, highlight it -#ifdef __WIN32__ - if ( (int)id == idMenuTitle ) - { - // visually select the menu title - MENUITEMINFO mii; - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STATE; - mii.fState = MFS_DEFAULT; - if ( !SetMenuItemInfo(GetHmenu(), (unsigned)id, FALSE, &mii) ) - { - wxLogLastError(wxT("SetMenuItemInfo")); - } - } + // if we just appended the title, highlight it +#ifdef __WIN32__ + if ( (int)id == idMenuTitle ) + { + // visually select the menu title + SetDefaultMenuItem(GetHmenu(), id); + } #endif // __WIN32__ - // if we're already attached to the menubar, we must update it - if ( IsAttached() && m_menuBar->IsAttached() ) - { - m_menuBar->Refresh(); - } + // if we're already attached to the menubar, we must update it + if ( IsAttached() && m_menuBar->IsAttached() ) + { + m_menuBar->Refresh(); + } + + return TRUE; +} + +void wxMenu::EndRadioGroup() +{ + if ( m_startRadioGroup == -1 ) + { + // nothing to do + return; + } + + wxMenuItemList::Node *nodeStart = GetMenuItems().Item(m_startRadioGroup); + wxCHECK_RET( nodeStart, _T("where is the radio group start item?") ); + + int endRadioGroup = GetMenuItemCount(); - return TRUE; + wxMenuItemList::Node *node = nodeStart; + for ( int n = m_startRadioGroup; n < endRadioGroup && node; n++ ) + { + wxMenuItem *item = (wxMenuItem *)node->GetData(); + item->SetRadioGroup(m_startRadioGroup, endRadioGroup - 1); + + node = node->GetNext(); } + + nodeStart->GetData()->Check(TRUE); + + // we're not inside a radio group any longer + m_startRadioGroup = -1; } bool wxMenu::DoAppend(wxMenuItem *item) { + wxCHECK_MSG( item, FALSE, _T("NULL item in wxMenu::DoAppend") ); + + if ( item->GetKind() == wxItem_Radio ) + { + if ( m_startRadioGroup == -1 ) + { + // start a new radio group + m_startRadioGroup = GetMenuItemCount(); + } + } + else // not a radio item + { + EndRadioGroup(); + } + return wxMenuBase::DoAppend(item) && DoInsertOrAppend(item); } @@ -409,15 +465,7 @@ void wxMenu::SetTitle(const wxString& label) // put the title string in bold face if ( !m_title.IsEmpty() ) { - MENUITEMINFO mii; - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STATE; - mii.fState = MFS_DEFAULT; - - if ( !SetMenuItemInfo(hMenu, (unsigned)idMenuTitle, FALSE, &mii) ) - { - wxLogLastError(wxT("SetMenuItemInfo")); - } + SetDefaultMenuItem(GetHmenu(), (UINT)idMenuTitle); } #endif // Win32 } @@ -680,9 +728,6 @@ bool wxMenuBar::Append(wxMenu *menu, const wxString& title) if ( !wxMenuBarBase::Append(menu, title) ) return FALSE; - // Already done in Append above - //menu->Attach(this); - m_titles.Add(title); if ( IsAttached() ) diff --git a/src/msw/menuitem.cpp b/src/msw/menuitem.cpp index cc38055c1f..2cabd2c1f2 100644 --- a/src/msw/menuitem.cpp +++ b/src/msw/menuitem.cpp @@ -94,6 +94,9 @@ wxMenuItem::wxMenuItem(wxMenu *pParentMenu, { wxASSERT_MSG( pParentMenu != NULL, wxT("a menu item should have a parent") ); + m_startRadioGroup = + m_endRadioGroup = -1; + #if wxUSE_OWNER_DRAWN // set default menu colors #define SYS_COLOR(c) (wxSystemSettings::GetColour(wxSYS_COLOUR_##c)) @@ -167,13 +170,57 @@ void wxMenuItem::Check(bool check) if ( m_isChecked == check ) return; - long rc = CheckMenuItem(GetHMenuOf(m_parentMenu), - GetRealId(), - MF_BYCOMMAND | - (check ? MF_CHECKED : MF_UNCHECKED)); + int flags = check ? MF_CHECKED : MF_UNCHECKED; + HMENU hmenu = GetHMenuOf(m_parentMenu); - if ( rc == -1 ) { - wxLogLastError(wxT("CheckMenuItem")); + if ( GetKind() == wxItem_Radio ) + { + // it doesn't make sense to uncheck a radio item - what would this do? + if ( !check ) + return; + + const wxMenuItemList& items = m_parentMenu->GetMenuItems(); + int pos = items.IndexOf(this); + wxCHECK_RET( pos != wxNOT_FOUND, + _T("menuitem not found in the menu items list?") ); + +#ifdef __WIN32__ + if ( !::CheckMenuRadioItem(hmenu, + m_startRadioGroup, // first group item + m_endRadioGroup, // last one + pos, // the one to check + MF_BYPOSITION | flags) ) + { + wxLogLastError(_T("CheckMenuRadioItem")); + } +#endif // __WIN32__ + + // also uncheck all the other items in this radio group + wxMenuItemList::Node *node = items.Item(m_startRadioGroup); + for ( int n = m_startRadioGroup; n <= m_endRadioGroup && node; n++ ) + { + if ( n != pos ) + { + node->GetData()->m_isChecked = FALSE; + } + + // we also have to do it in the menu for Win16 (under Win32 + // CheckMenuRadioItem() does it for us) +#ifndef __WIN32__ + ::CheckMenuItem(hmenu, n, n == pos ? MF_CHECKED : MF_UNCHECKED); +#endif // Win16 + + node = node->GetNext(); + } + } + else // check item + { + if ( ::CheckMenuItem(hmenu, + GetRealId(), + MF_BYCOMMAND | flags) == -1 ) + { + wxLogLastError(wxT("CheckMenuItem")); + } } wxMenuItemBase::Check(check);