]> git.saurik.com Git - wxWidgets.git/blob - src/univ/menu.cpp
files.lst files are no longer used
[wxWidgets.git] / src / univ / menu.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/menu.cpp
3 // Purpose: wxMenuItem, wxMenu and wxMenuBar implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 25.08.00
7 // RCS-ID: $Id$
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "univmenuitem.h"
22 #pragma implementation "univmenu.h"
23 #endif
24
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/dynarray.h"
33 #include "wx/control.h" // for FindAccelIndex()
34 #include "wx/menu.h"
35 #include "wx/settings.h"
36 #include "wx/accel.h"
37 #include "wx/log.h"
38 #endif // WX_PRECOMP
39
40 #if wxUSE_MENUS
41
42 #include "wx/popupwin.h"
43 #include "wx/evtloop.h"
44 #include "wx/dcclient.h"
45 #include "wx/frame.h"
46
47 #include "wx/univ/renderer.h"
48
49 #ifdef __WXMSW__
50 #include "wx/msw/private.h"
51 #endif // __WXMSW__
52
53 // ----------------------------------------------------------------------------
54 // wxMenuInfo contains all extra information about top level menus we need
55 // ----------------------------------------------------------------------------
56
57 class WXDLLEXPORT wxMenuInfo
58 {
59 public:
60 // ctor
61 wxMenuInfo(const wxString& text)
62 {
63 SetLabel(text);
64 SetEnabled();
65 }
66
67 // modifiers
68
69 void SetLabel(const wxString& text)
70 {
71 // remember the accel char (may be -1 if none)
72 m_indexAccel = wxControl::FindAccelIndex(text, &m_label);
73
74 // calculate the width later, after the menu bar is created
75 m_width = 0;
76 }
77
78 void SetEnabled(bool enabled = TRUE) { m_isEnabled = enabled; }
79
80 // accessors
81
82 const wxString& GetLabel() const { return m_label; }
83 bool IsEnabled() const { return m_isEnabled; }
84 wxCoord GetWidth(wxMenuBar *menubar) const
85 {
86 if ( !m_width )
87 {
88 wxConstCast(this, wxMenuInfo)->CalcWidth(menubar);
89 }
90
91 return m_width;
92 }
93
94 int GetAccelIndex() const { return m_indexAccel; }
95
96 private:
97 void CalcWidth(wxMenuBar *menubar)
98 {
99 wxSize size;
100 wxClientDC dc(menubar);
101 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
102 dc.GetTextExtent(m_label, &size.x, &size.y);
103
104 // adjust for the renderer we use and store the width
105 m_width = menubar->GetRenderer()->GetMenuBarItemSize(size).x;
106 }
107
108 wxString m_label;
109 wxCoord m_width;
110 int m_indexAccel;
111 bool m_isEnabled;
112 };
113
114 #include "wx/arrimpl.cpp"
115
116 WX_DEFINE_OBJARRAY(wxMenuInfoArray);
117
118 // ----------------------------------------------------------------------------
119 // wxPopupMenuWindow: a popup window showing a menu
120 // ----------------------------------------------------------------------------
121
122 class wxPopupMenuWindow : public wxPopupTransientWindow
123 {
124 public:
125 wxPopupMenuWindow(wxWindow *parent, wxMenu *menu);
126
127 ~wxPopupMenuWindow();
128
129 // override the base class version to select the first item initially
130 virtual void Popup(wxWindow *focus = NULL);
131
132 // override the base class version to dismiss any open submenus
133 virtual void Dismiss();
134
135 // notify the menu when the window disappears from screen
136 virtual void OnDismiss();
137
138 // called when a submenu is dismissed
139 void OnSubmenuDismiss() { m_hasOpenSubMenu = FALSE; }
140
141 // get the currently selected item (may be NULL)
142 wxMenuItem *GetCurrentItem() const
143 {
144 return m_nodeCurrent ? m_nodeCurrent->GetData() : NULL;
145 }
146
147 // find the menu item at given position
148 wxMenuItemList::Node *GetMenuItemFromPoint(const wxPoint& pt) const;
149
150 // refresh the given item
151 void RefreshItem(wxMenuItem *item);
152
153 // preselect the first item
154 void SelectFirst() { SetCurrent(m_menu->GetMenuItems().GetFirst()); }
155
156 // process the key event, return TRUE if done
157 bool ProcessKeyDown(int key);
158
159 // process mouse move event
160 void ProcessMouseMove(const wxPoint& pt);
161
162 // don't dismiss the popup window if the parent menu was clicked
163 virtual bool ProcessLeftDown(wxMouseEvent& event);
164
165 protected:
166 // how did we perform this operation?
167 enum InputMethod
168 {
169 WithKeyboard,
170 WithMouse
171 };
172
173 // draw the menu inside this window
174 virtual void DoDraw(wxControlRenderer *renderer);
175
176 // event handlers
177 void OnLeftUp(wxMouseEvent& event);
178 void OnMouseMove(wxMouseEvent& event);
179 void OnMouseLeave(wxMouseEvent& event);
180 void OnKeyDown(wxKeyEvent& event);
181
182 // reset the current item and node
183 void ResetCurrent();
184
185 // set the current node and item withotu refreshing anything
186 void SetCurrent(wxMenuItemList::Node *node);
187
188 // change the current item refreshing the old and new items
189 void ChangeCurrent(wxMenuItemList::Node *node);
190
191 // activate item, i.e. call either ClickItem() or OpenSubmenu() depending
192 // on what it is, return TRUE if something was done (i.e. it's not a
193 // separator...)
194 bool ActivateItem(wxMenuItem *item, InputMethod how = WithKeyboard);
195
196 // send the event about the item click
197 void ClickItem(wxMenuItem *item);
198
199 // show the submenu for this item
200 void OpenSubmenu(wxMenuItem *item, InputMethod how = WithKeyboard);
201
202 // can this tiem be opened?
203 bool CanOpen(wxMenuItem *item)
204 {
205 return item && item->IsEnabled() && item->IsSubMenu();
206 }
207
208 // dismiss the menu and all parent menus too
209 void DismissAndNotify();
210
211 // react to dimissing this menu and also dismiss the parent if
212 // dismissParent
213 void HandleDismiss(bool dismissParent);
214
215 // do we have an open submenu?
216 bool HasOpenSubmenu() const { return m_hasOpenSubMenu; }
217
218 // get previous node after the current one
219 wxMenuItemList::Node *GetPrevNode() const;
220
221 // get previous node before the given one, wrapping if it's the first one
222 wxMenuItemList::Node *GetPrevNode(wxMenuItemList::Node *node) const;
223
224 // get next node after the current one
225 wxMenuItemList::Node *GetNextNode() const;
226
227 // get next node after the given one, wrapping if it's the last one
228 wxMenuItemList::Node *GetNextNode(wxMenuItemList::Node *node) const;
229
230 private:
231 // the menu we show
232 wxMenu *m_menu;
233
234 // the menu node corresponding to the current item
235 wxMenuItemList::Node *m_nodeCurrent;
236
237 // do we currently have an opened submenu?
238 bool m_hasOpenSubMenu;
239
240 DECLARE_EVENT_TABLE()
241 };
242
243 // ----------------------------------------------------------------------------
244 // wxMenuKbdRedirector: an event handler which redirects kbd input to wxMenu
245 // ----------------------------------------------------------------------------
246
247 class wxMenuKbdRedirector : public wxEvtHandler
248 {
249 public:
250 wxMenuKbdRedirector(wxMenu *menu) { m_menu = menu; }
251
252 virtual bool ProcessEvent(wxEvent& event)
253 {
254 if ( event.GetEventType() == wxEVT_KEY_DOWN )
255 {
256 return m_menu->ProcessKeyDown(((wxKeyEvent &)event).GetKeyCode());
257 }
258 else
259 {
260 // return FALSE;
261
262 return wxEvtHandler::ProcessEvent(event);
263 }
264 }
265
266 private:
267 wxMenu *m_menu;
268 };
269
270 // ----------------------------------------------------------------------------
271 // wxWin macros
272 // ----------------------------------------------------------------------------
273
274 IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
275 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow)
276 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
277
278 BEGIN_EVENT_TABLE(wxPopupMenuWindow, wxPopupTransientWindow)
279 EVT_KEY_DOWN(wxPopupMenuWindow::OnKeyDown)
280
281 EVT_LEFT_UP(wxPopupMenuWindow::OnLeftUp)
282 EVT_MOTION(wxPopupMenuWindow::OnMouseMove)
283 EVT_LEAVE_WINDOW(wxPopupMenuWindow::OnMouseLeave)
284 END_EVENT_TABLE()
285
286 BEGIN_EVENT_TABLE(wxMenuBar, wxMenuBarBase)
287 EVT_KILL_FOCUS(wxMenuBar::OnKillFocus)
288
289 EVT_KEY_DOWN(wxMenuBar::OnKeyDown)
290
291 EVT_LEFT_DOWN(wxMenuBar::OnLeftDown)
292 EVT_MOTION(wxMenuBar::OnMouseMove)
293 END_EVENT_TABLE()
294
295 // ============================================================================
296 // implementation
297 // ============================================================================
298
299 // ----------------------------------------------------------------------------
300 // wxPopupMenuWindow
301 // ----------------------------------------------------------------------------
302
303 wxPopupMenuWindow::wxPopupMenuWindow(wxWindow *parent, wxMenu *menu)
304 {
305 m_menu = menu;
306 m_hasOpenSubMenu = FALSE;
307
308 ResetCurrent();
309
310 (void)Create(parent, wxBORDER_RAISED);
311
312 SetCursor(wxCURSOR_ARROW);
313 }
314
315 wxPopupMenuWindow::~wxPopupMenuWindow()
316 {
317 // When m_popupMenu in wxMenu is deleted because it
318 // is a child of an old menu bar being deleted (note: it does
319 // not get destroyed by the wxMenu destructor, but
320 // by DestroyChildren()), m_popupMenu should be reset to NULL.
321
322 m_menu->m_popupMenu = NULL;
323 }
324
325 // ----------------------------------------------------------------------------
326 // wxPopupMenuWindow current item/node handling
327 // ----------------------------------------------------------------------------
328
329 void wxPopupMenuWindow::ResetCurrent()
330 {
331 SetCurrent(NULL);
332 }
333
334 void wxPopupMenuWindow::SetCurrent(wxMenuItemList::Node *node)
335 {
336 m_nodeCurrent = node;
337 }
338
339 void wxPopupMenuWindow::ChangeCurrent(wxMenuItemList::Node *node)
340 {
341 if ( node != m_nodeCurrent )
342 {
343 wxMenuItemList::Node *nodeOldCurrent = m_nodeCurrent;
344
345 m_nodeCurrent = node;
346
347 if ( nodeOldCurrent )
348 {
349 wxMenuItem *item = nodeOldCurrent->GetData();
350 wxCHECK_RET( item, _T("no current item?") );
351
352 // if it was the currently opened menu, close it
353 if ( item->IsSubMenu() && item->GetSubMenu()->IsShown() )
354 {
355 item->GetSubMenu()->Dismiss();
356 OnSubmenuDismiss();
357 }
358
359 RefreshItem(item);
360 }
361
362 if ( m_nodeCurrent )
363 RefreshItem(m_nodeCurrent->GetData());
364 }
365 }
366
367 wxMenuItemList::Node *wxPopupMenuWindow::GetPrevNode() const
368 {
369 // return the last node if there had been no previously selected one
370 return m_nodeCurrent ? GetPrevNode(m_nodeCurrent)
371 : m_menu->GetMenuItems().GetLast();
372 }
373
374 wxMenuItemList::Node *
375 wxPopupMenuWindow::GetPrevNode(wxMenuItemList::Node *node) const
376 {
377 if ( node )
378 {
379 node = node->GetPrevious();
380 if ( !node )
381 {
382 node = m_menu->GetMenuItems().GetLast();
383 }
384 }
385 //else: the menu is empty
386
387 return node;
388 }
389
390 wxMenuItemList::Node *wxPopupMenuWindow::GetNextNode() const
391 {
392 // return the first node if there had been no previously selected one
393 return m_nodeCurrent ? GetNextNode(m_nodeCurrent)
394 : m_menu->GetMenuItems().GetFirst();
395 }
396
397 wxMenuItemList::Node *
398 wxPopupMenuWindow::GetNextNode(wxMenuItemList::Node *node) const
399 {
400 if ( node )
401 {
402 node = node->GetNext();
403 if ( !node )
404 {
405 node = m_menu->GetMenuItems().GetFirst();
406 }
407 }
408 //else: the menu is empty
409
410 return node;
411 }
412
413 // ----------------------------------------------------------------------------
414 // wxPopupMenuWindow popup/dismiss
415 // ----------------------------------------------------------------------------
416
417 void wxPopupMenuWindow::Popup(wxWindow *focus)
418 {
419 // check that the current item had been properly reset before
420 wxASSERT_MSG( !m_nodeCurrent ||
421 m_nodeCurrent == m_menu->GetMenuItems().GetFirst(),
422 _T("menu current item preselected incorrectly") );
423
424 wxPopupTransientWindow::Popup(focus);
425
426 #ifdef __WXMSW__
427 // ensure that this window is really on top of everything: without using
428 // SetWindowPos() it can be covered by its parent menu which is not
429 // really what we want
430 wxMenu *menuParent = m_menu->GetParent();
431 if ( menuParent )
432 {
433 wxPopupMenuWindow *win = menuParent->m_popupMenu;
434
435 // if we're shown, the parent menu must be also shown
436 wxCHECK_RET( win, _T("parent menu is not shown?") );
437
438 if ( !::SetWindowPos(GetHwndOf(win), GetHwnd(),
439 0, 0, 0, 0,
440 SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW) )
441 {
442 wxLogLastError(_T("SetWindowPos(HWND_TOP)"));
443 }
444
445 Refresh();
446 }
447 #endif // __WXMSW__
448 }
449
450 void wxPopupMenuWindow::Dismiss()
451 {
452 if ( HasOpenSubmenu() )
453 {
454 wxMenuItem *item = GetCurrentItem();
455 wxCHECK_RET( item && item->IsSubMenu(), _T("where is our open submenu?") );
456
457 wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
458 wxCHECK_RET( win, _T("opened submenu is not opened?") );
459
460 win->Dismiss();
461 OnSubmenuDismiss();
462 }
463
464 wxPopupTransientWindow::Dismiss();
465 }
466
467 void wxPopupMenuWindow::OnDismiss()
468 {
469 // when we are dismissed because the user clicked elsewhere or we lost
470 // focus in any other way, hide the parent menu as well
471 HandleDismiss(TRUE);
472 }
473
474 void wxPopupMenuWindow::HandleDismiss(bool dismissParent)
475 {
476 ResetCurrent();
477
478 m_menu->OnDismiss(dismissParent);
479 }
480
481 void wxPopupMenuWindow::DismissAndNotify()
482 {
483 Dismiss();
484 HandleDismiss(TRUE);
485 }
486
487 // ----------------------------------------------------------------------------
488 // wxPopupMenuWindow geometry
489 // ----------------------------------------------------------------------------
490
491 wxMenuItemList::Node *
492 wxPopupMenuWindow::GetMenuItemFromPoint(const wxPoint& pt) const
493 {
494 // we only use the y coord normally, but still check x in case the point is
495 // outside the window completely
496 if ( wxWindow::HitTest(pt) == wxHT_WINDOW_INSIDE )
497 {
498 wxCoord y = 0;
499 for ( wxMenuItemList::Node *node = m_menu->GetMenuItems().GetFirst();
500 node;
501 node = node->GetNext() )
502 {
503 wxMenuItem *item = node->GetData();
504 y += item->GetHeight();
505 if ( y > pt.y )
506 {
507 // found
508 return node;
509 }
510 }
511 }
512
513 return NULL;
514 }
515
516 // ----------------------------------------------------------------------------
517 // wxPopupMenuWindow drawing
518 // ----------------------------------------------------------------------------
519
520 void wxPopupMenuWindow::RefreshItem(wxMenuItem *item)
521 {
522 wxCHECK_RET( item, _T("can't refresh NULL item") );
523
524 wxASSERT_MSG( IsShown(), _T("can't refresh menu which is not shown") );
525
526 // FIXME: -1 here because of SetLogicalOrigin(1, 1) in DoDraw()
527 RefreshRect(wxRect(0, item->GetPosition() - 1,
528 m_menu->GetGeometryInfo().GetSize().x, item->GetHeight()));
529 }
530
531 void wxPopupMenuWindow::DoDraw(wxControlRenderer *renderer)
532 {
533 // no clipping so far - do we need it? I don't think so as the menu is
534 // never partially covered as it is always on top of everything
535
536 wxDC& dc = renderer->GetDC();
537 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
538
539 // FIXME: this should be done in the renderer, however when it is fixed
540 // wxPopupMenuWindow::RefreshItem() should be changed too!
541 dc.SetLogicalOrigin(1, 1);
542
543 wxRenderer *rend = renderer->GetRenderer();
544
545 wxCoord y = 0;
546 const wxMenuGeometryInfo& gi = m_menu->GetGeometryInfo();
547 for ( wxMenuItemList::Node *node = m_menu->GetMenuItems().GetFirst();
548 node;
549 node = node->GetNext() )
550 {
551 wxMenuItem *item = node->GetData();
552
553 if ( item->IsSeparator() )
554 {
555 rend->DrawMenuSeparator(dc, y, gi);
556 }
557 else // not a separator
558 {
559 int flags = 0;
560 if ( item->IsCheckable() )
561 {
562 flags |= wxCONTROL_CHECKABLE;
563
564 if ( item->IsChecked() )
565 {
566 flags |= wxCONTROL_CHECKED;
567 }
568 }
569
570 if ( !item->IsEnabled() )
571 flags |= wxCONTROL_DISABLED;
572
573 if ( item->IsSubMenu() )
574 flags |= wxCONTROL_ISSUBMENU;
575
576 if ( item == GetCurrentItem() )
577 flags |= wxCONTROL_SELECTED;
578
579 rend->DrawMenuItem
580 (
581 dc,
582 y,
583 gi,
584 item->GetLabel(),
585 item->GetAccelString(),
586 // strangely enough, for unchecked item we use the
587 // "checked" bitmap because this is the default one - this
588 // explains this strange boolean expression
589 item->GetBitmap(!item->IsCheckable() || item->IsChecked()),
590 flags,
591 item->GetAccelIndex()
592 );
593 }
594
595 y += item->GetHeight();
596 }
597 }
598
599 // ----------------------------------------------------------------------------
600 // wxPopupMenuWindow actions
601 // ----------------------------------------------------------------------------
602
603 void wxPopupMenuWindow::ClickItem(wxMenuItem *item)
604 {
605 wxCHECK_RET( item, _T("can't click NULL item") );
606
607 wxASSERT_MSG( !item->IsSeparator() && !item->IsSubMenu(),
608 _T("can't click this item") );
609
610 wxMenu* menu = m_menu;
611
612 // close all menus
613 DismissAndNotify();
614
615 menu->ClickItem(item);
616 }
617
618 void wxPopupMenuWindow::OpenSubmenu(wxMenuItem *item, InputMethod how)
619 {
620 wxCHECK_RET( item, _T("can't open NULL submenu") );
621
622 wxMenu *submenu = item->GetSubMenu();
623 wxCHECK_RET( submenu, _T("can only open submenus!") );
624
625 // FIXME: should take into account the border width
626 submenu->Popup(ClientToScreen(wxPoint(0, item->GetPosition())),
627 wxSize(m_menu->GetGeometryInfo().GetSize().x, 0),
628 how == WithKeyboard /* preselect first item then */);
629
630 m_hasOpenSubMenu = TRUE;
631 }
632
633 bool wxPopupMenuWindow::ActivateItem(wxMenuItem *item, InputMethod how)
634 {
635 // don't activate disabled items
636 if ( !item || !item->IsEnabled() )
637 {
638 return FALSE;
639 }
640
641 // normal menu items generate commands, submenus can be opened and
642 // the separators don't do anything
643 if ( item->IsSubMenu() )
644 {
645 OpenSubmenu(item, how);
646 }
647 else if ( !item->IsSeparator() )
648 {
649 ClickItem(item);
650 }
651 else // separator, can't activate
652 {
653 return FALSE;
654 }
655
656 return TRUE;
657 }
658
659 // ----------------------------------------------------------------------------
660 // wxPopupMenuWindow input handling
661 // ----------------------------------------------------------------------------
662
663 bool wxPopupMenuWindow::ProcessLeftDown(wxMouseEvent& event)
664 {
665 // wxPopupWindowHandler dismisses the window when the mouse is clicked
666 // outside it which is usually just fine, but there is one case when we
667 // don't want to do it: if the mouse was clicked on the parent submenu item
668 // which opens this menu, so check for it
669
670 wxPoint pos = event.GetPosition();
671 if ( HitTest(pos.x, pos.y) == wxHT_WINDOW_OUTSIDE )
672 {
673 wxMenu *menu = m_menu->GetParent();
674 if ( menu )
675 {
676 wxPopupMenuWindow *win = menu->m_popupMenu;
677
678 wxCHECK_MSG( win, FALSE, _T("parent menu not shown?") );
679
680 pos = ClientToScreen(pos);
681 if ( win->GetMenuItemFromPoint(win->ScreenToClient(pos)) )
682 {
683 // eat the event
684 return TRUE;
685 }
686 //else: it is outside the parent menu as well, do dismiss this one
687 }
688 }
689
690 return FALSE;
691 }
692
693 void wxPopupMenuWindow::OnLeftUp(wxMouseEvent& event)
694 {
695 wxMenuItemList::Node *node = GetMenuItemFromPoint(event.GetPosition());
696 if ( node )
697 {
698 ActivateItem(node->GetData(), WithMouse);
699 }
700 }
701
702 void wxPopupMenuWindow::OnMouseMove(wxMouseEvent& event)
703 {
704 const wxPoint pt = event.GetPosition();
705
706 // we need to ignore extra mouse events: example when this happens is when
707 // the mouse is on the menu and we open a submenu from keyboard - Windows
708 // then sends us a dummy mouse move event, we (correctly) determine that it
709 // happens in the parent menu and so immediately close the just opened
710 // submenu!
711 #ifdef __WXMSW__
712 static wxPoint s_ptLast;
713 wxPoint ptCur = ClientToScreen(pt);
714 if ( ptCur == s_ptLast )
715 {
716 return;
717 }
718
719 s_ptLast = ptCur;
720 #endif // __WXMSW__
721
722 ProcessMouseMove(pt);
723
724 event.Skip();
725 }
726
727 void wxPopupMenuWindow::ProcessMouseMove(const wxPoint& pt)
728 {
729 wxMenuItemList::Node *node = GetMenuItemFromPoint(pt);
730
731 // don't reset current to NULL here, we only do it when the mouse leaves
732 // the window (see below)
733 if ( node )
734 {
735 if ( node != m_nodeCurrent )
736 {
737 ChangeCurrent(node);
738
739 wxMenuItem *item = GetCurrentItem();
740 if ( CanOpen(item) )
741 {
742 OpenSubmenu(item, WithMouse);
743 }
744 }
745 //else: same item, nothing to do
746 }
747 else // not on an item
748 {
749 // the last open submenu forwards the mouse move messages to its
750 // parent, so if the mouse moves to another item of the parent menu,
751 // this menu is closed and this other item is selected - in the similar
752 // manner, the top menu forwards the mouse moves to the menubar which
753 // allows to select another top level menu by just moving the mouse
754
755 // we need to translate our client coords to the client coords of the
756 // window we forward this event to
757 wxPoint ptScreen = ClientToScreen(pt);
758
759 // if the mouse is outside this menu, let the parent one to
760 // process it
761 wxMenu *menuParent = m_menu->GetParent();
762 if ( menuParent )
763 {
764 wxPopupMenuWindow *win = menuParent->m_popupMenu;
765
766 // if we're shown, the parent menu must be also shown
767 wxCHECK_RET( win, _T("parent menu is not shown?") );
768
769 win->ProcessMouseMove(win->ScreenToClient(ptScreen));
770 }
771 else // no parent menu
772 {
773 wxMenuBar *menubar = m_menu->GetMenuBar();
774 if ( menubar )
775 {
776 if ( menubar->ProcessMouseEvent(
777 menubar->ScreenToClient(ptScreen)) )
778 {
779 // menubar has closed this menu and opened another one, probably
780 return;
781 }
782 }
783 }
784 //else: top level popup menu, no other processing to do
785 }
786 }
787
788 void wxPopupMenuWindow::OnMouseLeave(wxMouseEvent& event)
789 {
790 // due to the artefact of mouse events generation under MSW, we actually
791 // may get the mouse leave event after the menu had been already dismissed
792 // and calling ChangeCurrent() would then assert, so don't do it
793 if ( IsShown() )
794 {
795 // we shouldn't change the current them if our submenu is opened and
796 // mouse moved there, in this case the submenu is responsable for
797 // handling it
798 bool resetCurrent;
799 if ( HasOpenSubmenu() )
800 {
801 wxMenuItem *item = GetCurrentItem();
802 wxCHECK_RET( CanOpen(item), _T("where is our open submenu?") );
803
804 wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
805 wxCHECK_RET( win, _T("submenu is opened but not shown?") );
806
807 // only handle this event if the mouse is not inside the submenu
808 wxPoint pt = ClientToScreen(event.GetPosition());
809 resetCurrent =
810 win->HitTest(win->ScreenToClient(pt)) == wxHT_WINDOW_OUTSIDE;
811 }
812 else
813 {
814 // this menu is the last opened
815 resetCurrent = TRUE;
816 }
817
818 if ( resetCurrent )
819 {
820 ChangeCurrent(NULL);
821 }
822 }
823
824 event.Skip();
825 }
826
827 void wxPopupMenuWindow::OnKeyDown(wxKeyEvent& event)
828 {
829 if ( !ProcessKeyDown(event.GetKeyCode()) )
830 {
831 event.Skip();
832 }
833 }
834
835 bool wxPopupMenuWindow::ProcessKeyDown(int key)
836 {
837 wxMenuItem *item = GetCurrentItem();
838
839 // first let the opened submenu to have it (no test for IsEnabled() here,
840 // the keys navigate even in a disabled submenu if we had somehow managed
841 // to open it inspit of this)
842 if ( HasOpenSubmenu() )
843 {
844 wxCHECK_MSG( CanOpen(item), FALSE,
845 _T("has open submenu but another item selected?") );
846
847 if ( item->GetSubMenu()->ProcessKeyDown(key) )
848 return TRUE;
849 }
850
851 bool processed = TRUE;
852
853 // handle the up/down arrows, home, end, esc and return here, pass the
854 // left/right arrows to the menu bar except when the right arrow can be
855 // used to open a submenu
856 switch ( key )
857 {
858 case WXK_LEFT:
859 // if we're not a top level menu, close us, else leave this to the
860 // menubar
861 if ( !m_menu->GetParent() )
862 {
863 processed = FALSE;
864 break;
865 }
866
867 // fall through
868
869 case WXK_ESCAPE:
870 // close just this menu
871 Dismiss();
872 HandleDismiss(FALSE);
873 break;
874
875 case WXK_RETURN:
876 processed = ActivateItem(item);
877 break;
878
879 case WXK_HOME:
880 ChangeCurrent(m_menu->GetMenuItems().GetFirst());
881 break;
882
883 case WXK_END:
884 ChangeCurrent(m_menu->GetMenuItems().GetLast());
885 break;
886
887 case WXK_UP:
888 case WXK_DOWN:
889 {
890 bool up = key == WXK_UP;
891
892 wxMenuItemList::Node *nodeStart = up ? GetPrevNode()
893 : GetNextNode(),
894 *node = nodeStart;
895 while ( node && node->GetData()->IsSeparator() )
896 {
897 node = up ? GetPrevNode(node) : GetNextNode(node);
898
899 if ( node == nodeStart )
900 {
901 // nothing but separators and disabled items in this
902 // menu, break out
903 node = NULL;
904 }
905 }
906
907 if ( node )
908 {
909 ChangeCurrent(node);
910 }
911 else
912 {
913 processed = FALSE;
914 }
915 }
916 break;
917
918 case WXK_RIGHT:
919 // don't try to reopen an already opened menu
920 if ( !HasOpenSubmenu() && CanOpen(item) )
921 {
922 OpenSubmenu(item);
923 }
924 else
925 {
926 processed = FALSE;
927 }
928 break;
929
930 default:
931 // look for the menu item starting with this letter
932 if ( wxIsalnum(key) )
933 {
934 // we want to start from the item after this one because
935 // if we're already on the item with the given accel we want to
936 // go to the next one, not to stay in place
937 wxMenuItemList::Node *nodeStart = GetNextNode();
938
939 // do we have more than one item with this accel?
940 bool notUnique = FALSE;
941
942 // translate everything to lower case before comparing
943 wxChar chAccel = wxTolower(key);
944
945 // loop through all items searching for the item with this
946 // accel
947 wxMenuItemList::Node *node = nodeStart,
948 *nodeFound = NULL;
949 for ( ;; )
950 {
951 item = node->GetData();
952
953 int idxAccel = item->GetAccelIndex();
954 if ( idxAccel != -1 &&
955 wxTolower(item->GetLabel()[(size_t)idxAccel])
956 == chAccel )
957 {
958 // ok, found an item with this accel
959 if ( !nodeFound )
960 {
961 // store it but continue searching as we need to
962 // know if it's the only item with this accel or if
963 // there are more
964 nodeFound = node;
965 }
966 else // we already had found such item
967 {
968 notUnique = TRUE;
969
970 // no need to continue further, we won't find
971 // anything we don't already know
972 break;
973 }
974 }
975
976 // we want to iterate over all items wrapping around if
977 // necessary
978 node = GetNextNode(node);
979 if ( node == nodeStart )
980 {
981 // we've seen all nodes
982 break;
983 }
984 }
985
986 if ( nodeFound )
987 {
988 item = nodeFound->GetData();
989
990 // go to this item anyhow
991 ChangeCurrent(nodeFound);
992
993 if ( !notUnique && item->IsEnabled() )
994 {
995 // unique item with this accel - activate it
996 processed = ActivateItem(item);
997 }
998 //else: just select it but don't activate as the user might
999 // have wanted to activate another item
1000
1001 // skip "processed = FALSE" below
1002 break;
1003 }
1004 }
1005
1006 processed = FALSE;
1007 }
1008
1009 return processed;
1010 }
1011
1012 // ----------------------------------------------------------------------------
1013 // wxMenu
1014 // ----------------------------------------------------------------------------
1015
1016 void wxMenu::Init()
1017 {
1018 m_geometry = NULL;
1019
1020 m_popupMenu = NULL;
1021
1022 m_startRadioGroup = -1;
1023 }
1024
1025 wxMenu::~wxMenu()
1026 {
1027 delete m_geometry;
1028 delete m_popupMenu;
1029 }
1030
1031 // ----------------------------------------------------------------------------
1032 // wxMenu and wxMenuGeometryInfo
1033 // ----------------------------------------------------------------------------
1034
1035 wxMenuGeometryInfo::~wxMenuGeometryInfo()
1036 {
1037 }
1038
1039 const wxMenuGeometryInfo& wxMenu::GetGeometryInfo() const
1040 {
1041 if ( !m_geometry )
1042 {
1043 if ( m_popupMenu )
1044 {
1045 wxConstCast(this, wxMenu)->m_geometry =
1046 m_popupMenu->GetRenderer()->GetMenuGeometry(m_popupMenu, *this);
1047 }
1048 else
1049 {
1050 wxFAIL_MSG( _T("can't get geometry without window") );
1051 }
1052 }
1053
1054 return *m_geometry;
1055 }
1056
1057 void wxMenu::InvalidateGeometryInfo()
1058 {
1059 if ( m_geometry )
1060 {
1061 delete m_geometry;
1062 m_geometry = NULL;
1063 }
1064 }
1065
1066 // ----------------------------------------------------------------------------
1067 // wxMenu adding/removing items
1068 // ----------------------------------------------------------------------------
1069
1070 void wxMenu::OnItemAdded(wxMenuItem *item)
1071 {
1072 InvalidateGeometryInfo();
1073
1074 #if wxUSE_ACCEL
1075 AddAccelFor(item);
1076 #endif // wxUSE_ACCEL
1077
1078 // the submenus of a popup menu should have the same invoking window as it
1079 // has
1080 if ( m_invokingWindow && item->IsSubMenu() )
1081 {
1082 item->GetSubMenu()->SetInvokingWindow(m_invokingWindow);
1083 }
1084 }
1085
1086 void wxMenu::EndRadioGroup()
1087 {
1088 // we're not inside a radio group any longer
1089 m_startRadioGroup = -1;
1090 }
1091
1092 bool wxMenu::DoAppend(wxMenuItem *item)
1093 {
1094 bool check = FALSE;
1095
1096 if ( item->GetKind() == wxITEM_RADIO )
1097 {
1098 int count = GetMenuItemCount();
1099
1100 if ( m_startRadioGroup == -1 )
1101 {
1102 // start a new radio group
1103 m_startRadioGroup = count;
1104
1105 // for now it has just one element
1106 item->SetAsRadioGroupStart();
1107 item->SetRadioGroupEnd(m_startRadioGroup);
1108
1109 // ensure that we have a checked item in the radio group
1110 check = TRUE;
1111 }
1112 else // extend the current radio group
1113 {
1114 // we need to update its end item
1115 item->SetRadioGroupStart(m_startRadioGroup);
1116 wxMenuItemList::Node *node = GetMenuItems().Item(m_startRadioGroup);
1117
1118 if ( node )
1119 {
1120 node->GetData()->SetRadioGroupEnd(count);
1121 }
1122 else
1123 {
1124 wxFAIL_MSG( _T("where is the radio group start item?") );
1125 }
1126 }
1127 }
1128 else // not a radio item
1129 {
1130 EndRadioGroup();
1131 }
1132
1133 if ( !wxMenuBase::DoAppend(item) )
1134 return FALSE;
1135
1136 OnItemAdded(item);
1137
1138 return TRUE;
1139 }
1140
1141 bool wxMenu::DoInsert(size_t pos, wxMenuItem *item)
1142 {
1143 if ( !wxMenuBase::DoInsert(pos, item) )
1144 return FALSE;
1145
1146 OnItemAdded(item);
1147
1148 return TRUE;
1149 }
1150
1151 wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
1152 {
1153 wxMenuItem *itemOld = wxMenuBase::DoRemove(item);
1154
1155 if ( itemOld )
1156 {
1157 InvalidateGeometryInfo();
1158
1159 #if wxUSE_ACCEL
1160 RemoveAccelFor(item);
1161 #endif // wxUSE_ACCEL
1162 }
1163
1164 return itemOld;
1165 }
1166
1167 // ----------------------------------------------------------------------------
1168 // wxMenu attaching/detaching
1169 // ----------------------------------------------------------------------------
1170
1171 void wxMenu::Attach(wxMenuBarBase *menubar)
1172 {
1173 wxMenuBase::Attach(menubar);
1174
1175 wxCHECK_RET( m_menuBar, _T("menubar can't be NULL after attaching") );
1176
1177 // unfortunately, we can't use m_menuBar->GetEventHandler() here because,
1178 // if the menubar is currently showing a menu, its event handler is a
1179 // temporary one installed by wxPopupWindow and so will disappear soon any
1180 // any attempts to use it from the newly attached menu would result in a
1181 // crash
1182 //
1183 // so we use the menubar itself, even if it's a pity as it means we can't
1184 // redirect all menu events by changing the menubar handler (FIXME)
1185 SetNextHandler(m_menuBar);
1186 }
1187
1188 void wxMenu::Detach()
1189 {
1190 wxMenuBase::Detach();
1191 }
1192
1193 // ----------------------------------------------------------------------------
1194 // wxMenu misc functions
1195 // ----------------------------------------------------------------------------
1196
1197 wxWindow *wxMenu::GetRootWindow() const
1198 {
1199 if ( m_menuBar )
1200 {
1201 // simple case - a normal menu attached to the menubar
1202 return m_menuBar;
1203 }
1204
1205 // we're a popup menu but the trouble is that only the top level popup menu
1206 // has a pointer to the invoking window, so we must walk up the menu chain
1207 // if needed
1208 wxWindow *win = GetInvokingWindow();
1209 if ( win )
1210 {
1211 // we already have it
1212 return win;
1213 }
1214
1215 wxMenu *menu = GetParent();
1216 while ( menu )
1217 {
1218 // We are a submenu of a menu of a menubar
1219 if (menu->GetMenuBar())
1220 return menu->GetMenuBar();
1221
1222 win = menu->GetInvokingWindow();
1223 if ( win )
1224 break;
1225
1226 menu = menu->GetParent();
1227 }
1228
1229 // we're probably going to crash in the caller anyhow, but try to detect
1230 // this error as soon as possible
1231 wxASSERT_MSG( win, _T("menu without any associated window?") );
1232
1233 // also remember it in this menu so that we don't have to search for it the
1234 // next time
1235 wxConstCast(this, wxMenu)->m_invokingWindow = win;
1236
1237 return win;
1238 }
1239
1240 wxRenderer *wxMenu::GetRenderer() const
1241 {
1242 // we're going to crash without renderer!
1243 wxCHECK_MSG( m_popupMenu, NULL, _T("neither popup nor menubar menu?") );
1244
1245 return m_popupMenu->GetRenderer();
1246 }
1247
1248 void wxMenu::RefreshItem(wxMenuItem *item)
1249 {
1250 // the item geometry changed, so our might have changed as well
1251 InvalidateGeometryInfo();
1252
1253 if ( IsShown() )
1254 {
1255 // this would be a bug in IsShown()
1256 wxCHECK_RET( m_popupMenu, _T("must have popup window if shown!") );
1257
1258 // recalc geometry to update the item height and such
1259 (void)GetGeometryInfo();
1260
1261 m_popupMenu->RefreshItem(item);
1262 }
1263 }
1264
1265 // ----------------------------------------------------------------------------
1266 // wxMenu showing and hiding
1267 // ----------------------------------------------------------------------------
1268
1269 bool wxMenu::IsShown() const
1270 {
1271 return m_popupMenu && m_popupMenu->IsShown();
1272 }
1273
1274 void wxMenu::OnDismiss(bool dismissParent)
1275 {
1276 if ( m_menuParent )
1277 {
1278 // always notify the parent about submenu disappearance
1279 wxPopupMenuWindow *win = m_menuParent->m_popupMenu;
1280 if ( win )
1281 {
1282 win->OnSubmenuDismiss();
1283 }
1284 else
1285 {
1286 wxFAIL_MSG( _T("parent menu not shown?") );
1287 }
1288
1289 // and if we dismiss everything, propagate to parent
1290 if ( dismissParent )
1291 {
1292 // dismissParent is recursive
1293 m_menuParent->Dismiss();
1294 m_menuParent->OnDismiss(TRUE);
1295 }
1296 }
1297 else // no parent menu
1298 {
1299 // notify the menu bar if we're a top level menu
1300 if ( m_menuBar )
1301 {
1302 m_menuBar->OnDismissMenu(dismissParent);
1303 }
1304 else // popup menu
1305 {
1306 wxCHECK_RET( m_invokingWindow, _T("what kind of menu is this?") );
1307
1308 m_invokingWindow->DismissPopupMenu();
1309
1310 // Why reset it here? We need it for sending the event to...
1311 // SetInvokingWindow(NULL);
1312 }
1313 }
1314 }
1315
1316 void wxMenu::Popup(const wxPoint& pos, const wxSize& size, bool selectFirst)
1317 {
1318 // create the popup window if not done yet
1319 if ( !m_popupMenu )
1320 {
1321 m_popupMenu = new wxPopupMenuWindow(GetRootWindow(), this);
1322 }
1323
1324 // select the first item unless disabled
1325 if ( selectFirst )
1326 {
1327 m_popupMenu->SelectFirst();
1328 }
1329
1330 // the geometry might have changed since the last time we were shown, so
1331 // always resize
1332 m_popupMenu->SetClientSize(GetGeometryInfo().GetSize());
1333
1334 // position it as specified
1335 m_popupMenu->Position(pos, size);
1336
1337 // the menu can't have the focus itself (it is a Windows limitation), so
1338 // always keep the focus at the originating window
1339 wxWindow *focus = GetRootWindow();
1340
1341 wxASSERT_MSG( focus, _T("no window to keep focus on?") );
1342
1343 // and show it
1344 m_popupMenu->Popup(focus);
1345 }
1346
1347 void wxMenu::Dismiss()
1348 {
1349 wxCHECK_RET( IsShown(), _T("can't dismiss hidden menu") );
1350
1351 m_popupMenu->Dismiss();
1352 }
1353
1354 // ----------------------------------------------------------------------------
1355 // wxMenu event processing
1356 // ----------------------------------------------------------------------------
1357
1358 bool wxMenu::ProcessKeyDown(int key)
1359 {
1360 wxCHECK_MSG( m_popupMenu, FALSE,
1361 _T("can't process key events if not shown") );
1362
1363 return m_popupMenu->ProcessKeyDown(key);
1364 }
1365
1366 bool wxMenu::ClickItem(wxMenuItem *item)
1367 {
1368 int isChecked;
1369 if ( item->IsCheckable() )
1370 {
1371 // update the item state
1372 isChecked = !item->IsChecked();
1373
1374 item->Check(isChecked != 0);
1375 }
1376 else
1377 {
1378 // not applicabled
1379 isChecked = -1;
1380 }
1381
1382 return SendEvent(item->GetId(), isChecked);
1383 }
1384
1385 // ----------------------------------------------------------------------------
1386 // wxMenu accel support
1387 // ----------------------------------------------------------------------------
1388
1389 #if wxUSE_ACCEL
1390
1391 bool wxMenu::ProcessAccelEvent(const wxKeyEvent& event)
1392 {
1393 // do we have an item for this accel?
1394 wxMenuItem *item = m_accelTable.GetMenuItem(event);
1395 if ( item && item->IsEnabled() )
1396 {
1397 return ClickItem(item);
1398 }
1399
1400 // try our submenus
1401 for ( wxMenuItemList::Node *node = GetMenuItems().GetFirst();
1402 node;
1403 node = node->GetNext() )
1404 {
1405 const wxMenuItem *item = node->GetData();
1406 if ( item->IsSubMenu() && item->IsEnabled() )
1407 {
1408 // try its elements
1409 if ( item->GetSubMenu()->ProcessAccelEvent(event) )
1410 {
1411 return TRUE;
1412 }
1413 }
1414 }
1415
1416 return FALSE;
1417 }
1418
1419 void wxMenu::AddAccelFor(wxMenuItem *item)
1420 {
1421 wxAcceleratorEntry *accel = item->GetAccel();
1422 if ( accel )
1423 {
1424 accel->SetMenuItem(item);
1425
1426 m_accelTable.Add(*accel);
1427
1428 delete accel;
1429 }
1430 }
1431
1432 void wxMenu::RemoveAccelFor(wxMenuItem *item)
1433 {
1434 wxAcceleratorEntry *accel = item->GetAccel();
1435 if ( accel )
1436 {
1437 m_accelTable.Remove(*accel);
1438
1439 delete accel;
1440 }
1441 }
1442
1443 #endif // wxUSE_ACCEL
1444
1445 // ----------------------------------------------------------------------------
1446 // wxMenuItem construction
1447 // ----------------------------------------------------------------------------
1448
1449 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
1450 int id,
1451 const wxString& text,
1452 const wxString& help,
1453 wxItemKind kind,
1454 wxMenu *subMenu)
1455 : wxMenuItemBase(parentMenu, id, text, help, kind, subMenu)
1456 {
1457 m_posY =
1458 m_height = -1;
1459
1460 m_radioGroup.start = -1;
1461 m_isRadioGroupStart = FALSE;
1462
1463 UpdateAccelInfo();
1464 }
1465
1466 wxMenuItem::~wxMenuItem()
1467 {
1468 }
1469
1470 // ----------------------------------------------------------------------------
1471 // wxMenuItemBase methods implemented here
1472 // ----------------------------------------------------------------------------
1473
1474 /* static */
1475 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
1476 int id,
1477 const wxString& name,
1478 const wxString& help,
1479 wxItemKind kind,
1480 wxMenu *subMenu)
1481 {
1482 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
1483 }
1484
1485 /* static */
1486 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
1487 {
1488 return wxStripMenuCodes(text);
1489 }
1490
1491 // ----------------------------------------------------------------------------
1492 // wxMenuItem operations
1493 // ----------------------------------------------------------------------------
1494
1495 void wxMenuItem::NotifyMenu()
1496 {
1497 m_parentMenu->RefreshItem(this);
1498 }
1499
1500 void wxMenuItem::UpdateAccelInfo()
1501 {
1502 m_indexAccel = wxControl::FindAccelIndex(m_text);
1503
1504 // will be empty if the text contains no TABs - ok
1505 m_strAccel = m_text.AfterFirst(_T('\t'));
1506 }
1507
1508 void wxMenuItem::SetText(const wxString& text)
1509 {
1510 if ( text != m_text )
1511 {
1512 // first call the base class version to change m_text
1513 wxMenuItemBase::SetText(text);
1514
1515 UpdateAccelInfo();
1516
1517 NotifyMenu();
1518 }
1519 }
1520
1521 void wxMenuItem::SetCheckable(bool checkable)
1522 {
1523 if ( checkable != IsCheckable() )
1524 {
1525 wxMenuItemBase::SetCheckable(checkable);
1526
1527 NotifyMenu();
1528 }
1529 }
1530
1531 void wxMenuItem::SetBitmaps(const wxBitmap& bmpChecked,
1532 const wxBitmap& bmpUnchecked)
1533 {
1534 m_bmpChecked = bmpChecked;
1535 m_bmpUnchecked = bmpUnchecked;
1536
1537 NotifyMenu();
1538 }
1539
1540 void wxMenuItem::Enable(bool enable)
1541 {
1542 if ( enable != m_isEnabled )
1543 {
1544 wxMenuItemBase::Enable(enable);
1545
1546 NotifyMenu();
1547 }
1548 }
1549
1550 void wxMenuItem::Check(bool check)
1551 {
1552 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
1553
1554 if ( m_isChecked == check )
1555 return;
1556
1557 if ( GetKind() == wxITEM_RADIO )
1558 {
1559 // it doesn't make sense to uncheck a radio item - what would this do?
1560 if ( !check )
1561 return;
1562
1563 // get the index of this item in the menu
1564 const wxMenuItemList& items = m_parentMenu->GetMenuItems();
1565 int pos = items.IndexOf(this);
1566 wxCHECK_RET( pos != wxNOT_FOUND,
1567 _T("menuitem not found in the menu items list?") );
1568
1569 // get the radio group range
1570 int start,
1571 end;
1572
1573 if ( m_isRadioGroupStart )
1574 {
1575 // we already have all information we need
1576 start = pos;
1577 end = m_radioGroup.end;
1578 }
1579 else // next radio group item
1580 {
1581 // get the radio group end from the start item
1582 start = m_radioGroup.start;
1583 end = items.Item(start)->GetData()->m_radioGroup.end;
1584 }
1585
1586 // also uncheck all the other items in this radio group
1587 wxMenuItemList::Node *node = items.Item(start);
1588 for ( int n = start; n <= end && node; n++ )
1589 {
1590 if ( n != pos )
1591 {
1592 node->GetData()->m_isChecked = FALSE;
1593 }
1594 node = node->GetNext();
1595 }
1596 }
1597
1598 wxMenuItemBase::Check(check);
1599
1600 NotifyMenu();
1601 }
1602
1603 // radio group stuff
1604 // -----------------
1605
1606 void wxMenuItem::SetAsRadioGroupStart()
1607 {
1608 m_isRadioGroupStart = TRUE;
1609 }
1610
1611 void wxMenuItem::SetRadioGroupStart(int start)
1612 {
1613 wxASSERT_MSG( !m_isRadioGroupStart,
1614 _T("should only be called for the next radio items") );
1615
1616 m_radioGroup.start = start;
1617 }
1618
1619 void wxMenuItem::SetRadioGroupEnd(int end)
1620 {
1621 wxASSERT_MSG( m_isRadioGroupStart,
1622 _T("should only be called for the first radio item") );
1623
1624 m_radioGroup.end = end;
1625 }
1626
1627 // ----------------------------------------------------------------------------
1628 // wxMenuBar creation
1629 // ----------------------------------------------------------------------------
1630
1631 void wxMenuBar::Init()
1632 {
1633 m_frameLast = NULL;
1634
1635 m_current = -1;
1636
1637 m_menuShown = NULL;
1638
1639 m_shouldShowMenu = FALSE;
1640
1641 m_windowStyle |= wxNO_FULL_REPAINT_ON_RESIZE;
1642 }
1643
1644 void wxMenuBar::Attach(wxFrame *frame)
1645 {
1646 // maybe you really wanted to call Detach()?
1647 wxCHECK_RET( frame, _T("wxMenuBar::Attach(NULL) called") );
1648
1649 wxMenuBarBase::Attach(frame);
1650
1651 if ( IsCreated() )
1652 {
1653 // reparent if necessary
1654 if ( m_frameLast != frame )
1655 {
1656 Reparent(frame);
1657 }
1658
1659 // show it back - was hidden by Detach()
1660 Show();
1661 }
1662 else // not created yet, do it now
1663 {
1664 // we have no way to return the error from here anyhow :-(
1665 (void)Create(frame, -1);
1666
1667 SetCursor(wxCURSOR_ARROW);
1668
1669 SetFont(wxSystemSettings::GetFont(wxSYS_SYSTEM_FONT));
1670
1671 // calculate and set our height (it won't be changed any more)
1672 SetSize(-1, GetBestSize().y);
1673 }
1674
1675 // remember the last frame which had us to avoid unnecessarily reparenting
1676 // above
1677 m_frameLast = frame;
1678 }
1679
1680 void wxMenuBar::Detach()
1681 {
1682 // don't delete the window because we may be reattached later, just hide it
1683 if ( m_frameLast )
1684 {
1685 Hide();
1686 }
1687
1688 wxMenuBarBase::Detach();
1689 }
1690
1691 wxMenuBar::~wxMenuBar()
1692 {
1693 }
1694
1695 // ----------------------------------------------------------------------------
1696 // wxMenuBar adding/removing items
1697 // ----------------------------------------------------------------------------
1698
1699 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1700 {
1701 return Insert(GetCount(), menu, title);
1702 }
1703
1704 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1705 {
1706 if ( !wxMenuBarBase::Insert(pos, menu, title) )
1707 return FALSE;
1708
1709 wxMenuInfo *info = new wxMenuInfo(title);
1710 m_menuInfos.Insert(info, pos);
1711
1712 RefreshAllItemsAfter(pos);
1713
1714 return TRUE;
1715 }
1716
1717 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
1718 {
1719 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
1720
1721 if ( menuOld )
1722 {
1723 wxMenuInfo& info = m_menuInfos[pos];
1724
1725 info.SetLabel(title);
1726
1727 // even if the old menu was disabled, the new one is not any more
1728 info.SetEnabled();
1729
1730 // even if we change only this one, the new label has different width,
1731 // so we need to refresh everything beyond this item as well
1732 RefreshAllItemsAfter(pos);
1733 }
1734
1735 return menuOld;
1736 }
1737
1738 wxMenu *wxMenuBar::Remove(size_t pos)
1739 {
1740 wxMenu *menuOld = wxMenuBarBase::Remove(pos);
1741
1742 if ( menuOld )
1743 {
1744 m_menuInfos.RemoveAt(pos);
1745
1746 // this doesn't happen too often, so don't try to be too smart - just
1747 // refresh everything
1748 Refresh();
1749 }
1750
1751 return menuOld;
1752 }
1753
1754 // ----------------------------------------------------------------------------
1755 // wxMenuBar top level menus access
1756 // ----------------------------------------------------------------------------
1757
1758 wxCoord wxMenuBar::GetItemWidth(size_t pos) const
1759 {
1760 return m_menuInfos[pos].GetWidth(wxConstCast(this, wxMenuBar));
1761 }
1762
1763 void wxMenuBar::EnableTop(size_t pos, bool enable)
1764 {
1765 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1766
1767 if ( enable != m_menuInfos[pos].IsEnabled() )
1768 {
1769 m_menuInfos[pos].SetEnabled(enable);
1770
1771 RefreshItem(pos);
1772 }
1773 //else: nothing to do
1774 }
1775
1776 bool wxMenuBar::IsEnabledTop(size_t pos) const
1777 {
1778 wxCHECK_MSG( pos < GetCount(), FALSE, _T("invalid index in IsEnabledTop") );
1779
1780 return m_menuInfos[pos].IsEnabled();
1781 }
1782
1783 void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
1784 {
1785 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1786
1787 if ( label != m_menuInfos[pos].GetLabel() )
1788 {
1789 m_menuInfos[pos].SetLabel(label);
1790
1791 RefreshItem(pos);
1792 }
1793 //else: nothing to do
1794 }
1795
1796 wxString wxMenuBar::GetLabelTop(size_t pos) const
1797 {
1798 wxCHECK_MSG( pos < GetCount(), _T(""), _T("invalid index in GetLabelTop") );
1799
1800 return m_menuInfos[pos].GetLabel();
1801 }
1802
1803 // ----------------------------------------------------------------------------
1804 // wxMenuBar drawing
1805 // ----------------------------------------------------------------------------
1806
1807 void wxMenuBar::RefreshAllItemsAfter(size_t pos)
1808 {
1809 if ( !IsCreated() )
1810 {
1811 // no need to refresh if nothing is shown yet
1812 return;
1813 }
1814
1815 wxRect rect = GetItemRect(pos);
1816 rect.width = GetClientSize().x - rect.x;
1817 RefreshRect(rect);
1818 }
1819
1820 void wxMenuBar::RefreshItem(size_t pos)
1821 {
1822 wxCHECK_RET( pos != (size_t)-1,
1823 _T("invalid item in wxMenuBar::RefreshItem") );
1824
1825 if ( !IsCreated() )
1826 {
1827 // no need to refresh if nothing is shown yet
1828 return;
1829 }
1830
1831 RefreshRect(GetItemRect(pos));
1832 }
1833
1834 void wxMenuBar::DoDraw(wxControlRenderer *renderer)
1835 {
1836 wxDC& dc = renderer->GetDC();
1837 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1838
1839 // redraw only the items which must be redrawn
1840
1841 // we don't have to use GetUpdateClientRect() here because our client rect
1842 // is the same as total one
1843 wxRect rectUpdate = GetUpdateRegion().GetBox();
1844
1845 int flagsMenubar = GetStateFlags();
1846
1847 wxRect rect;
1848 rect.y = 0;
1849 rect.height = GetClientSize().y;
1850
1851 wxCoord x = 0;
1852 size_t count = GetCount();
1853 for ( size_t n = 0; n < count; n++ )
1854 {
1855 if ( x > rectUpdate.GetRight() )
1856 {
1857 // all remaining items are to the right of rectUpdate
1858 break;
1859 }
1860
1861 rect.x = x;
1862 rect.width = GetItemWidth(n);
1863 x += rect.width;
1864 if ( x < rectUpdate.x )
1865 {
1866 // this item is still to the left of rectUpdate
1867 continue;
1868 }
1869
1870 int flags = flagsMenubar;
1871 if ( m_current != -1 && n == (size_t)m_current )
1872 {
1873 flags |= wxCONTROL_SELECTED;
1874 }
1875
1876 if ( !IsEnabledTop(n) )
1877 {
1878 flags |= wxCONTROL_DISABLED;
1879 }
1880
1881 GetRenderer()->DrawMenuBarItem
1882 (
1883 dc,
1884 rect,
1885 m_menuInfos[n].GetLabel(),
1886 flags,
1887 m_menuInfos[n].GetAccelIndex()
1888 );
1889 }
1890 }
1891
1892 // ----------------------------------------------------------------------------
1893 // wxMenuBar geometry
1894 // ----------------------------------------------------------------------------
1895
1896 wxRect wxMenuBar::GetItemRect(size_t pos) const
1897 {
1898 wxASSERT_MSG( pos < GetCount(), _T("invalid menu bar item index") );
1899 wxASSERT_MSG( IsCreated(), _T("can't call this method yet") );
1900
1901 wxRect rect;
1902 rect.x =
1903 rect.y = 0;
1904 rect.height = GetClientSize().y;
1905
1906 for ( size_t n = 0; n < pos; n++ )
1907 {
1908 rect.x += GetItemWidth(n);
1909 }
1910
1911 rect.width = GetItemWidth(pos);
1912
1913 return rect;
1914 }
1915
1916 wxSize wxMenuBar::DoGetBestClientSize() const
1917 {
1918 wxSize size;
1919 if ( GetMenuCount() > 0 )
1920 {
1921 wxClientDC dc(wxConstCast(this, wxMenuBar));
1922 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1923 dc.GetTextExtent(GetLabelTop(0), &size.x, &size.y);
1924
1925 // adjust for the renderer we use
1926 size = GetRenderer()->GetMenuBarItemSize(size);
1927 }
1928 else // empty menubar
1929 {
1930 size.x =
1931 size.y = 0;
1932 }
1933
1934 // the width is arbitrary, of course, for horizontal menubar
1935 size.x = 100;
1936
1937 return size;
1938 }
1939
1940 int wxMenuBar::GetMenuFromPoint(const wxPoint& pos) const
1941 {
1942 if ( pos.x < 0 || pos.y < 0 || pos.y > GetClientSize().y )
1943 return -1;
1944
1945 // do find it
1946 wxCoord x = 0;
1947 size_t count = GetCount();
1948 for ( size_t item = 0; item < count; item++ )
1949 {
1950 x += GetItemWidth(item);
1951
1952 if ( x > pos.x )
1953 {
1954 return item;
1955 }
1956 }
1957
1958 // to the right of the last menu item
1959 return -1;
1960 }
1961
1962 // ----------------------------------------------------------------------------
1963 // wxMenuBar menu operations
1964 // ----------------------------------------------------------------------------
1965
1966 void wxMenuBar::SelectMenu(size_t pos)
1967 {
1968 SetFocus();
1969 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::SelectMenu"));
1970 CaptureMouse();
1971
1972 DoSelectMenu(pos);
1973 }
1974
1975 void wxMenuBar::DoSelectMenu(size_t pos)
1976 {
1977 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in DoSelectMenu") );
1978
1979 int posOld = m_current;
1980
1981 m_current = pos;
1982
1983 if ( posOld != -1 )
1984 {
1985 // close the previous menu
1986 if ( IsShowingMenu() )
1987 {
1988 // restore m_shouldShowMenu flag after DismissMenu() which resets
1989 // it to FALSE
1990 bool old = m_shouldShowMenu;
1991
1992 DismissMenu();
1993
1994 m_shouldShowMenu = old;
1995 }
1996
1997 RefreshItem((size_t)posOld);
1998 }
1999
2000 RefreshItem(pos);
2001 }
2002
2003 void wxMenuBar::PopupMenu(size_t pos)
2004 {
2005 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in PopupCurrentMenu") );
2006
2007 SetFocus();
2008 DoSelectMenu(pos);
2009 PopupCurrentMenu();
2010 }
2011
2012 // ----------------------------------------------------------------------------
2013 // wxMenuBar input handing
2014 // ----------------------------------------------------------------------------
2015
2016 /*
2017 Note that wxMenuBar doesn't use wxInputHandler but handles keyboard and
2018 mouse in the same way under all platforms. This is because it doesn't derive
2019 from wxControl (which works with input handlers) but directly from wxWindow.
2020
2021 Also, menu bar input handling is rather simple, so maybe it's not really
2022 worth making it themeable - at least I've decided against doing it now as it
2023 would merging the changes back into trunk more difficult. But it still could
2024 be done later if really needed.
2025 */
2026
2027 void wxMenuBar::OnKillFocus(wxFocusEvent& event)
2028 {
2029 if ( m_current != -1 )
2030 {
2031 RefreshItem((size_t)m_current);
2032
2033 m_current = -1;
2034 }
2035
2036 event.Skip();
2037 }
2038
2039 void wxMenuBar::OnLeftDown(wxMouseEvent& event)
2040 {
2041 if ( HasCapture() )
2042 {
2043 OnDismiss();
2044
2045 event.Skip();
2046 }
2047 else // we didn't have mouse capture, capture it now
2048 {
2049 m_current = GetMenuFromPoint(event.GetPosition());
2050 if ( m_current == -1 )
2051 {
2052 // unfortunately, we can't prevent wxMSW from giving us the focus,
2053 // so we can only give it back
2054 GiveAwayFocus();
2055 }
2056 else // on item
2057 {
2058 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::OnLeftDown"));
2059 CaptureMouse();
2060
2061 // show it as selected
2062 RefreshItem((size_t)m_current);
2063
2064 // show the menu
2065 PopupCurrentMenu(FALSE /* don't select first item - as Windows does */);
2066 }
2067 }
2068 }
2069
2070 void wxMenuBar::OnMouseMove(wxMouseEvent& event)
2071 {
2072 if ( HasCapture() )
2073 {
2074 (void)ProcessMouseEvent(event.GetPosition());
2075 }
2076 else
2077 {
2078 event.Skip();
2079 }
2080 }
2081
2082 bool wxMenuBar::ProcessMouseEvent(const wxPoint& pt)
2083 {
2084 // a hack to ignore the extra mouse events MSW sends us: this is similar to
2085 // wxUSE_MOUSEEVENT_HACK in wxWin itself but it isn't enough for us here as
2086 // we get the messages from different windows (old and new popup menus for
2087 // example)
2088 #ifdef __WXMSW__
2089 static wxPoint s_ptLast;
2090 if ( pt == s_ptLast )
2091 {
2092 return FALSE;
2093 }
2094
2095 s_ptLast = pt;
2096 #endif // __WXMSW__
2097
2098 int currentNew = GetMenuFromPoint(pt);
2099 if ( (currentNew == -1) || (currentNew == m_current) )
2100 {
2101 return FALSE;
2102 }
2103
2104 // select the new active item
2105 DoSelectMenu(currentNew);
2106
2107 // show the menu if we know that we should, even if we hadn't been showing
2108 // it before (this may happen if the previous menu was disabled)
2109 if ( m_shouldShowMenu && !m_menuShown)
2110 {
2111 // open the new menu if the old one we closed had been opened
2112 PopupCurrentMenu(FALSE /* don't select first item - as Windows does */);
2113 }
2114
2115 return TRUE;
2116 }
2117
2118 void wxMenuBar::OnKeyDown(wxKeyEvent& event)
2119 {
2120 // ensure that we have a current item - we might not have it if we're
2121 // given the focus with Alt or F10 press (and under GTK+ the menubar
2122 // somehow gets the keyboard events even when it doesn't have focus...)
2123 if ( m_current == -1 )
2124 {
2125 if ( !HasCapture() )
2126 {
2127 SelectMenu(0);
2128 }
2129 else // we do have capture
2130 {
2131 // we always maintain a valid current item while we're in modal
2132 // state (i.e. have the capture)
2133 wxFAIL_MSG( _T("how did we manage to lose current item?") );
2134
2135 return;
2136 }
2137 }
2138
2139 int key = event.GetKeyCode();
2140
2141 // first let the menu have it
2142 if ( IsShowingMenu() && m_menuShown->ProcessKeyDown(key) )
2143 {
2144 return;
2145 }
2146
2147 // cycle through the menu items when left/right arrows are pressed and open
2148 // the menu when up/down one is
2149 switch ( key )
2150 {
2151 case WXK_MENU:
2152 // Alt must be processed at wxWindow level too
2153 event.Skip();
2154 // fall through
2155
2156 case WXK_ESCAPE:
2157 // remove the selection and give the focus away
2158 if ( m_current != -1 )
2159 {
2160 if ( IsShowingMenu() )
2161 {
2162 DismissMenu();
2163 }
2164
2165 OnDismiss();
2166 }
2167 break;
2168
2169 case WXK_LEFT:
2170 case WXK_RIGHT:
2171 {
2172 size_t count = GetCount();
2173 if ( count == 1 )
2174 {
2175 // the item won't change anyhow
2176 break;
2177 }
2178 //else: otherwise, it will
2179
2180 // remember if we were showing a menu - if we did, we should
2181 // show the new menu after changing the item
2182 bool wasMenuOpened = IsShowingMenu();
2183 if ( wasMenuOpened )
2184 {
2185 DismissMenu();
2186 }
2187
2188 // cast is safe as we tested for -1 above
2189 size_t currentNew = (size_t)m_current;
2190
2191 if ( key == WXK_LEFT )
2192 {
2193 if ( currentNew-- == 0 )
2194 currentNew = count - 1;
2195 }
2196 else // right
2197 {
2198 if ( ++currentNew == count )
2199 currentNew = 0;
2200 }
2201
2202 DoSelectMenu(currentNew);
2203
2204 if ( wasMenuOpened )
2205 {
2206 PopupCurrentMenu();
2207 }
2208 }
2209 break;
2210
2211 case WXK_DOWN:
2212 case WXK_UP:
2213 case WXK_RETURN:
2214 // open the menu
2215 PopupCurrentMenu();
2216 break;
2217
2218 default:
2219 // letters open the corresponding menu
2220 {
2221 bool unique;
2222 int idxFound = FindNextItemForAccel(m_current, key, &unique);
2223
2224 if ( idxFound != -1 )
2225 {
2226 if ( IsShowingMenu() )
2227 {
2228 DismissMenu();
2229 }
2230
2231 DoSelectMenu((size_t)idxFound);
2232
2233 // if the item is not unique, just select it but don't
2234 // activate as the user might have wanted to activate
2235 // another item
2236 //
2237 // also, don't try to open a disabled menu
2238 if ( unique && IsEnabledTop((size_t)idxFound) )
2239 {
2240 // open the menu
2241 PopupCurrentMenu();
2242 }
2243
2244 // skip the "event.Skip()" below
2245 break;
2246 }
2247 }
2248
2249 event.Skip();
2250 }
2251 }
2252
2253 // ----------------------------------------------------------------------------
2254 // wxMenuBar accel handling
2255 // ----------------------------------------------------------------------------
2256
2257 int wxMenuBar::FindNextItemForAccel(int idxStart, int key, bool *unique) const
2258 {
2259 if ( !wxIsalnum(key) )
2260 {
2261 // we only support letters/digits as accels
2262 return -1;
2263 }
2264
2265 // do we have more than one item with this accel?
2266 if ( unique )
2267 *unique = TRUE;
2268
2269 // translate everything to lower case before comparing
2270 wxChar chAccel = wxTolower(key);
2271
2272 // the index of the item with this accel
2273 int idxFound = -1;
2274
2275 // loop through all items searching for the item with this
2276 // accel starting at the item after the current one
2277 int count = GetCount();
2278 int n = idxStart == -1 ? 0 : idxStart + 1;
2279
2280 if ( n == count )
2281 {
2282 // wrap
2283 n = 0;
2284 }
2285
2286 idxStart = n;
2287 for ( ;; )
2288 {
2289 const wxMenuInfo& info = m_menuInfos[n];
2290
2291 int idxAccel = info.GetAccelIndex();
2292 if ( idxAccel != -1 &&
2293 wxTolower(info.GetLabel()[(size_t)idxAccel])
2294 == chAccel )
2295 {
2296 // ok, found an item with this accel
2297 if ( idxFound == -1 )
2298 {
2299 // store it but continue searching as we need to
2300 // know if it's the only item with this accel or if
2301 // there are more
2302 idxFound = n;
2303 }
2304 else // we already had found such item
2305 {
2306 if ( unique )
2307 *unique = FALSE;
2308
2309 // no need to continue further, we won't find
2310 // anything we don't already know
2311 break;
2312 }
2313 }
2314
2315 // we want to iterate over all items wrapping around if
2316 // necessary
2317 if ( ++n == count )
2318 {
2319 // wrap
2320 n = 0;
2321 }
2322
2323 if ( n == idxStart )
2324 {
2325 // we've seen all items
2326 break;
2327 }
2328 }
2329
2330 return idxFound;
2331 }
2332
2333 #if wxUSE_ACCEL
2334
2335 bool wxMenuBar::ProcessAccelEvent(const wxKeyEvent& event)
2336 {
2337 size_t n = 0;
2338 for ( wxMenuList::Node *node = m_menus.GetFirst();
2339 node;
2340 node = node->GetNext(), n++ )
2341 {
2342 // accels of the items in the disabled menus shouldn't work
2343 if ( m_menuInfos[n].IsEnabled() )
2344 {
2345 if ( node->GetData()->ProcessAccelEvent(event) )
2346 {
2347 // menu processed it
2348 return TRUE;
2349 }
2350 }
2351 }
2352
2353 // not found
2354 return FALSE;
2355 }
2356
2357 #endif // wxUSE_ACCEL
2358
2359 // ----------------------------------------------------------------------------
2360 // wxMenuBar menus showing
2361 // ----------------------------------------------------------------------------
2362
2363 void wxMenuBar::PopupCurrentMenu(bool selectFirst)
2364 {
2365 wxCHECK_RET( m_current != -1, _T("no menu to popup") );
2366
2367 // forgot to call DismissMenu()?
2368 wxASSERT_MSG( !m_menuShown, _T("shouldn't show two menus at once!") );
2369
2370 // in any case, we should show it - even if we won't
2371 m_shouldShowMenu = TRUE;
2372
2373 if ( IsEnabledTop(m_current) )
2374 {
2375 // remember the menu we show
2376 m_menuShown = GetMenu(m_current);
2377
2378 // we don't show the menu at all if it has no items
2379 if ( !m_menuShown->IsEmpty() )
2380 {
2381 // position it correctly: note that we must use screen coords and
2382 // that we pass 0 as width to position the menu exactly below the
2383 // item, not to the right of it
2384 wxRect rectItem = GetItemRect(m_current);
2385
2386 m_menuShown->Popup(ClientToScreen(rectItem.GetPosition()),
2387 wxSize(0, rectItem.GetHeight()),
2388 selectFirst);
2389 }
2390 else
2391 {
2392 // reset it back as no menu is shown
2393 m_menuShown = NULL;
2394 }
2395 }
2396 //else: don't show disabled menu
2397 }
2398
2399 void wxMenuBar::DismissMenu()
2400 {
2401 wxCHECK_RET( m_menuShown, _T("can't dismiss menu if none is shown") );
2402
2403 m_menuShown->Dismiss();
2404 OnDismissMenu();
2405 }
2406
2407 void wxMenuBar::OnDismissMenu(bool dismissMenuBar)
2408 {
2409 m_shouldShowMenu = FALSE;
2410 m_menuShown = NULL;
2411 if ( dismissMenuBar )
2412 {
2413 OnDismiss();
2414 }
2415 }
2416
2417 void wxMenuBar::OnDismiss()
2418 {
2419 if ( GetCapture() )
2420 {
2421 wxLogTrace(_T("mousecapture"), _T("Releasing mouse from wxMenuBar::OnDismiss"));
2422 GetCapture()->ReleaseMouse();
2423 }
2424
2425 if ( m_current != -1 )
2426 {
2427 size_t current = m_current;
2428 m_current = -1;
2429
2430 RefreshItem(current);
2431 }
2432
2433 GiveAwayFocus();
2434 }
2435
2436 void wxMenuBar::GiveAwayFocus()
2437 {
2438 GetFrame()->SetFocus();
2439 }
2440
2441 // ----------------------------------------------------------------------------
2442 // popup menu support
2443 // ----------------------------------------------------------------------------
2444
2445 wxEventLoop *wxWindow::ms_evtLoopPopup = NULL;
2446
2447 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
2448 {
2449 wxCHECK_MSG( !ms_evtLoopPopup, FALSE,
2450 _T("can't show more than one popup menu at a time") );
2451
2452 #ifdef __WXMSW__
2453 // we need to change the cursor before showing the menu as, apparently, no
2454 // cursor changes took place while the mouse is captured
2455 wxCursor cursorOld = GetCursor();
2456 SetCursor(wxCURSOR_ARROW);
2457 #endif // __WXMSW__
2458
2459 #if 0
2460 // flash any delayed log messages before showing the menu, otherwise it
2461 // could be dismissed (because it would lose focus) immediately after being
2462 // shown
2463 wxLog::FlushActive();
2464
2465 // some controls update themselves from OnIdle() call - let them do it
2466 wxTheApp->ProcessIdle();
2467
2468 // if the window hadn't been refreshed yet, the menu can adversely affect
2469 // its next OnPaint() handler execution - i.e. scrolled window refresh
2470 // logic breaks then as it scrolls part of the menu which hadn't been there
2471 // when the update event was generated into view
2472 Update();
2473 #endif // 0
2474
2475 menu->SetInvokingWindow(this);
2476
2477 // wxLogDebug( "Name of invoking window %s", menu->GetInvokingWindow()->GetName().c_str() );
2478
2479 menu->Popup(ClientToScreen(wxPoint(x, y)), wxSize(0, 0));
2480
2481 // this is not very useful if the menu was popped up because of the mouse
2482 // click but I think it is nice to do when it appears because of a key
2483 // press (i.e. Windows menu key)
2484 //
2485 // Windows itself doesn't do it, but IMHO this is nice
2486 WarpPointer(x, y);
2487
2488 // we have to redirect all keyboard input to the menu temporarily
2489 PushEventHandler(new wxMenuKbdRedirector(menu));
2490
2491 // enter the local modal loop
2492 ms_evtLoopPopup = new wxEventLoop;
2493 ms_evtLoopPopup->Run();
2494
2495 delete ms_evtLoopPopup;
2496 ms_evtLoopPopup = NULL;
2497
2498 // remove the handler
2499 PopEventHandler(TRUE /* delete it */);
2500
2501 menu->SetInvokingWindow(NULL);
2502
2503 #ifdef __WXMSW__
2504 SetCursor(cursorOld);
2505 #endif // __WXMSW__
2506
2507 return TRUE;
2508 }
2509
2510 void wxWindow::DismissPopupMenu()
2511 {
2512 wxCHECK_RET( ms_evtLoopPopup, _T("no popup menu shown") );
2513
2514 ms_evtLoopPopup->Exit();
2515 }
2516
2517 #endif // wxUSE_MENUS
2518