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