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