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