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}
#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
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; }
/////////////////////////////////////////////////////////////////////////////
-// 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)
// implementation only from now on
// -------------------------------
+ virtual void Attach(wxMenuBarBase *menubar);
+
bool MSWCommand(WXUINT param, WXWORD id);
// semi-private accessors
// 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;
// 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)
};
if (item->IsCheckable())
{
item->Toggle();
+
// use the new value
commandEvent.SetInt(item->IsChecked());
}
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 )
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 )
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
// ---------------------------------------------------------------------------
void wxMenu::Init()
{
m_doBreak = FALSE;
+ m_startRadioGroup = -1;
// create the menu
m_hMenu = (WXHMENU)CreatePopupMenu();
m_doBreak = TRUE;
}
+void wxMenu::Attach(wxMenuBarBase *menubar)
+{
+ wxMenuBase::Attach(menubar);
+
+ EndRadioGroup();
+}
+
#if wxUSE_ACCEL
int wxMenu::FindAccel(int id) const
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);
}
// 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
}
if ( !wxMenuBarBase::Append(menu, title) )
return FALSE;
- // Already done in Append above
- //menu->Attach(this);
-
m_titles.Add(title);
if ( IsAttached() )
{
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))
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);