1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: wxMenu, wxMenuBar, wxMenuItem
4 // Author: Julian Smart
5 // Modified by: Vadim Zeitlin
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ===========================================================================
14 // ===========================================================================
16 // ---------------------------------------------------------------------------
18 // ---------------------------------------------------------------------------
21 #pragma implementation "menu.h"
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
42 #include "wx/ownerdrw.h"
45 #include "wx/msw/private.h"
47 // other standard headers
50 // ----------------------------------------------------------------------------
52 // ----------------------------------------------------------------------------
54 extern wxMenu
*wxCurrentPopupMenu
;
56 // ----------------------------------------------------------------------------
58 // ----------------------------------------------------------------------------
60 // the (popup) menu title has this special id
61 static const int idMenuTitle
= -2;
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
67 // make the given menu item default
68 static void SetDefaultMenuItem(HMENU hmenu
, UINT id
)
73 mii
.cbSize
= sizeof(MENUITEMINFO
);
74 mii
.fMask
= MIIM_STATE
;
75 mii
.fState
= MFS_DEFAULT
;
77 if ( !::SetMenuItemInfo(hmenu
, id
, FALSE
, &mii
) )
79 wxLogLastError(wxT("SetMenuItemInfo"));
85 UINT
GetMenuState(HMENU hMenu
, UINT id
, UINT flags
)
89 info
.cbSize
= sizeof(info
);
90 info
.fMask
= MIIM_STATE
;
91 if ( !GetMenuItemInfo(hMenu
, id
, flags
& MF_BYCOMMAND
? FALSE
: TRUE
, & info
) )
92 wxLogLastError(wxT("GetMenuItemInfo"));
97 // ============================================================================
99 // ============================================================================
101 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
102 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxWindow
)
104 // ---------------------------------------------------------------------------
105 // wxMenu construction, adding and removing menu items
106 // ---------------------------------------------------------------------------
108 // Construct a menu with optional title (then use append)
112 m_startRadioGroup
= -1;
115 m_hMenu
= (WXHMENU
)CreatePopupMenu();
118 wxLogLastError(wxT("CreatePopupMenu"));
121 // if we have a title, insert it in the beginning of the menu
124 Append(idMenuTitle
, m_title
);
129 // The wxWindow destructor will take care of deleting the submenus.
132 // we should free Windows resources only if Windows doesn't do it for us
133 // which happens if we're attached to a menubar or a submenu of another
135 if ( !IsAttached() && !GetParent() )
137 if ( !::DestroyMenu(GetHmenu()) )
139 wxLogLastError(wxT("DestroyMenu"));
145 WX_CLEAR_ARRAY(m_accels
);
146 #endif // wxUSE_ACCEL
151 // this will take effect during the next call to Append()
155 void wxMenu::Attach(wxMenuBarBase
*menubar
)
157 wxMenuBase::Attach(menubar
);
164 int wxMenu::FindAccel(int id
) const
166 size_t n
, count
= m_accels
.GetCount();
167 for ( n
= 0; n
< count
; n
++ )
169 if ( m_accels
[n
]->m_command
== id
)
176 void wxMenu::UpdateAccel(wxMenuItem
*item
)
178 if ( item
->IsSubMenu() )
180 wxMenu
*submenu
= item
->GetSubMenu();
181 wxMenuItemList::compatibility_iterator node
= submenu
->GetMenuItems().GetFirst();
184 UpdateAccel(node
->GetData());
186 node
= node
->GetNext();
189 else if ( !item
->IsSeparator() )
191 // find the (new) accel for this item
192 wxAcceleratorEntry
*accel
= wxGetAccelFromString(item
->GetText());
194 accel
->m_command
= item
->GetId();
197 int n
= FindAccel(item
->GetId());
198 if ( n
== wxNOT_FOUND
)
200 // no old, add new if any
204 return; // skipping RebuildAccelTable() below
208 // replace old with new or just remove the old one if no new
213 m_accels
.RemoveAt(n
);
218 m_menuBar
->RebuildAccelTable();
221 //else: it is a separator, they can't have accels, nothing to do
224 #endif // wxUSE_ACCEL
226 // append a new item or submenu to the menu
227 bool wxMenu::DoInsertOrAppend(wxMenuItem
*pItem
, size_t pos
)
231 #endif // wxUSE_ACCEL
235 // if "Break" has just been called, insert a menu break before this item
236 // (and don't forget to reset the flag)
238 flags
|= MF_MENUBREAK
;
242 if ( pItem
->IsSeparator() ) {
243 flags
|= MF_SEPARATOR
;
246 // id is the numeric id for normal menu items and HMENU for submenus as
247 // required by ::AppendMenu() API
249 wxMenu
*submenu
= pItem
->GetSubMenu();
250 if ( submenu
!= NULL
) {
251 wxASSERT_MSG( submenu
->GetHMenu(), wxT("invalid submenu") );
253 submenu
->SetParent(this);
255 id
= (UINT
)submenu
->GetHMenu();
265 #if wxUSE_OWNER_DRAWN
266 if ( pItem
->IsOwnerDrawn() ) { // want to get {Measure|Draw}Item messages?
267 // item draws itself, pass pointer to it in data parameter
268 flags
|= MF_OWNERDRAW
;
269 pData
= (LPCTSTR
)pItem
;
274 // menu is just a normal string (passed in data parameter)
277 pData
= (wxChar
*)pItem
->GetText().c_str();
281 if ( pos
== (size_t)-1 )
283 ok
= ::AppendMenu(GetHmenu(), flags
, id
, pData
);
287 ok
= ::InsertMenu(GetHmenu(), pos
, flags
| MF_BYPOSITION
, id
, pData
);
292 wxLogLastError(wxT("Insert or AppendMenu"));
297 // if we just appended the title, highlight it
299 if ( (int)id
== idMenuTitle
)
301 // visually select the menu title
302 SetDefaultMenuItem(GetHmenu(), id
);
306 // if we're already attached to the menubar, we must update it
307 if ( IsAttached() && m_menuBar
->IsAttached() )
309 m_menuBar
->Refresh();
315 void wxMenu::EndRadioGroup()
317 // we're not inside a radio group any longer
318 m_startRadioGroup
= -1;
321 bool wxMenu::DoAppend(wxMenuItem
*item
)
323 wxCHECK_MSG( item
, FALSE
, _T("NULL item in wxMenu::DoAppend") );
327 if ( item
->GetKind() == wxITEM_RADIO
)
329 int count
= GetMenuItemCount();
331 if ( m_startRadioGroup
== -1 )
333 // start a new radio group
334 m_startRadioGroup
= count
;
336 // for now it has just one element
337 item
->SetAsRadioGroupStart();
338 item
->SetRadioGroupEnd(m_startRadioGroup
);
340 // ensure that we have a checked item in the radio group
343 else // extend the current radio group
345 // we need to update its end item
346 item
->SetRadioGroupStart(m_startRadioGroup
);
347 wxMenuItemList::compatibility_iterator node
= GetMenuItems().Item(m_startRadioGroup
);
351 node
->GetData()->SetRadioGroupEnd(count
);
355 wxFAIL_MSG( _T("where is the radio group start item?") );
359 else // not a radio item
364 if ( !wxMenuBase::DoAppend(item
) || !DoInsertOrAppend(item
) )
371 // check the item initially
378 bool wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
380 return wxMenuBase::DoInsert(pos
, item
) && DoInsertOrAppend(item
, pos
);
383 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
385 // we need to find the items position in the child list
387 wxMenuItemList::compatibility_iterator node
= GetMenuItems().GetFirst();
388 for ( pos
= 0; node
; pos
++ )
390 if ( node
->GetData() == item
)
393 node
= node
->GetNext();
396 // DoRemove() (unlike Remove) can only be called for existing item!
397 wxCHECK_MSG( node
, NULL
, wxT("bug in wxMenu::Remove logic") );
400 // remove the corresponding accel from the accel table
401 int n
= FindAccel(item
->GetId());
402 if ( n
!= wxNOT_FOUND
)
406 m_accels
.RemoveAt(n
);
408 //else: this item doesn't have an accel, nothing to do
409 #endif // wxUSE_ACCEL
411 // remove the item from the menu
412 if ( !::RemoveMenu(GetHmenu(), (UINT
)pos
, MF_BYPOSITION
) )
414 wxLogLastError(wxT("RemoveMenu"));
417 if ( IsAttached() && m_menuBar
->IsAttached() )
419 // otherwise, the chane won't be visible
420 m_menuBar
->Refresh();
423 // and from internal data structures
424 return wxMenuBase::DoRemove(item
);
427 // ---------------------------------------------------------------------------
428 // accelerator helpers
429 // ---------------------------------------------------------------------------
433 // create the wxAcceleratorEntries for our accels and put them into provided
434 // array - return the number of accels we have
435 size_t wxMenu::CopyAccels(wxAcceleratorEntry
*accels
) const
437 size_t count
= GetAccelCount();
438 for ( size_t n
= 0; n
< count
; n
++ )
440 *accels
++ = *m_accels
[n
];
446 #endif // wxUSE_ACCEL
448 // ---------------------------------------------------------------------------
450 // ---------------------------------------------------------------------------
452 void wxMenu::SetTitle(const wxString
& label
)
454 bool hasNoTitle
= m_title
.IsEmpty();
457 HMENU hMenu
= GetHmenu();
461 if ( !label
.IsEmpty() )
463 if ( !::InsertMenu(hMenu
, 0u, MF_BYPOSITION
| MF_STRING
,
464 (unsigned)idMenuTitle
, m_title
) ||
465 !::InsertMenu(hMenu
, 1u, MF_BYPOSITION
, (unsigned)-1, NULL
) )
467 wxLogLastError(wxT("InsertMenu"));
473 if ( label
.IsEmpty() )
475 // remove the title and the separator after it
476 if ( !RemoveMenu(hMenu
, 0, MF_BYPOSITION
) ||
477 !RemoveMenu(hMenu
, 0, MF_BYPOSITION
) )
479 wxLogLastError(wxT("RemoveMenu"));
488 info
.cbSize
= sizeof(info
);
489 info
.fMask
= MIIM_TYPE
;
490 info
.fType
= MFT_STRING
;
491 info
.cch
= m_title
.Length();
492 info
.dwTypeData
= (LPTSTR
) m_title
.c_str();
493 if ( !SetMenuItemInfo(hMenu
, 0, TRUE
, & info
) )
495 wxLogLastError(wxT("SetMenuItemInfo"));
498 if ( !ModifyMenu(hMenu
, 0u,
499 MF_BYPOSITION
| MF_STRING
,
500 (unsigned)idMenuTitle
, m_title
) )
502 wxLogLastError(wxT("ModifyMenu"));
509 // put the title string in bold face
510 if ( !m_title
.IsEmpty() )
512 SetDefaultMenuItem(GetHmenu(), (UINT
)idMenuTitle
);
517 // ---------------------------------------------------------------------------
519 // ---------------------------------------------------------------------------
521 bool wxMenu::MSWCommand(WXUINT
WXUNUSED(param
), WXWORD id
)
523 // ignore commands from the menu title
525 // NB: VC++ generates wrong assembler for `if ( id != idMenuTitle )'!!
526 if ( id
!= (WXWORD
)idMenuTitle
)
528 // VZ: previosuly, the command int was set to id too which was quite
529 // useless anyhow (as it could be retrieved using GetId()) and
530 // uncompatible with wxGTK, so now we use the command int instead
531 // to pass the checked status
532 UINT menuState
= ::GetMenuState(GetHmenu(), id
, MF_BYCOMMAND
) ;
533 SendEvent(id
, menuState
& MF_CHECKED
);
539 // ---------------------------------------------------------------------------
541 // ---------------------------------------------------------------------------
543 wxWindow
*wxMenu::GetWindow() const
545 if ( m_invokingWindow
!= NULL
)
546 return m_invokingWindow
;
547 else if ( m_menuBar
!= NULL
)
548 return m_menuBar
->GetFrame();
553 // ---------------------------------------------------------------------------
555 // ---------------------------------------------------------------------------
557 void wxMenuBar::Init()
559 m_eventHandler
= this;
563 wxMenuBar::wxMenuBar()
568 wxMenuBar::wxMenuBar( long WXUNUSED(style
) )
573 wxMenuBar::wxMenuBar(int count
, wxMenu
*menus
[], const wxString titles
[])
577 m_titles
.Alloc(count
);
579 for ( int i
= 0; i
< count
; i
++ )
581 m_menus
.Append(menus
[i
]);
582 m_titles
.Add(titles
[i
]);
584 menus
[i
]->Attach(this);
588 wxMenuBar::~wxMenuBar()
590 // we should free Windows resources only if Windows doesn't do it for us
591 // which happens if we're attached to a frame
592 if (m_hMenu
&& !IsAttached())
594 ::DestroyMenu((HMENU
)m_hMenu
);
595 m_hMenu
= (WXHMENU
)NULL
;
599 // ---------------------------------------------------------------------------
601 // ---------------------------------------------------------------------------
603 void wxMenuBar::Refresh()
605 wxCHECK_RET( IsAttached(), wxT("can't refresh unattached menubar") );
607 DrawMenuBar(GetHwndOf(GetFrame()));
610 WXHMENU
wxMenuBar::Create()
615 m_hMenu
= (WXHMENU
)::CreateMenu();
619 wxLogLastError(wxT("CreateMenu"));
623 size_t count
= GetMenuCount(), i
;
624 wxMenuList::iterator it
;
625 for ( i
= 0, it
= m_menus
.begin(); i
< count
; i
++, it
++ )
627 if ( !::AppendMenu((HMENU
)m_hMenu
, MF_POPUP
| MF_STRING
,
628 (UINT
)(*it
)->GetHMenu(),
631 wxLogLastError(wxT("AppendMenu"));
639 // ---------------------------------------------------------------------------
640 // wxMenuBar functions to work with the top level submenus
641 // ---------------------------------------------------------------------------
643 // NB: we don't support owner drawn top level items for now, if we do these
644 // functions would have to be changed to use wxMenuItem as well
646 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
648 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
650 int flag
= enable
? MF_ENABLED
: MF_GRAYED
;
652 EnableMenuItem((HMENU
)m_hMenu
, pos
, MF_BYPOSITION
| flag
);
657 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
659 wxCHECK_RET( pos
< GetMenuCount(), wxT("invalid menu index") );
661 m_titles
[pos
] = label
;
667 //else: have to modify the existing menu
670 UINT flagsOld
= ::GetMenuState((HMENU
)m_hMenu
, pos
, MF_BYPOSITION
);
671 if ( flagsOld
== 0xFFFFFFFF )
673 wxLogLastError(wxT("GetMenuState"));
678 if ( flagsOld
& MF_POPUP
)
680 // HIBYTE contains the number of items in the submenu in this case
682 id
= (UINT
)::GetSubMenu((HMENU
)m_hMenu
, pos
);
692 info
.cbSize
= sizeof(info
);
693 info
.fMask
= MIIM_TYPE
;
694 info
.fType
= MFT_STRING
;
695 info
.cch
= label
.Length();
696 info
.dwTypeData
= (LPTSTR
) label
.c_str();
697 if ( !SetMenuItemInfo(GetHmenu(), id
, TRUE
, & info
) )
699 wxLogLastError(wxT("SetMenuItemInfo"));
703 if ( ::ModifyMenu(GetHmenu(), pos
, MF_BYPOSITION
| MF_STRING
| flagsOld
,
704 id
, label
) == (int)0xFFFFFFFF )
706 wxLogLastError(wxT("ModifyMenu"));
713 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
715 wxCHECK_MSG( pos
< GetMenuCount(), wxEmptyString
,
716 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
718 return m_titles
[pos
];
721 // ---------------------------------------------------------------------------
722 // wxMenuBar construction
723 // ---------------------------------------------------------------------------
725 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
727 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
731 m_titles
[pos
] = title
;
735 // can't use ModifyMenu() because it deletes the submenu it replaces
736 if ( !::RemoveMenu(GetHmenu(), (UINT
)pos
, MF_BYPOSITION
) )
738 wxLogLastError(wxT("RemoveMenu"));
741 if ( !::InsertMenu(GetHmenu(), (UINT
)pos
,
742 MF_BYPOSITION
| MF_POPUP
| MF_STRING
,
743 (UINT
)GetHmenuOf(menu
), title
) )
745 wxLogLastError(wxT("InsertMenu"));
749 if ( menuOld
->HasAccels() || menu
->HasAccels() )
751 // need to rebuild accell table
754 #endif // wxUSE_ACCEL
762 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
764 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
767 m_titles
.Insert(title
, pos
);
771 if ( !::InsertMenu(GetHmenu(), pos
,
772 MF_BYPOSITION
| MF_POPUP
| MF_STRING
,
773 (UINT
)GetHmenuOf(menu
), title
) )
775 wxLogLastError(wxT("InsertMenu"));
779 if ( menu
->HasAccels() )
781 // need to rebuild accell table
784 #endif // wxUSE_ACCEL
792 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
794 WXHMENU submenu
= menu
? menu
->GetHMenu() : 0;
795 wxCHECK_MSG( submenu
, FALSE
, wxT("can't append invalid menu to menubar") );
797 if ( !wxMenuBarBase::Append(menu
, title
) )
804 if ( !::AppendMenu(GetHmenu(), MF_POPUP
| MF_STRING
,
805 (UINT
)submenu
, title
) )
807 wxLogLastError(wxT("AppendMenu"));
811 if ( menu
->HasAccels() )
813 // need to rebuild accell table
816 #endif // wxUSE_ACCEL
824 wxMenu
*wxMenuBar::Remove(size_t pos
)
826 wxMenu
*menu
= wxMenuBarBase::Remove(pos
);
832 if ( !::RemoveMenu(GetHmenu(), (UINT
)pos
, MF_BYPOSITION
) )
834 wxLogLastError(wxT("RemoveMenu"));
838 if ( menu
->HasAccels() )
840 // need to rebuild accell table
843 #endif // wxUSE_ACCEL
848 m_titles
.RemoveAt(pos
);
855 void wxMenuBar::RebuildAccelTable()
857 // merge the accelerators of all menus into one accel table
858 size_t nAccelCount
= 0;
859 size_t i
, count
= GetMenuCount();
860 wxMenuList::iterator it
;
861 for ( i
= 0, it
= m_menus
.begin(); i
< count
; i
++, it
++ )
863 nAccelCount
+= (*it
)->GetAccelCount();
868 wxAcceleratorEntry
*accelEntries
= new wxAcceleratorEntry
[nAccelCount
];
871 for ( i
= 0, it
= m_menus
.begin(); i
< count
; i
++, it
++ )
873 nAccelCount
+= (*it
)->CopyAccels(&accelEntries
[nAccelCount
]);
876 m_accelTable
= wxAcceleratorTable(nAccelCount
, accelEntries
);
878 delete [] accelEntries
;
882 #endif // wxUSE_ACCEL
884 void wxMenuBar::Attach(wxFrame
*frame
)
886 wxMenuBarBase::Attach(frame
);
890 #endif // wxUSE_ACCEL
893 void wxMenuBar::Detach()
895 wxMenuBarBase::Detach();
898 #endif // wxUSE_MENUS