]> git.saurik.com Git - wxWidgets.git/blob - src/msw/menu.cpp
93e92056ef02f1f94f3d2b4e323ac341e7eee8df
[wxWidgets.git] / src / msw / menu.cpp
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
54 extern wxMenu *wxCurrentPopupMenu;
55
56 // ----------------------------------------------------------------------------
57 // constants
58 // ----------------------------------------------------------------------------
59
60 // the (popup) menu title has this special id
61 static const int idMenuTitle = -2;
62
63 // ----------------------------------------------------------------------------
64 // macros
65 // ----------------------------------------------------------------------------
66
67 IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
68 IMPLEMENT_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)
79 void 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.
99 wxMenu::~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
118 void 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
126 int 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
138 void 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
189 bool 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
287 bool wxMenu::DoAppend(wxMenuItem *item)
288 {
289 return wxMenuBase::DoAppend(item) && DoInsertOrAppend(item);
290 }
291
292 bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
293 {
294 return wxMenuBase::DoInsert(pos, item) && DoInsertOrAppend(item, pos);
295 }
296
297 wxMenuItem *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
349 size_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
366 void 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
429 bool 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
450 wxWindow *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
464 void wxMenuBar::Init()
465 {
466 m_eventHandler = this;
467 m_hMenu = 0;
468 }
469
470 wxMenuBar::wxMenuBar()
471 {
472 Init();
473 }
474
475 wxMenuBar::wxMenuBar( long WXUNUSED(style) )
476 {
477 Init();
478 }
479
480 wxMenuBar::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
495 wxMenuBar::~wxMenuBar()
496 {
497 }
498
499 // ---------------------------------------------------------------------------
500 // wxMenuBar helpers
501 // ---------------------------------------------------------------------------
502
503 void wxMenuBar::Refresh()
504 {
505 wxCHECK_RET( IsAttached(), wxT("can't refresh unattached menubar") );
506
507 DrawMenuBar(GetHwndOf(GetFrame()));
508 }
509
510 WXHMENU 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
545 void 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
556 void 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
597 wxString 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
609 wxMenu *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
645 bool 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 if ( IsAttached() )
653 {
654 if ( !::InsertMenu(GetHmenu(), pos,
655 MF_BYPOSITION | MF_POPUP | MF_STRING,
656 (UINT)GetHmenuOf(menu), title) )
657 {
658 wxLogLastError(wxT("InsertMenu"));
659 }
660
661 #if wxUSE_ACCEL
662 if ( menu->HasAccels() )
663 {
664 // need to rebuild accell table
665 RebuildAccelTable();
666 }
667 #endif // wxUSE_ACCEL
668
669 Refresh();
670 }
671
672 return TRUE;
673 }
674
675 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
676 {
677 WXHMENU submenu = menu ? menu->GetHMenu() : 0;
678 wxCHECK_MSG( submenu, FALSE, wxT("can't append invalid menu to menubar") );
679
680 if ( !wxMenuBarBase::Append(menu, title) )
681 return FALSE;
682
683 // Already done in Append above
684 //menu->Attach(this);
685
686 m_titles.Add(title);
687
688 if ( IsAttached() )
689 {
690 if ( !::AppendMenu(GetHmenu(), MF_POPUP | MF_STRING,
691 (UINT)submenu, title) )
692 {
693 wxLogLastError(wxT("AppendMenu"));
694 }
695
696 #if wxUSE_ACCEL
697 if ( menu->HasAccels() )
698 {
699 // need to rebuild accell table
700 RebuildAccelTable();
701 }
702 #endif // wxUSE_ACCEL
703
704 Refresh();
705 }
706
707 return TRUE;
708 }
709
710 wxMenu *wxMenuBar::Remove(size_t pos)
711 {
712 wxMenu *menu = wxMenuBarBase::Remove(pos);
713 if ( !menu )
714 return NULL;
715
716 if ( IsAttached() )
717 {
718 if ( !::RemoveMenu(GetHmenu(), (UINT)pos, MF_BYPOSITION) )
719 {
720 wxLogLastError(wxT("RemoveMenu"));
721 }
722
723 #if wxUSE_ACCEL
724 if ( menu->HasAccels() )
725 {
726 // need to rebuild accell table
727 RebuildAccelTable();
728 }
729 #endif // wxUSE_ACCEL
730
731 Refresh();
732 }
733
734 m_titles.Remove(pos);
735
736 return menu;
737 }
738
739 #if wxUSE_ACCEL
740
741 void wxMenuBar::RebuildAccelTable()
742 {
743 // merge the accelerators of all menus into one accel table
744 size_t nAccelCount = 0;
745 size_t i, count = GetMenuCount();
746 for ( i = 0; i < count; i++ )
747 {
748 nAccelCount += m_menus[i]->GetAccelCount();
749 }
750
751 if ( nAccelCount )
752 {
753 wxAcceleratorEntry *accelEntries = new wxAcceleratorEntry[nAccelCount];
754
755 nAccelCount = 0;
756 for ( i = 0; i < count; i++ )
757 {
758 nAccelCount += m_menus[i]->CopyAccels(&accelEntries[nAccelCount]);
759 }
760
761 m_accelTable = wxAcceleratorTable(nAccelCount, accelEntries);
762
763 delete [] accelEntries;
764 }
765 }
766
767 #endif // wxUSE_ACCEL
768
769 void wxMenuBar::Attach(wxFrame *frame)
770 {
771 wxMenuBarBase::Attach(frame);
772
773 m_menuBarFrame = frame;
774
775 #if wxUSE_ACCEL
776 RebuildAccelTable();
777 #endif // wxUSE_ACCEL
778 }
779
780 void wxMenuBar::Detach()
781 {
782 m_hMenu = (WXHMENU)NULL;
783
784 wxMenuBarBase::Detach();
785 }
786
787 #endif // wxUSE_MENUS