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