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