]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/msw/menu.cpp
popupcmn is needed by wxGTK too, why was it excluded?
[wxWidgets.git] / src / msw / menu.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: menu.cpp
3// Purpose: wxMenu, wxMenuBar, wxMenuItem
4// Author: Julian Smart
5// Modified by: Vadim Zeitlin
6// Created: 04/01/98
7// RCS-ID: $Id$
8// Copyright: (c) Julian Smart and Markus Holzem
9// Licence: wxWindows license
10/////////////////////////////////////////////////////////////////////////////
11
12// ===========================================================================
13// declarations
14// ===========================================================================
15
16// ---------------------------------------------------------------------------
17// headers
18// ---------------------------------------------------------------------------
19
20#ifdef __GNUG__
21 #pragma implementation "menu.h"
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
28 #pragma hdrstop
29#endif
30
31#if wxUSE_MENUS
32
33#ifndef WX_PRECOMP
34 #include "wx/frame.h"
35 #include "wx/menu.h"
36 #include "wx/utils.h"
37 #include "wx/intl.h"
38 #include "wx/log.h"
39#endif
40
41#if wxUSE_OWNER_DRAWN
42 #include "wx/ownerdrw.h"
43#endif
44
45#include "wx/msw/private.h"
46
47// other standard headers
48#include <string.h>
49
50// ----------------------------------------------------------------------------
51// global variables
52// ----------------------------------------------------------------------------
53
54extern wxMenu *wxCurrentPopupMenu;
55
56// ----------------------------------------------------------------------------
57// constants
58// ----------------------------------------------------------------------------
59
60// the (popup) menu title has this special id
61static const int idMenuTitle = -2;
62
63// ----------------------------------------------------------------------------
64// macros
65// ----------------------------------------------------------------------------
66
67IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
68IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow)
69
70// ============================================================================
71// implementation
72// ============================================================================
73
74// ---------------------------------------------------------------------------
75// wxMenu construction, adding and removing menu items
76// ---------------------------------------------------------------------------
77
78// Construct a menu with optional title (then use append)
79void wxMenu::Init()
80{
81 m_doBreak = FALSE;
82
83 // create the menu
84 m_hMenu = (WXHMENU)CreatePopupMenu();
85 if ( !m_hMenu )
86 {
87 wxLogLastError(wxT("CreatePopupMenu"));
88 }
89
90 // if we have a title, insert it in the beginning of the menu
91 if ( !!m_title )
92 {
93 Append(idMenuTitle, m_title);
94 AppendSeparator();
95 }
96}
97
98// The wxWindow destructor will take care of deleting the submenus.
99wxMenu::~wxMenu()
100{
101 // we should free Windows resources only if Windows doesn't do it for us
102 // which happens if we're attached to a menubar or a submenu of another
103 // menu
104 if ( !IsAttached() && !GetParent() )
105 {
106 if ( !::DestroyMenu(GetHmenu()) )
107 {
108 wxLogLastError(wxT("DestroyMenu"));
109 }
110 }
111
112#if wxUSE_ACCEL
113 // delete accels
114 WX_CLEAR_ARRAY(m_accels);
115#endif // wxUSE_ACCEL
116}
117
118void wxMenu::Break()
119{
120 // this will take effect during the next call to Append()
121 m_doBreak = TRUE;
122}
123
124#if wxUSE_ACCEL
125
126int wxMenu::FindAccel(int id) const
127{
128 size_t n, count = m_accels.GetCount();
129 for ( n = 0; n < count; n++ )
130 {
131 if ( m_accels[n]->m_command == id )
132 return n;
133 }
134
135 return wxNOT_FOUND;
136}
137
138void wxMenu::UpdateAccel(wxMenuItem *item)
139{
140 if ( item->IsSubMenu() )
141 {
142 wxMenu *submenu = item->GetSubMenu();
143 wxMenuItemList::Node *node = submenu->GetMenuItems().GetFirst();
144 while ( node )
145 {
146 UpdateAccel(node->GetData());
147
148 node = node->GetNext();
149 }
150 }
151 else if ( !item->IsSeparator() )
152 {
153 // find the (new) accel for this item
154 wxAcceleratorEntry *accel = wxGetAccelFromString(item->GetText());
155 if ( accel )
156 accel->m_command = item->GetId();
157
158 // find the old one
159 int n = FindAccel(item->GetId());
160 if ( n == wxNOT_FOUND )
161 {
162 // no old, add new if any
163 if ( accel )
164 m_accels.Add(accel);
165 else
166 return; // skipping RebuildAccelTable() below
167 }
168 else
169 {
170 // replace old with new or just remove the old one if no new
171 delete m_accels[n];
172 if ( accel )
173 m_accels[n] = accel;
174 else
175 m_accels.RemoveAt(n);
176 }
177
178 if ( IsAttached() )
179 {
180 m_menuBar->RebuildAccelTable();
181 }
182 }
183 //else: it is a separator, they can't have accels, nothing to do
184}
185
186#endif // wxUSE_ACCEL
187
188// append a new item or submenu to the menu
189bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos)
190{
191#if wxUSE_ACCEL
192 UpdateAccel(pItem);
193#endif // wxUSE_ACCEL
194
195 UINT flags = 0;
196
197 // if "Break" has just been called, insert a menu break before this item
198 // (and don't forget to reset the flag)
199 if ( m_doBreak ) {
200 flags |= MF_MENUBREAK;
201 m_doBreak = FALSE;
202 }
203
204 if ( pItem->IsSeparator() ) {
205 flags |= MF_SEPARATOR;
206 }
207
208 // id is the numeric id for normal menu items and HMENU for submenus as
209 // required by ::AppendMenu() API
210 UINT id;
211 wxMenu *submenu = pItem->GetSubMenu();
212 if ( submenu != NULL ) {
213 wxASSERT_MSG( submenu->GetHMenu(), wxT("invalid submenu") );
214
215 submenu->SetParent(this);
216
217 id = (UINT)submenu->GetHMenu();
218
219 flags |= MF_POPUP;
220 }
221 else {
222 id = pItem->GetId();
223 }
224
225 LPCTSTR pData;
226
227#if wxUSE_OWNER_DRAWN
228 if ( pItem->IsOwnerDrawn() ) { // want to get {Measure|Draw}Item messages?
229 // item draws itself, pass pointer to it in data parameter
230 flags |= MF_OWNERDRAW;
231 pData = (LPCTSTR)pItem;
232 }
233 else
234#endif
235 {
236 // menu is just a normal string (passed in data parameter)
237 flags |= MF_STRING;
238
239 pData = (wxChar*)pItem->GetText().c_str();
240 }
241
242 BOOL ok;
243 if ( pos == (size_t)-1 )
244 {
245 ok = ::AppendMenu(GetHmenu(), flags, id, pData);
246 }
247 else
248 {
249 ok = ::InsertMenu(GetHmenu(), pos, flags | MF_BYPOSITION, id, pData);
250 }
251
252 if ( !ok )
253 {
254 wxLogLastError(wxT("Insert or AppendMenu"));
255
256 return FALSE;
257 }
258 else
259 {
260 // if we just appended the title, highlight it
261#ifdef __WIN32__
262 if ( (int)id == idMenuTitle )
263 {
264 // visually select the menu title
265 MENUITEMINFO mii;
266 mii.cbSize = sizeof(mii);
267 mii.fMask = MIIM_STATE;
268 mii.fState = MFS_DEFAULT;
269
270 if ( !SetMenuItemInfo(GetHmenu(), (unsigned)id, FALSE, &mii) )
271 {
272 wxLogLastError(wxT("SetMenuItemInfo"));
273 }
274 }
275#endif // __WIN32__
276
277 // if we're already attached to the menubar, we must update it
278 if ( IsAttached() && m_menuBar->IsAttached() )
279 {
280 m_menuBar->Refresh();
281 }
282
283 return TRUE;
284 }
285}
286
287bool wxMenu::DoAppend(wxMenuItem *item)
288{
289 return wxMenuBase::DoAppend(item) && DoInsertOrAppend(item);
290}
291
292bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
293{
294 return wxMenuBase::DoInsert(pos, item) && DoInsertOrAppend(item, pos);
295}
296
297wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
298{
299 // we need to find the items position in the child list
300 size_t pos;
301 wxMenuItemList::Node *node = GetMenuItems().GetFirst();
302 for ( pos = 0; node; pos++ )
303 {
304 if ( node->GetData() == item )
305 break;
306
307 node = node->GetNext();
308 }
309
310 // DoRemove() (unlike Remove) can only be called for existing item!
311 wxCHECK_MSG( node, NULL, wxT("bug in wxMenu::Remove logic") );
312
313#if wxUSE_ACCEL
314 // remove the corresponding accel from the accel table
315 int n = FindAccel(item->GetId());
316 if ( n != wxNOT_FOUND )
317 {
318 delete m_accels[n];
319
320 m_accels.RemoveAt(n);
321 }
322 //else: this item doesn't have an accel, nothing to do
323#endif // wxUSE_ACCEL
324
325 // remove the item from the menu
326 if ( !::RemoveMenu(GetHmenu(), (UINT)pos, MF_BYPOSITION) )
327 {
328 wxLogLastError(wxT("RemoveMenu"));
329 }
330
331 if ( IsAttached() && m_menuBar->IsAttached() )
332 {
333 // otherwise, the chane won't be visible
334 m_menuBar->Refresh();
335 }
336
337 // and from internal data structures
338 return wxMenuBase::DoRemove(item);
339}
340
341// ---------------------------------------------------------------------------
342// accelerator helpers
343// ---------------------------------------------------------------------------
344
345#if wxUSE_ACCEL
346
347// create the wxAcceleratorEntries for our accels and put them into provided
348// array - return the number of accels we have
349size_t wxMenu::CopyAccels(wxAcceleratorEntry *accels) const
350{
351 size_t count = GetAccelCount();
352 for ( size_t n = 0; n < count; n++ )
353 {
354 *accels++ = *m_accels[n];
355 }
356
357 return count;
358}
359
360#endif // wxUSE_ACCEL
361
362// ---------------------------------------------------------------------------
363// set wxMenu title
364// ---------------------------------------------------------------------------
365
366void wxMenu::SetTitle(const wxString& label)
367{
368 bool hasNoTitle = m_title.IsEmpty();
369 m_title = label;
370
371 HMENU hMenu = GetHmenu();
372
373 if ( hasNoTitle )
374 {
375 if ( !label.IsEmpty() )
376 {
377 if ( !::InsertMenu(hMenu, 0u, MF_BYPOSITION | MF_STRING,
378 (unsigned)idMenuTitle, m_title) ||
379 !::InsertMenu(hMenu, 1u, MF_BYPOSITION, (unsigned)-1, NULL) )
380 {
381 wxLogLastError(wxT("InsertMenu"));
382 }
383 }
384 }
385 else
386 {
387 if ( label.IsEmpty() )
388 {
389 // remove the title and the separator after it
390 if ( !RemoveMenu(hMenu, 0, MF_BYPOSITION) ||
391 !RemoveMenu(hMenu, 0, MF_BYPOSITION) )
392 {
393 wxLogLastError(wxT("RemoveMenu"));
394 }
395 }
396 else
397 {
398 // modify the title
399 if ( !ModifyMenu(hMenu, 0u,
400 MF_BYPOSITION | MF_STRING,
401 (unsigned)idMenuTitle, m_title) )
402 {
403 wxLogLastError(wxT("ModifyMenu"));
404 }
405 }
406 }
407
408#ifdef __WIN32__
409 // put the title string in bold face
410 if ( !m_title.IsEmpty() )
411 {
412 MENUITEMINFO mii;
413 mii.cbSize = sizeof(mii);
414 mii.fMask = MIIM_STATE;
415 mii.fState = MFS_DEFAULT;
416
417 if ( !SetMenuItemInfo(hMenu, (unsigned)idMenuTitle, FALSE, &mii) )
418 {
419 wxLogLastError(wxT("SetMenuItemInfo"));
420 }
421 }
422#endif // Win32
423}
424
425// ---------------------------------------------------------------------------
426// event processing
427// ---------------------------------------------------------------------------
428
429bool wxMenu::MSWCommand(WXUINT WXUNUSED(param), WXWORD id)
430{
431 // ignore commands from the menu title
432
433 // NB: VC++ generates wrong assembler for `if ( id != idMenuTitle )'!!
434 if ( id != (WXWORD)idMenuTitle )
435 {
436 // VZ: previosuly, the command int was set to id too which was quite
437 // useless anyhow (as it could be retrieved using GetId()) and
438 // uncompatible with wxGTK, so now we use the command int instead
439 // to pass the checked status
440 SendEvent(id, ::GetMenuState(GetHmenu(), id, MF_BYCOMMAND) & MF_CHECKED);
441 }
442
443 return TRUE;
444}
445
446// ---------------------------------------------------------------------------
447// other
448// ---------------------------------------------------------------------------
449
450wxWindow *wxMenu::GetWindow() const
451{
452 if ( m_invokingWindow != NULL )
453 return m_invokingWindow;
454 else if ( m_menuBar != NULL)
455 return m_menuBar->GetFrame();
456
457 return NULL;
458}
459
460// ---------------------------------------------------------------------------
461// Menu Bar
462// ---------------------------------------------------------------------------
463
464void wxMenuBar::Init()
465{
466 m_eventHandler = this;
467 m_hMenu = 0;
468}
469
470wxMenuBar::wxMenuBar()
471{
472 Init();
473}
474
475wxMenuBar::wxMenuBar( long WXUNUSED(style) )
476{
477 Init();
478}
479
480wxMenuBar::wxMenuBar(int count, wxMenu *menus[], const wxString titles[])
481{
482 Init();
483
484 m_titles.Alloc(count);
485
486 for ( int i = 0; i < count; i++ )
487 {
488 m_menus.Append(menus[i]);
489 m_titles.Add(titles[i]);
490
491 menus[i]->Attach(this);
492 }
493}
494
495wxMenuBar::~wxMenuBar()
496{
497}
498
499// ---------------------------------------------------------------------------
500// wxMenuBar helpers
501// ---------------------------------------------------------------------------
502
503void wxMenuBar::Refresh()
504{
505 wxCHECK_RET( IsAttached(), wxT("can't refresh unattached menubar") );
506
507 DrawMenuBar(GetHwndOf(GetFrame()));
508}
509
510WXHMENU wxMenuBar::Create()
511{
512 if ( m_hMenu != 0 )
513 return m_hMenu;
514
515 m_hMenu = (WXHMENU)::CreateMenu();
516
517 if ( !m_hMenu )
518 {
519 wxLogLastError(wxT("CreateMenu"));
520 }
521 else
522 {
523 size_t count = GetMenuCount();
524 for ( size_t i = 0; i < count; i++ )
525 {
526 if ( !::AppendMenu((HMENU)m_hMenu, MF_POPUP | MF_STRING,
527 (UINT)m_menus[i]->GetHMenu(),
528 m_titles[i]) )
529 {
530 wxLogLastError(wxT("AppendMenu"));
531 }
532 }
533 }
534
535 return m_hMenu;
536}
537
538// ---------------------------------------------------------------------------
539// wxMenuBar functions to work with the top level submenus
540// ---------------------------------------------------------------------------
541
542// NB: we don't support owner drawn top level items for now, if we do these
543// functions would have to be changed to use wxMenuItem as well
544
545void wxMenuBar::EnableTop(size_t pos, bool enable)
546{
547 wxCHECK_RET( IsAttached(), wxT("doesn't work with unattached menubars") );
548
549 int flag = enable ? MF_ENABLED : MF_GRAYED;
550
551 EnableMenuItem((HMENU)m_hMenu, pos, MF_BYPOSITION | flag);
552
553 Refresh();
554}
555
556void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
557{
558 wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
559
560 m_titles[pos] = label;
561
562 if ( !IsAttached() )
563 {
564 return;
565 }
566 //else: have to modify the existing menu
567
568 UINT id;
569 UINT flagsOld = ::GetMenuState((HMENU)m_hMenu, pos, MF_BYPOSITION);
570 if ( flagsOld == 0xFFFFFFFF )
571 {
572 wxLogLastError(wxT("GetMenuState"));
573
574 return;
575 }
576
577 if ( flagsOld & MF_POPUP )
578 {
579 // HIBYTE contains the number of items in the submenu in this case
580 flagsOld &= 0xff;
581 id = (UINT)::GetSubMenu((HMENU)m_hMenu, pos);
582 }
583 else
584 {
585 id = pos;
586 }
587
588 if ( ::ModifyMenu(GetHmenu(), pos, MF_BYPOSITION | MF_STRING | flagsOld,
589 id, label) == (int)0xFFFFFFFF )
590 {
591 wxLogLastError(wxT("ModifyMenu"));
592 }
593
594 Refresh();
595}
596
597wxString wxMenuBar::GetLabelTop(size_t pos) const
598{
599 wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
600 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
601
602 return m_titles[pos];
603}
604
605// ---------------------------------------------------------------------------
606// wxMenuBar construction
607// ---------------------------------------------------------------------------
608
609wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
610{
611 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
612 if ( !menuOld )
613 return FALSE;
614 m_titles[pos] = title;
615
616 if ( IsAttached() )
617 {
618 // can't use ModifyMenu() because it deletes the submenu it replaces
619 if ( !::RemoveMenu(GetHmenu(), (UINT)pos, MF_BYPOSITION) )
620 {
621 wxLogLastError(wxT("RemoveMenu"));
622 }
623
624 if ( !::InsertMenu(GetHmenu(), (UINT)pos,
625 MF_BYPOSITION | MF_POPUP | MF_STRING,
626 (UINT)GetHmenuOf(menu), title) )
627 {
628 wxLogLastError(wxT("InsertMenu"));
629 }
630
631#if wxUSE_ACCEL
632 if ( menuOld->HasAccels() || menu->HasAccels() )
633 {
634 // need to rebuild accell table
635 RebuildAccelTable();
636 }
637#endif // wxUSE_ACCEL
638
639 Refresh();
640 }
641
642 return menuOld;
643}
644
645bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
646{
647 if ( !wxMenuBarBase::Insert(pos, menu, title) )
648 return FALSE;
649
650 m_titles.Insert(title, pos);
651
652 menu->Attach(this);
653
654 if ( IsAttached() )
655 {
656 if ( !::InsertMenu(GetHmenu(), pos,
657 MF_BYPOSITION | MF_POPUP | MF_STRING,
658 (UINT)GetHmenuOf(menu), title) )
659 {
660 wxLogLastError(wxT("InsertMenu"));
661 }
662
663#if wxUSE_ACCEL
664 if ( menu->HasAccels() )
665 {
666 // need to rebuild accell table
667 RebuildAccelTable();
668 }
669#endif // wxUSE_ACCEL
670
671 Refresh();
672 }
673
674 return TRUE;
675}
676
677bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
678{
679 WXHMENU submenu = menu ? menu->GetHMenu() : 0;
680 wxCHECK_MSG( submenu, FALSE, wxT("can't append invalid menu to menubar") );
681
682 if ( !wxMenuBarBase::Append(menu, title) )
683 return FALSE;
684
685 // Already done in Append above
686 //menu->Attach(this);
687
688 m_titles.Add(title);
689
690 if ( IsAttached() )
691 {
692 if ( !::AppendMenu(GetHmenu(), MF_POPUP | MF_STRING,
693 (UINT)submenu, title) )
694 {
695 wxLogLastError(wxT("AppendMenu"));
696 }
697
698#if wxUSE_ACCEL
699 if ( menu->HasAccels() )
700 {
701 // need to rebuild accell table
702 RebuildAccelTable();
703 }
704#endif // wxUSE_ACCEL
705
706 Refresh();
707 }
708
709 return TRUE;
710}
711
712wxMenu *wxMenuBar::Remove(size_t pos)
713{
714 wxMenu *menu = wxMenuBarBase::Remove(pos);
715 if ( !menu )
716 return NULL;
717
718 if ( IsAttached() )
719 {
720 if ( !::RemoveMenu(GetHmenu(), (UINT)pos, MF_BYPOSITION) )
721 {
722 wxLogLastError(wxT("RemoveMenu"));
723 }
724
725 menu->Detach();
726
727#if wxUSE_ACCEL
728 if ( menu->HasAccels() )
729 {
730 // need to rebuild accell table
731 RebuildAccelTable();
732 }
733#endif // wxUSE_ACCEL
734
735 Refresh();
736 }
737
738 m_titles.Remove(pos);
739
740 return menu;
741}
742
743#if wxUSE_ACCEL
744
745void wxMenuBar::RebuildAccelTable()
746{
747 // merge the accelerators of all menus into one accel table
748 size_t nAccelCount = 0;
749 size_t i, count = GetMenuCount();
750 for ( i = 0; i < count; i++ )
751 {
752 nAccelCount += m_menus[i]->GetAccelCount();
753 }
754
755 if ( nAccelCount )
756 {
757 wxAcceleratorEntry *accelEntries = new wxAcceleratorEntry[nAccelCount];
758
759 nAccelCount = 0;
760 for ( i = 0; i < count; i++ )
761 {
762 nAccelCount += m_menus[i]->CopyAccels(&accelEntries[nAccelCount]);
763 }
764
765 m_accelTable = wxAcceleratorTable(nAccelCount, accelEntries);
766
767 delete [] accelEntries;
768 }
769}
770
771#endif // wxUSE_ACCEL
772
773void wxMenuBar::Attach(wxFrame *frame)
774{
775 wxMenuBarBase::Attach(frame);
776
777 m_menuBarFrame = frame;
778
779#if wxUSE_ACCEL
780 RebuildAccelTable();
781#endif // wxUSE_ACCEL
782}
783
784void wxMenuBar::Detach()
785{
786 m_hMenu = (WXHMENU)NULL;
787
788 wxMenuBarBase::Detach();
789}
790
791#endif // wxUSE_MENUS