1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/univ/menu.cpp
3 // Purpose: wxMenuItem, wxMenu and wxMenuBar implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
29 #include "wx/dynarray.h"
30 #include "wx/control.h" // for FindAccelIndex()
32 #include "wx/settings.h"
37 #include "wx/popupwin.h"
38 #include "wx/evtloop.h"
39 #include "wx/dcclient.h"
42 #include "wx/univ/renderer.h"
45 #include "wx/msw/private.h"
48 // ----------------------------------------------------------------------------
49 // wxMenuInfo contains all extra information about top level menus we need
50 // ----------------------------------------------------------------------------
52 class WXDLLEXPORT wxMenuInfo
56 wxMenuInfo(const wxString
& text
)
64 void SetLabel(const wxString
& text
)
66 // remember the accel char (may be -1 if none)
67 m_indexAccel
= wxControl::FindAccelIndex(text
, &m_label
);
69 // calculate the width later, after the menu bar is created
73 void SetEnabled(bool enabled
= true) { m_isEnabled
= enabled
; }
77 const wxString
& GetLabel() const { return m_label
; }
78 bool IsEnabled() const { return m_isEnabled
; }
79 wxCoord
GetWidth(wxMenuBar
*menubar
) const
83 wxConstCast(this, wxMenuInfo
)->CalcWidth(menubar
);
89 int GetAccelIndex() const { return m_indexAccel
; }
92 void CalcWidth(wxMenuBar
*menubar
)
95 wxClientDC
dc(menubar
);
96 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
97 dc
.GetTextExtent(m_label
, &size
.x
, &size
.y
);
99 // adjust for the renderer we use and store the width
100 m_width
= menubar
->GetRenderer()->GetMenuBarItemSize(size
).x
;
109 #include "wx/arrimpl.cpp"
111 WX_DEFINE_OBJARRAY(wxMenuInfoArray
);
113 // ----------------------------------------------------------------------------
114 // wxPopupMenuWindow: a popup window showing a menu
115 // ----------------------------------------------------------------------------
117 class wxPopupMenuWindow
: public wxPopupTransientWindow
120 wxPopupMenuWindow(wxWindow
*parent
, wxMenu
*menu
);
122 ~wxPopupMenuWindow();
124 // override the base class version to select the first item initially
125 virtual void Popup(wxWindow
*focus
= NULL
);
127 // override the base class version to dismiss any open submenus
128 virtual void Dismiss();
130 // notify the menu when the window disappears from screen
131 virtual void OnDismiss();
133 // called when a submenu is dismissed
134 void OnSubmenuDismiss(bool dismissParent
);
136 // the default wxMSW wxPopupTransientWindow::OnIdle disables the capture
137 // when the cursor is inside the popup, which dsables the menu tracking
138 // so override it to do nothing
140 void OnIdle(wxIdleEvent
& WXUNUSED(event
)) { }
143 // get the currently selected item (may be NULL)
144 wxMenuItem
*GetCurrentItem() const
146 return m_nodeCurrent
? m_nodeCurrent
->GetData() : NULL
;
149 // find the menu item at given position
150 wxMenuItemList::compatibility_iterator
GetMenuItemFromPoint(const wxPoint
& pt
) const;
152 // refresh the given item
153 void RefreshItem(wxMenuItem
*item
);
155 // preselect the first item
156 void SelectFirst() { SetCurrent(m_menu
->GetMenuItems().GetFirst()); }
158 // process the key event, return true if done
159 bool ProcessKeyDown(int key
);
161 // process mouse move event
162 void ProcessMouseMove(const wxPoint
& pt
);
164 // don't dismiss the popup window if the parent menu was clicked
165 virtual bool ProcessLeftDown(wxMouseEvent
& event
);
168 // how did we perform this operation?
175 // draw the menu inside this window
176 virtual void DoDraw(wxControlRenderer
*renderer
);
179 void OnLeftUp(wxMouseEvent
& event
);
180 void OnMouseMove(wxMouseEvent
& event
);
181 void OnMouseLeave(wxMouseEvent
& event
);
182 void OnKeyDown(wxKeyEvent
& event
);
184 // reset the current item and node
187 // set the current node and item withotu refreshing anything
188 void SetCurrent(wxMenuItemList::compatibility_iterator node
);
189 virtual bool SetCurrent(bool doit
= true){return wxPopupTransientWindow::SetCurrent(doit
);};
191 // change the current item refreshing the old and new items
192 void ChangeCurrent(wxMenuItemList::compatibility_iterator node
);
194 // activate item, i.e. call either ClickItem() or OpenSubmenu() depending
195 // on what it is, return true if something was done (i.e. it's not a
197 bool ActivateItem(wxMenuItem
*item
, InputMethod how
= WithKeyboard
);
199 // send the event about the item click
200 void ClickItem(wxMenuItem
*item
);
202 // show the submenu for this item
203 void OpenSubmenu(wxMenuItem
*item
, InputMethod how
= WithKeyboard
);
205 // can this tiem be opened?
206 bool CanOpen(wxMenuItem
*item
)
208 return item
&& item
->IsEnabled() && item
->IsSubMenu();
211 // dismiss the menu and all parent menus too
212 void DismissAndNotify();
214 // react to dimissing this menu and also dismiss the parent if
216 void HandleDismiss(bool dismissParent
);
218 // do we have an open submenu?
219 bool HasOpenSubmenu() const { return m_hasOpenSubMenu
; }
221 // get previous node after the current one
222 wxMenuItemList::compatibility_iterator
GetPrevNode() const;
224 // get previous node before the given one, wrapping if it's the first one
225 wxMenuItemList::compatibility_iterator
GetPrevNode(wxMenuItemList::compatibility_iterator node
) const;
227 // get next node after the current one
228 wxMenuItemList::compatibility_iterator
GetNextNode() const;
230 // get next node after the given one, wrapping if it's the last one
231 wxMenuItemList::compatibility_iterator
GetNextNode(wxMenuItemList::compatibility_iterator node
) const;
237 // the menu node corresponding to the current item
238 wxMenuItemList::compatibility_iterator m_nodeCurrent
;
240 // do we currently have an opened submenu?
241 bool m_hasOpenSubMenu
;
243 DECLARE_EVENT_TABLE()
246 // ----------------------------------------------------------------------------
247 // wxMenuKbdRedirector: an event handler which redirects kbd input to wxMenu
248 // ----------------------------------------------------------------------------
250 class wxMenuKbdRedirector
: public wxEvtHandler
253 wxMenuKbdRedirector(wxMenu
*menu
) { m_menu
= menu
; }
255 virtual bool ProcessEvent(wxEvent
& event
)
257 if ( event
.GetEventType() == wxEVT_KEY_DOWN
)
259 return m_menu
->ProcessKeyDown(((wxKeyEvent
&)event
).GetKeyCode());
265 return wxEvtHandler::ProcessEvent(event
);
273 // ----------------------------------------------------------------------------
275 // ----------------------------------------------------------------------------
277 IMPLEMENT_DYNAMIC_CLASS(wxMenu
, wxEvtHandler
)
278 IMPLEMENT_DYNAMIC_CLASS(wxMenuBar
, wxWindow
)
279 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem
, wxObject
)
281 BEGIN_EVENT_TABLE(wxPopupMenuWindow
, wxPopupTransientWindow
)
282 EVT_KEY_DOWN(wxPopupMenuWindow::OnKeyDown
)
284 EVT_LEFT_UP(wxPopupMenuWindow::OnLeftUp
)
285 EVT_MOTION(wxPopupMenuWindow::OnMouseMove
)
286 EVT_LEAVE_WINDOW(wxPopupMenuWindow::OnMouseLeave
)
288 EVT_IDLE(wxPopupMenuWindow::OnIdle
)
292 BEGIN_EVENT_TABLE(wxMenuBar
, wxMenuBarBase
)
293 EVT_KILL_FOCUS(wxMenuBar::OnKillFocus
)
295 EVT_KEY_DOWN(wxMenuBar::OnKeyDown
)
297 EVT_LEFT_DOWN(wxMenuBar::OnLeftDown
)
298 EVT_MOTION(wxMenuBar::OnMouseMove
)
301 // ============================================================================
303 // ============================================================================
305 // ----------------------------------------------------------------------------
307 // ----------------------------------------------------------------------------
309 wxPopupMenuWindow::wxPopupMenuWindow(wxWindow
*parent
, wxMenu
*menu
)
312 m_hasOpenSubMenu
= false;
316 (void)Create(parent
, wxBORDER_RAISED
);
318 SetCursor(wxCURSOR_ARROW
);
321 wxPopupMenuWindow::~wxPopupMenuWindow()
323 // When m_popupMenu in wxMenu is deleted because it
324 // is a child of an old menu bar being deleted (note: it does
325 // not get destroyed by the wxMenu destructor, but
326 // by DestroyChildren()), m_popupMenu should be reset to NULL.
328 m_menu
->m_popupMenu
= NULL
;
331 // ----------------------------------------------------------------------------
332 // wxPopupMenuWindow current item/node handling
333 // ----------------------------------------------------------------------------
335 void wxPopupMenuWindow::ResetCurrent()
338 SetCurrent(wxMenuItemList::compatibility_iterator());
340 SetCurrent((wxwxMenuItemListNode
*)NULL
);
344 void wxPopupMenuWindow::SetCurrent(wxMenuItemList::compatibility_iterator node
)
346 m_nodeCurrent
= node
;
349 void wxPopupMenuWindow::ChangeCurrent(wxMenuItemList::compatibility_iterator node
)
351 if ( node
!= m_nodeCurrent
)
353 wxMenuItemList::compatibility_iterator nodeOldCurrent
= m_nodeCurrent
;
355 m_nodeCurrent
= node
;
357 if ( nodeOldCurrent
)
359 wxMenuItem
*item
= nodeOldCurrent
->GetData();
360 wxCHECK_RET( item
, _T("no current item?") );
362 // if it was the currently opened menu, close it
363 if ( item
->IsSubMenu() && item
->GetSubMenu()->IsShown() )
365 item
->GetSubMenu()->Dismiss();
366 OnSubmenuDismiss( false );
373 RefreshItem(m_nodeCurrent
->GetData());
377 wxMenuItemList::compatibility_iterator
wxPopupMenuWindow::GetPrevNode() const
379 // return the last node if there had been no previously selected one
380 return m_nodeCurrent
? GetPrevNode(m_nodeCurrent
)
381 : m_menu
->GetMenuItems().GetLast();
384 wxMenuItemList::compatibility_iterator
385 wxPopupMenuWindow::GetPrevNode(wxMenuItemList::compatibility_iterator node
) const
389 node
= node
->GetPrevious();
392 node
= m_menu
->GetMenuItems().GetLast();
395 //else: the menu is empty
400 wxMenuItemList::compatibility_iterator
wxPopupMenuWindow::GetNextNode() const
402 // return the first node if there had been no previously selected one
403 return m_nodeCurrent
? GetNextNode(m_nodeCurrent
)
404 : m_menu
->GetMenuItems().GetFirst();
407 wxMenuItemList::compatibility_iterator
408 wxPopupMenuWindow::GetNextNode(wxMenuItemList::compatibility_iterator node
) const
412 node
= node
->GetNext();
415 node
= m_menu
->GetMenuItems().GetFirst();
418 //else: the menu is empty
423 // ----------------------------------------------------------------------------
424 // wxPopupMenuWindow popup/dismiss
425 // ----------------------------------------------------------------------------
427 void wxPopupMenuWindow::Popup(wxWindow
*focus
)
429 // check that the current item had been properly reset before
430 wxASSERT_MSG( !m_nodeCurrent
||
431 m_nodeCurrent
== m_menu
->GetMenuItems().GetFirst(),
432 _T("menu current item preselected incorrectly") );
434 wxPopupTransientWindow::Popup(focus
);
436 // the base class no-longer captures the mouse automatically when Popup
437 // is called, so do it here to allow the menu tracking to work
442 // ensure that this window is really on top of everything: without using
443 // SetWindowPos() it can be covered by its parent menu which is not
444 // really what we want
445 wxMenu
*menuParent
= m_menu
->GetParent();
448 wxPopupMenuWindow
*win
= menuParent
->m_popupMenu
;
450 // if we're shown, the parent menu must be also shown
451 wxCHECK_RET( win
, _T("parent menu is not shown?") );
453 if ( !::SetWindowPos(GetHwndOf(win
), GetHwnd(),
455 SWP_NOMOVE
| SWP_NOSIZE
| SWP_NOREDRAW
) )
457 wxLogLastError(_T("SetWindowPos(HWND_TOP)"));
465 void wxPopupMenuWindow::Dismiss()
467 if ( HasOpenSubmenu() )
469 wxMenuItem
*item
= GetCurrentItem();
470 wxCHECK_RET( item
&& item
->IsSubMenu(), _T("where is our open submenu?") );
472 wxPopupMenuWindow
*win
= item
->GetSubMenu()->m_popupMenu
;
473 wxCHECK_RET( win
, _T("opened submenu is not opened?") );
476 OnSubmenuDismiss( false );
479 wxPopupTransientWindow::Dismiss();
484 void wxPopupMenuWindow::OnDismiss()
486 // when we are dismissed because the user clicked elsewhere or we lost
487 // focus in any other way, hide the parent menu as well
491 void wxPopupMenuWindow::OnSubmenuDismiss(bool WXUNUSED(dismissParent
))
493 m_hasOpenSubMenu
= false;
496 void wxPopupMenuWindow::HandleDismiss(bool dismissParent
)
498 m_menu
->OnDismiss(dismissParent
);
501 void wxPopupMenuWindow::DismissAndNotify()
507 // ----------------------------------------------------------------------------
508 // wxPopupMenuWindow geometry
509 // ----------------------------------------------------------------------------
511 wxMenuItemList::compatibility_iterator
512 wxPopupMenuWindow::GetMenuItemFromPoint(const wxPoint
& pt
) const
514 // we only use the y coord normally, but still check x in case the point is
515 // outside the window completely
516 if ( wxWindow::HitTest(pt
) == wxHT_WINDOW_INSIDE
)
519 for ( wxMenuItemList::compatibility_iterator node
= m_menu
->GetMenuItems().GetFirst();
521 node
= node
->GetNext() )
523 wxMenuItem
*item
= node
->GetData();
524 y
+= item
->GetHeight();
534 return wxMenuItemList::compatibility_iterator();
540 // ----------------------------------------------------------------------------
541 // wxPopupMenuWindow drawing
542 // ----------------------------------------------------------------------------
544 void wxPopupMenuWindow::RefreshItem(wxMenuItem
*item
)
546 wxCHECK_RET( item
, _T("can't refresh NULL item") );
548 wxASSERT_MSG( IsShown(), _T("can't refresh menu which is not shown") );
550 // FIXME: -1 here because of SetLogicalOrigin(1, 1) in DoDraw()
551 RefreshRect(wxRect(0, item
->GetPosition() - 1,
552 m_menu
->GetGeometryInfo().GetSize().x
, item
->GetHeight()));
555 void wxPopupMenuWindow::DoDraw(wxControlRenderer
*renderer
)
557 // no clipping so far - do we need it? I don't think so as the menu is
558 // never partially covered as it is always on top of everything
560 wxDC
& dc
= renderer
->GetDC();
561 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
563 // FIXME: this should be done in the renderer, however when it is fixed
564 // wxPopupMenuWindow::RefreshItem() should be changed too!
565 dc
.SetLogicalOrigin(1, 1);
567 wxRenderer
*rend
= renderer
->GetRenderer();
570 const wxMenuGeometryInfo
& gi
= m_menu
->GetGeometryInfo();
571 for ( wxMenuItemList::compatibility_iterator node
= m_menu
->GetMenuItems().GetFirst();
573 node
= node
->GetNext() )
575 wxMenuItem
*item
= node
->GetData();
577 if ( item
->IsSeparator() )
579 rend
->DrawMenuSeparator(dc
, y
, gi
);
581 else // not a separator
584 if ( item
->IsCheckable() )
586 flags
|= wxCONTROL_CHECKABLE
;
588 if ( item
->IsChecked() )
590 flags
|= wxCONTROL_CHECKED
;
594 if ( !item
->IsEnabled() )
595 flags
|= wxCONTROL_DISABLED
;
597 if ( item
->IsSubMenu() )
598 flags
|= wxCONTROL_ISSUBMENU
;
600 if ( item
== GetCurrentItem() )
601 flags
|= wxCONTROL_SELECTED
;
605 if ( !item
->IsEnabled() )
607 bmp
= item
->GetDisabledBitmap();
612 // strangely enough, for unchecked item we use the
613 // "checked" bitmap because this is the default one - this
614 // explains this strange boolean expression
615 bmp
= item
->GetBitmap(!item
->IsCheckable() || item
->IsChecked());
624 item
->GetAccelString(),
627 item
->GetAccelIndex()
631 y
+= item
->GetHeight();
635 // ----------------------------------------------------------------------------
636 // wxPopupMenuWindow actions
637 // ----------------------------------------------------------------------------
639 void wxPopupMenuWindow::ClickItem(wxMenuItem
*item
)
641 wxCHECK_RET( item
, _T("can't click NULL item") );
643 wxASSERT_MSG( !item
->IsSeparator() && !item
->IsSubMenu(),
644 _T("can't click this item") );
646 wxMenu
* menu
= m_menu
;
651 menu
->ClickItem(item
);
654 void wxPopupMenuWindow::OpenSubmenu(wxMenuItem
*item
, InputMethod how
)
656 wxCHECK_RET( item
, _T("can't open NULL submenu") );
658 wxMenu
*submenu
= item
->GetSubMenu();
659 wxCHECK_RET( submenu
, _T("can only open submenus!") );
661 // FIXME: should take into account the border width
662 submenu
->Popup(ClientToScreen(wxPoint(0, item
->GetPosition())),
663 wxSize(m_menu
->GetGeometryInfo().GetSize().x
, 0),
664 how
== WithKeyboard
/* preselect first item then */);
666 m_hasOpenSubMenu
= true;
669 bool wxPopupMenuWindow::ActivateItem(wxMenuItem
*item
, InputMethod how
)
671 // don't activate disabled items
672 if ( !item
|| !item
->IsEnabled() )
677 // normal menu items generate commands, submenus can be opened and
678 // the separators don't do anything
679 if ( item
->IsSubMenu() )
681 OpenSubmenu(item
, how
);
683 else if ( !item
->IsSeparator() )
687 else // separator, can't activate
695 // ----------------------------------------------------------------------------
696 // wxPopupMenuWindow input handling
697 // ----------------------------------------------------------------------------
699 bool wxPopupMenuWindow::ProcessLeftDown(wxMouseEvent
& event
)
701 // wxPopupWindowHandler dismisses the window when the mouse is clicked
702 // outside it which is usually just fine, but there is one case when we
703 // don't want to do it: if the mouse was clicked on the parent submenu item
704 // which opens this menu, so check for it
706 wxPoint pos
= event
.GetPosition();
707 if ( HitTest(pos
.x
, pos
.y
) == wxHT_WINDOW_OUTSIDE
)
709 wxMenu
*menu
= m_menu
->GetParent();
712 wxPopupMenuWindow
*win
= menu
->m_popupMenu
;
714 wxCHECK_MSG( win
, false, _T("parent menu not shown?") );
716 pos
= ClientToScreen(pos
);
717 if ( win
->GetMenuItemFromPoint(win
->ScreenToClient(pos
)) )
722 //else: it is outside the parent menu as well, do dismiss this one
729 void wxPopupMenuWindow::OnLeftUp(wxMouseEvent
& event
)
731 wxMenuItemList::compatibility_iterator node
= GetMenuItemFromPoint(event
.GetPosition());
734 ActivateItem(node
->GetData(), WithMouse
);
738 void wxPopupMenuWindow::OnMouseMove(wxMouseEvent
& event
)
740 const wxPoint pt
= event
.GetPosition();
742 // we need to ignore extra mouse events: example when this happens is when
743 // the mouse is on the menu and we open a submenu from keyboard - Windows
744 // then sends us a dummy mouse move event, we (correctly) determine that it
745 // happens in the parent menu and so immediately close the just opened
748 static wxPoint s_ptLast
;
749 wxPoint ptCur
= ClientToScreen(pt
);
750 if ( ptCur
== s_ptLast
)
758 ProcessMouseMove(pt
);
763 void wxPopupMenuWindow::ProcessMouseMove(const wxPoint
& pt
)
765 wxMenuItemList::compatibility_iterator node
= GetMenuItemFromPoint(pt
);
767 // don't reset current to NULL here, we only do it when the mouse leaves
768 // the window (see below)
771 if ( node
!= m_nodeCurrent
)
775 wxMenuItem
*item
= GetCurrentItem();
778 OpenSubmenu(item
, WithMouse
);
781 //else: same item, nothing to do
783 else // not on an item
785 // the last open submenu forwards the mouse move messages to its
786 // parent, so if the mouse moves to another item of the parent menu,
787 // this menu is closed and this other item is selected - in the similar
788 // manner, the top menu forwards the mouse moves to the menubar which
789 // allows to select another top level menu by just moving the mouse
791 // we need to translate our client coords to the client coords of the
792 // window we forward this event to
793 wxPoint ptScreen
= ClientToScreen(pt
);
795 // if the mouse is outside this menu, let the parent one to
797 wxMenu
*menuParent
= m_menu
->GetParent();
800 wxPopupMenuWindow
*win
= menuParent
->m_popupMenu
;
802 // if we're shown, the parent menu must be also shown
803 wxCHECK_RET( win
, _T("parent menu is not shown?") );
805 win
->ProcessMouseMove(win
->ScreenToClient(ptScreen
));
807 else // no parent menu
809 wxMenuBar
*menubar
= m_menu
->GetMenuBar();
812 if ( menubar
->ProcessMouseEvent(
813 menubar
->ScreenToClient(ptScreen
)) )
815 // menubar has closed this menu and opened another one, probably
820 //else: top level popup menu, no other processing to do
824 void wxPopupMenuWindow::OnMouseLeave(wxMouseEvent
& event
)
826 // due to the artefact of mouse events generation under MSW, we actually
827 // may get the mouse leave event after the menu had been already dismissed
828 // and calling ChangeCurrent() would then assert, so don't do it
831 // we shouldn't change the current them if our submenu is opened and
832 // mouse moved there, in this case the submenu is responsable for
835 if ( HasOpenSubmenu() )
837 wxMenuItem
*item
= GetCurrentItem();
838 wxCHECK_RET( CanOpen(item
), _T("where is our open submenu?") );
840 wxPopupMenuWindow
*win
= item
->GetSubMenu()->m_popupMenu
;
841 wxCHECK_RET( win
, _T("submenu is opened but not shown?") );
843 // only handle this event if the mouse is not inside the submenu
844 wxPoint pt
= ClientToScreen(event
.GetPosition());
846 win
->HitTest(win
->ScreenToClient(pt
)) == wxHT_WINDOW_OUTSIDE
;
850 // this menu is the last opened
857 ChangeCurrent(wxMenuItemList::compatibility_iterator());
867 void wxPopupMenuWindow::OnKeyDown(wxKeyEvent
& event
)
869 wxMenuBar
*menubar
= m_menu
->GetMenuBar();
873 menubar
->ProcessEvent(event
);
875 else if ( !ProcessKeyDown(event
.GetKeyCode()) )
881 bool wxPopupMenuWindow::ProcessKeyDown(int key
)
883 wxMenuItem
*item
= GetCurrentItem();
885 // first let the opened submenu to have it (no test for IsEnabled() here,
886 // the keys navigate even in a disabled submenu if we had somehow managed
887 // to open it inspit of this)
888 if ( HasOpenSubmenu() )
890 wxCHECK_MSG( CanOpen(item
), false,
891 _T("has open submenu but another item selected?") );
893 if ( item
->GetSubMenu()->ProcessKeyDown(key
) )
897 bool processed
= true;
899 // handle the up/down arrows, home, end, esc and return here, pass the
900 // left/right arrows to the menu bar except when the right arrow can be
901 // used to open a submenu
905 // if we're not a top level menu, close us, else leave this to the
907 if ( !m_menu
->GetParent() )
916 // close just this menu
918 HandleDismiss(false);
922 processed
= ActivateItem(item
);
926 ChangeCurrent(m_menu
->GetMenuItems().GetFirst());
930 ChangeCurrent(m_menu
->GetMenuItems().GetLast());
936 bool up
= key
== WXK_UP
;
938 wxMenuItemList::compatibility_iterator nodeStart
= up
? GetPrevNode()
941 while ( node
&& node
->GetData()->IsSeparator() )
943 node
= up
? GetPrevNode(node
) : GetNextNode(node
);
945 if ( node
== nodeStart
)
947 // nothing but separators and disabled items in this
950 node
= wxMenuItemList::compatibility_iterator();
969 // don't try to reopen an already opened menu
970 if ( !HasOpenSubmenu() && CanOpen(item
) )
981 // look for the menu item starting with this letter
982 if ( wxIsalnum((wxChar
)key
) )
984 // we want to start from the item after this one because
985 // if we're already on the item with the given accel we want to
986 // go to the next one, not to stay in place
987 wxMenuItemList::compatibility_iterator nodeStart
= GetNextNode();
989 // do we have more than one item with this accel?
990 bool notUnique
= false;
992 // translate everything to lower case before comparing
993 wxChar chAccel
= (wxChar
)wxTolower(key
);
995 // loop through all items searching for the item with this
997 wxMenuItemList::compatibility_iterator node
= nodeStart
,
999 nodeFound
= wxMenuItemList::compatibility_iterator();
1005 item
= node
->GetData();
1007 int idxAccel
= item
->GetAccelIndex();
1008 if ( idxAccel
!= -1 &&
1009 wxTolower(item
->GetLabel()[(size_t)idxAccel
])
1012 // ok, found an item with this accel
1015 // store it but continue searching as we need to
1016 // know if it's the only item with this accel or if
1020 else // we already had found such item
1024 // no need to continue further, we won't find
1025 // anything we don't already know
1030 // we want to iterate over all items wrapping around if
1032 node
= GetNextNode(node
);
1033 if ( node
== nodeStart
)
1035 // we've seen all nodes
1042 item
= nodeFound
->GetData();
1044 // go to this item anyhow
1045 ChangeCurrent(nodeFound
);
1047 if ( !notUnique
&& item
->IsEnabled() )
1049 // unique item with this accel - activate it
1050 processed
= ActivateItem(item
);
1052 //else: just select it but don't activate as the user might
1053 // have wanted to activate another item
1055 // skip "processed = false" below
1066 // ----------------------------------------------------------------------------
1068 // ----------------------------------------------------------------------------
1076 m_startRadioGroup
= -1;
1085 // ----------------------------------------------------------------------------
1086 // wxMenu and wxMenuGeometryInfo
1087 // ----------------------------------------------------------------------------
1089 wxMenuGeometryInfo::~wxMenuGeometryInfo()
1093 const wxMenuGeometryInfo
& wxMenu::GetGeometryInfo() const
1099 wxConstCast(this, wxMenu
)->m_geometry
=
1100 m_popupMenu
->GetRenderer()->GetMenuGeometry(m_popupMenu
, *this);
1104 wxFAIL_MSG( _T("can't get geometry without window") );
1111 void wxMenu::InvalidateGeometryInfo()
1120 // ----------------------------------------------------------------------------
1121 // wxMenu adding/removing items
1122 // ----------------------------------------------------------------------------
1124 void wxMenu::OnItemAdded(wxMenuItem
*item
)
1126 InvalidateGeometryInfo();
1130 #endif // wxUSE_ACCEL
1132 // the submenus of a popup menu should have the same invoking window as it
1134 if ( m_invokingWindow
&& item
->IsSubMenu() )
1136 item
->GetSubMenu()->SetInvokingWindow(m_invokingWindow
);
1140 void wxMenu::EndRadioGroup()
1142 // we're not inside a radio group any longer
1143 m_startRadioGroup
= -1;
1146 wxMenuItem
* wxMenu::DoAppend(wxMenuItem
*item
)
1148 if ( item
->GetKind() == wxITEM_RADIO
)
1150 int count
= GetMenuItemCount();
1152 if ( m_startRadioGroup
== -1 )
1154 // start a new radio group
1155 m_startRadioGroup
= count
;
1157 // for now it has just one element
1158 item
->SetAsRadioGroupStart();
1159 item
->SetRadioGroupEnd(m_startRadioGroup
);
1161 else // extend the current radio group
1163 // we need to update its end item
1164 item
->SetRadioGroupStart(m_startRadioGroup
);
1165 wxMenuItemList::compatibility_iterator node
= GetMenuItems().Item(m_startRadioGroup
);
1169 node
->GetData()->SetRadioGroupEnd(count
);
1173 wxFAIL_MSG( _T("where is the radio group start item?") );
1177 else // not a radio item
1182 if ( !wxMenuBase::DoAppend(item
) )
1190 wxMenuItem
* wxMenu::DoInsert(size_t pos
, wxMenuItem
*item
)
1192 if ( !wxMenuBase::DoInsert(pos
, item
) )
1200 wxMenuItem
*wxMenu::DoRemove(wxMenuItem
*item
)
1202 wxMenuItem
*itemOld
= wxMenuBase::DoRemove(item
);
1206 InvalidateGeometryInfo();
1209 RemoveAccelFor(item
);
1210 #endif // wxUSE_ACCEL
1216 // ----------------------------------------------------------------------------
1217 // wxMenu attaching/detaching
1218 // ----------------------------------------------------------------------------
1220 void wxMenu::Attach(wxMenuBarBase
*menubar
)
1222 wxMenuBase::Attach(menubar
);
1224 wxCHECK_RET( m_menuBar
, _T("menubar can't be NULL after attaching") );
1226 // unfortunately, we can't use m_menuBar->GetEventHandler() here because,
1227 // if the menubar is currently showing a menu, its event handler is a
1228 // temporary one installed by wxPopupWindow and so will disappear soon any
1229 // any attempts to use it from the newly attached menu would result in a
1232 // so we use the menubar itself, even if it's a pity as it means we can't
1233 // redirect all menu events by changing the menubar handler (FIXME)
1234 SetNextHandler(m_menuBar
);
1237 void wxMenu::Detach()
1239 wxMenuBase::Detach();
1242 // ----------------------------------------------------------------------------
1243 // wxMenu misc functions
1244 // ----------------------------------------------------------------------------
1246 wxWindow
*wxMenu::GetRootWindow() const
1250 // simple case - a normal menu attached to the menubar
1251 return GetMenuBar();
1254 // we're a popup menu but the trouble is that only the top level popup menu
1255 // has a pointer to the invoking window, so we must walk up the menu chain
1257 wxWindow
*win
= GetInvokingWindow();
1260 // we already have it
1264 wxMenu
*menu
= GetParent();
1267 // We are a submenu of a menu of a menubar
1268 if (menu
->GetMenuBar())
1269 return menu
->GetMenuBar();
1271 win
= menu
->GetInvokingWindow();
1275 menu
= menu
->GetParent();
1278 // we're probably going to crash in the caller anyhow, but try to detect
1279 // this error as soon as possible
1280 wxASSERT_MSG( win
, _T("menu without any associated window?") );
1282 // also remember it in this menu so that we don't have to search for it the
1284 wxConstCast(this, wxMenu
)->m_invokingWindow
= win
;
1289 wxRenderer
*wxMenu::GetRenderer() const
1291 // we're going to crash without renderer!
1292 wxCHECK_MSG( m_popupMenu
, NULL
, _T("neither popup nor menubar menu?") );
1294 return m_popupMenu
->GetRenderer();
1297 void wxMenu::RefreshItem(wxMenuItem
*item
)
1299 // the item geometry changed, so our might have changed as well
1300 InvalidateGeometryInfo();
1304 // this would be a bug in IsShown()
1305 wxCHECK_RET( m_popupMenu
, _T("must have popup window if shown!") );
1307 // recalc geometry to update the item height and such
1308 (void)GetGeometryInfo();
1310 m_popupMenu
->RefreshItem(item
);
1314 // ----------------------------------------------------------------------------
1315 // wxMenu showing and hiding
1316 // ----------------------------------------------------------------------------
1318 bool wxMenu::IsShown() const
1320 return m_popupMenu
&& m_popupMenu
->IsShown();
1323 void wxMenu::OnDismiss(bool dismissParent
)
1327 // always notify the parent about submenu disappearance
1328 wxPopupMenuWindow
*win
= m_menuParent
->m_popupMenu
;
1331 win
->OnSubmenuDismiss( true );
1335 wxFAIL_MSG( _T("parent menu not shown?") );
1338 // and if we dismiss everything, propagate to parent
1339 if ( dismissParent
)
1341 // dismissParent is recursive
1342 m_menuParent
->Dismiss();
1343 m_menuParent
->OnDismiss(true);
1346 else // no parent menu
1348 // notify the menu bar if we're a top level menu
1351 m_menuBar
->OnDismissMenu(dismissParent
);
1355 wxCHECK_RET( m_invokingWindow
, _T("what kind of menu is this?") );
1357 m_invokingWindow
->DismissPopupMenu();
1359 // Why reset it here? We need it for sending the event to...
1360 // SetInvokingWindow(NULL);
1365 void wxMenu::Popup(const wxPoint
& pos
, const wxSize
& size
, bool selectFirst
)
1367 // create the popup window if not done yet
1370 m_popupMenu
= new wxPopupMenuWindow(GetRootWindow(), this);
1373 // select the first item unless disabled
1376 m_popupMenu
->SelectFirst();
1379 // the geometry might have changed since the last time we were shown, so
1381 m_popupMenu
->SetClientSize(GetGeometryInfo().GetSize());
1383 // position it as specified
1384 m_popupMenu
->Position(pos
, size
);
1386 // the menu can't have the focus itself (it is a Windows limitation), so
1387 // always keep the focus at the originating window
1388 wxWindow
*focus
= GetRootWindow();
1390 wxASSERT_MSG( focus
, _T("no window to keep focus on?") );
1393 m_popupMenu
->Popup(focus
);
1396 void wxMenu::Dismiss()
1398 wxCHECK_RET( IsShown(), _T("can't dismiss hidden menu") );
1400 m_popupMenu
->Dismiss();
1403 // ----------------------------------------------------------------------------
1404 // wxMenu event processing
1405 // ----------------------------------------------------------------------------
1407 bool wxMenu::ProcessKeyDown(int key
)
1409 wxCHECK_MSG( m_popupMenu
, false,
1410 _T("can't process key events if not shown") );
1412 return m_popupMenu
->ProcessKeyDown(key
);
1415 bool wxMenu::ClickItem(wxMenuItem
*item
)
1418 if ( item
->IsCheckable() )
1420 // update the item state
1421 isChecked
= !item
->IsChecked();
1423 item
->Check(isChecked
!= 0);
1431 return SendEvent(item
->GetId(), isChecked
);
1434 // ----------------------------------------------------------------------------
1435 // wxMenu accel support
1436 // ----------------------------------------------------------------------------
1440 bool wxMenu::ProcessAccelEvent(const wxKeyEvent
& event
)
1442 // do we have an item for this accel?
1443 wxMenuItem
*item
= m_accelTable
.GetMenuItem(event
);
1444 if ( item
&& item
->IsEnabled() )
1446 return ClickItem(item
);
1450 for ( wxMenuItemList::compatibility_iterator node
= GetMenuItems().GetFirst();
1452 node
= node
->GetNext() )
1454 const wxMenuItem
*item
= node
->GetData();
1455 if ( item
->IsSubMenu() && item
->IsEnabled() )
1458 if ( item
->GetSubMenu()->ProcessAccelEvent(event
) )
1468 void wxMenu::AddAccelFor(wxMenuItem
*item
)
1470 wxAcceleratorEntry
*accel
= item
->GetAccel();
1473 accel
->SetMenuItem(item
);
1475 m_accelTable
.Add(*accel
);
1481 void wxMenu::RemoveAccelFor(wxMenuItem
*item
)
1483 wxAcceleratorEntry
*accel
= item
->GetAccel();
1486 m_accelTable
.Remove(*accel
);
1492 #endif // wxUSE_ACCEL
1494 // ----------------------------------------------------------------------------
1495 // wxMenuItem construction
1496 // ----------------------------------------------------------------------------
1498 wxMenuItem::wxMenuItem(wxMenu
*parentMenu
,
1500 const wxString
& text
,
1501 const wxString
& help
,
1504 : wxMenuItemBase(parentMenu
, id
, text
, help
, kind
, subMenu
)
1507 m_height
= wxDefaultCoord
;
1509 m_radioGroup
.start
= -1;
1510 m_isRadioGroupStart
= false;
1512 m_bmpDisabled
= wxNullBitmap
;
1517 wxMenuItem::~wxMenuItem()
1521 // ----------------------------------------------------------------------------
1522 // wxMenuItemBase methods implemented here
1523 // ----------------------------------------------------------------------------
1526 wxMenuItem
*wxMenuItemBase::New(wxMenu
*parentMenu
,
1528 const wxString
& name
,
1529 const wxString
& help
,
1533 return new wxMenuItem(parentMenu
, id
, name
, help
, kind
, subMenu
);
1537 wxString
wxMenuItemBase::GetLabelFromText(const wxString
& text
)
1539 return wxStripMenuCodes(text
);
1542 // ----------------------------------------------------------------------------
1543 // wxMenuItem operations
1544 // ----------------------------------------------------------------------------
1546 void wxMenuItem::NotifyMenu()
1548 m_parentMenu
->RefreshItem(this);
1551 void wxMenuItem::UpdateAccelInfo()
1553 m_indexAccel
= wxControl::FindAccelIndex(m_text
);
1555 // will be empty if the text contains no TABs - ok
1556 m_strAccel
= m_text
.AfterFirst(_T('\t'));
1559 void wxMenuItem::SetText(const wxString
& text
)
1561 if ( text
!= m_text
)
1563 // first call the base class version to change m_text
1564 wxMenuItemBase::SetText(text
);
1572 void wxMenuItem::SetCheckable(bool checkable
)
1574 if ( checkable
!= IsCheckable() )
1576 wxMenuItemBase::SetCheckable(checkable
);
1582 void wxMenuItem::SetBitmaps(const wxBitmap
& bmpChecked
,
1583 const wxBitmap
& bmpUnchecked
)
1585 m_bmpChecked
= bmpChecked
;
1586 m_bmpUnchecked
= bmpUnchecked
;
1591 void wxMenuItem::Enable(bool enable
)
1593 if ( enable
!= m_isEnabled
)
1595 wxMenuItemBase::Enable(enable
);
1601 void wxMenuItem::Check(bool check
)
1603 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
1605 if ( m_isChecked
== check
)
1608 if ( GetKind() == wxITEM_RADIO
)
1610 // it doesn't make sense to uncheck a radio item - what would this do?
1614 // get the index of this item in the menu
1615 const wxMenuItemList
& items
= m_parentMenu
->GetMenuItems();
1616 int pos
= items
.IndexOf(this);
1617 wxCHECK_RET( pos
!= wxNOT_FOUND
,
1618 _T("menuitem not found in the menu items list?") );
1620 // get the radio group range
1624 if ( m_isRadioGroupStart
)
1626 // we already have all information we need
1628 end
= m_radioGroup
.end
;
1630 else // next radio group item
1632 // get the radio group end from the start item
1633 start
= m_radioGroup
.start
;
1634 end
= items
.Item(start
)->GetData()->m_radioGroup
.end
;
1637 // also uncheck all the other items in this radio group
1638 wxMenuItemList::compatibility_iterator node
= items
.Item(start
);
1639 for ( int n
= start
; n
<= end
&& node
; n
++ )
1643 node
->GetData()->m_isChecked
= false;
1645 node
= node
->GetNext();
1649 wxMenuItemBase::Check(check
);
1654 // radio group stuff
1655 // -----------------
1657 void wxMenuItem::SetAsRadioGroupStart()
1659 m_isRadioGroupStart
= true;
1662 void wxMenuItem::SetRadioGroupStart(int start
)
1664 wxASSERT_MSG( !m_isRadioGroupStart
,
1665 _T("should only be called for the next radio items") );
1667 m_radioGroup
.start
= start
;
1670 void wxMenuItem::SetRadioGroupEnd(int end
)
1672 wxASSERT_MSG( m_isRadioGroupStart
,
1673 _T("should only be called for the first radio item") );
1675 m_radioGroup
.end
= end
;
1678 // ----------------------------------------------------------------------------
1679 // wxMenuBar creation
1680 // ----------------------------------------------------------------------------
1682 void wxMenuBar::Init()
1690 m_shouldShowMenu
= false;
1693 wxMenuBar::wxMenuBar(size_t n
, wxMenu
*menus
[], const wxString titles
[], long WXUNUSED(style
))
1697 for (size_t i
= 0; i
< n
; ++i
)
1698 Append(menus
[i
], titles
[i
]);
1701 void wxMenuBar::Attach(wxFrame
*frame
)
1703 // maybe you really wanted to call Detach()?
1704 wxCHECK_RET( frame
, _T("wxMenuBar::Attach(NULL) called") );
1706 wxMenuBarBase::Attach(frame
);
1710 // reparent if necessary
1711 if ( m_frameLast
!= frame
)
1716 // show it back - was hidden by Detach()
1719 else // not created yet, do it now
1721 // we have no way to return the error from here anyhow :-(
1722 (void)Create(frame
, wxID_ANY
);
1724 SetCursor(wxCURSOR_ARROW
);
1726 SetFont(wxSystemSettings::GetFont(wxSYS_SYSTEM_FONT
));
1728 // calculate and set our height (it won't be changed any more)
1729 SetSize(wxDefaultCoord
, GetBestSize().y
);
1732 // remember the last frame which had us to avoid unnecessarily reparenting
1734 m_frameLast
= frame
;
1737 void wxMenuBar::Detach()
1739 // don't delete the window because we may be reattached later, just hide it
1745 wxMenuBarBase::Detach();
1748 wxMenuBar::~wxMenuBar()
1752 // ----------------------------------------------------------------------------
1753 // wxMenuBar adding/removing items
1754 // ----------------------------------------------------------------------------
1756 bool wxMenuBar::Append(wxMenu
*menu
, const wxString
& title
)
1758 return Insert(GetCount(), menu
, title
);
1761 bool wxMenuBar::Insert(size_t pos
, wxMenu
*menu
, const wxString
& title
)
1763 if ( !wxMenuBarBase::Insert(pos
, menu
, title
) )
1766 wxMenuInfo
*info
= new wxMenuInfo(title
);
1767 m_menuInfos
.Insert(info
, pos
);
1769 RefreshAllItemsAfter(pos
);
1774 wxMenu
*wxMenuBar::Replace(size_t pos
, wxMenu
*menu
, const wxString
& title
)
1776 wxMenu
*menuOld
= wxMenuBarBase::Replace(pos
, menu
, title
);
1780 wxMenuInfo
& info
= m_menuInfos
[pos
];
1782 info
.SetLabel(title
);
1784 // even if the old menu was disabled, the new one is not any more
1787 // even if we change only this one, the new label has different width,
1788 // so we need to refresh everything beyond this item as well
1789 RefreshAllItemsAfter(pos
);
1795 wxMenu
*wxMenuBar::Remove(size_t pos
)
1797 wxMenu
*menuOld
= wxMenuBarBase::Remove(pos
);
1801 m_menuInfos
.RemoveAt(pos
);
1803 // this doesn't happen too often, so don't try to be too smart - just
1804 // refresh everything
1811 // ----------------------------------------------------------------------------
1812 // wxMenuBar top level menus access
1813 // ----------------------------------------------------------------------------
1815 wxCoord
wxMenuBar::GetItemWidth(size_t pos
) const
1817 return m_menuInfos
[pos
].GetWidth(wxConstCast(this, wxMenuBar
));
1820 void wxMenuBar::EnableTop(size_t pos
, bool enable
)
1822 wxCHECK_RET( pos
< GetCount(), _T("invalid index in EnableTop") );
1824 if ( enable
!= m_menuInfos
[pos
].IsEnabled() )
1826 m_menuInfos
[pos
].SetEnabled(enable
);
1830 //else: nothing to do
1833 bool wxMenuBar::IsEnabledTop(size_t pos
) const
1835 wxCHECK_MSG( pos
< GetCount(), false, _T("invalid index in IsEnabledTop") );
1837 return m_menuInfos
[pos
].IsEnabled();
1840 void wxMenuBar::SetLabelTop(size_t pos
, const wxString
& label
)
1842 wxCHECK_RET( pos
< GetCount(), _T("invalid index in EnableTop") );
1844 if ( label
!= m_menuInfos
[pos
].GetLabel() )
1846 m_menuInfos
[pos
].SetLabel(label
);
1850 //else: nothing to do
1853 wxString
wxMenuBar::GetLabelTop(size_t pos
) const
1855 wxCHECK_MSG( pos
< GetCount(), wxEmptyString
, _T("invalid index in GetLabelTop") );
1857 return m_menuInfos
[pos
].GetLabel();
1860 // ----------------------------------------------------------------------------
1861 // wxMenuBar drawing
1862 // ----------------------------------------------------------------------------
1864 void wxMenuBar::RefreshAllItemsAfter(size_t pos
)
1868 // no need to refresh if nothing is shown yet
1872 wxRect rect
= GetItemRect(pos
);
1873 rect
.width
= GetClientSize().x
- rect
.x
;
1877 void wxMenuBar::RefreshItem(size_t pos
)
1879 wxCHECK_RET( pos
!= (size_t)-1,
1880 _T("invalid item in wxMenuBar::RefreshItem") );
1884 // no need to refresh if nothing is shown yet
1888 RefreshRect(GetItemRect(pos
));
1891 void wxMenuBar::DoDraw(wxControlRenderer
*renderer
)
1893 wxDC
& dc
= renderer
->GetDC();
1894 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
1896 // redraw only the items which must be redrawn
1898 // we don't have to use GetUpdateClientRect() here because our client rect
1899 // is the same as total one
1900 wxRect rectUpdate
= GetUpdateRegion().GetBox();
1902 int flagsMenubar
= GetStateFlags();
1906 rect
.height
= GetClientSize().y
;
1909 size_t count
= GetCount();
1910 for ( size_t n
= 0; n
< count
; n
++ )
1912 if ( x
> rectUpdate
.GetRight() )
1914 // all remaining items are to the right of rectUpdate
1919 rect
.width
= GetItemWidth(n
);
1921 if ( x
< rectUpdate
.x
)
1923 // this item is still to the left of rectUpdate
1927 int flags
= flagsMenubar
;
1928 if ( m_current
!= -1 && n
== (size_t)m_current
)
1930 flags
|= wxCONTROL_SELECTED
;
1933 if ( !IsEnabledTop(n
) )
1935 flags
|= wxCONTROL_DISABLED
;
1938 GetRenderer()->DrawMenuBarItem
1942 m_menuInfos
[n
].GetLabel(),
1944 m_menuInfos
[n
].GetAccelIndex()
1949 // ----------------------------------------------------------------------------
1950 // wxMenuBar geometry
1951 // ----------------------------------------------------------------------------
1953 wxRect
wxMenuBar::GetItemRect(size_t pos
) const
1955 wxASSERT_MSG( pos
< GetCount(), _T("invalid menu bar item index") );
1956 wxASSERT_MSG( IsCreated(), _T("can't call this method yet") );
1961 rect
.height
= GetClientSize().y
;
1963 for ( size_t n
= 0; n
< pos
; n
++ )
1965 rect
.x
+= GetItemWidth(n
);
1968 rect
.width
= GetItemWidth(pos
);
1973 wxSize
wxMenuBar::DoGetBestClientSize() const
1976 if ( GetMenuCount() > 0 )
1978 wxClientDC
dc(wxConstCast(this, wxMenuBar
));
1979 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
1980 dc
.GetTextExtent(GetLabelTop(0), &size
.x
, &size
.y
);
1982 // adjust for the renderer we use
1983 size
= GetRenderer()->GetMenuBarItemSize(size
);
1985 else // empty menubar
1991 // the width is arbitrary, of course, for horizontal menubar
1997 int wxMenuBar::GetMenuFromPoint(const wxPoint
& pos
) const
1999 if ( pos
.x
< 0 || pos
.y
< 0 || pos
.y
> GetClientSize().y
)
2004 size_t count
= GetCount();
2005 for ( size_t item
= 0; item
< count
; item
++ )
2007 x
+= GetItemWidth(item
);
2015 // to the right of the last menu item
2019 // ----------------------------------------------------------------------------
2020 // wxMenuBar menu operations
2021 // ----------------------------------------------------------------------------
2023 void wxMenuBar::SelectMenu(size_t pos
)
2026 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::SelectMenu"));
2032 void wxMenuBar::DoSelectMenu(size_t pos
)
2034 wxCHECK_RET( pos
< GetCount(), _T("invalid menu index in DoSelectMenu") );
2036 int posOld
= m_current
;
2042 // close the previous menu
2043 if ( IsShowingMenu() )
2045 // restore m_shouldShowMenu flag after DismissMenu() which resets
2047 bool old
= m_shouldShowMenu
;
2051 m_shouldShowMenu
= old
;
2054 RefreshItem((size_t)posOld
);
2060 void wxMenuBar::PopupMenu(size_t pos
)
2062 wxCHECK_RET( pos
< GetCount(), _T("invalid menu index in PopupCurrentMenu") );
2069 // ----------------------------------------------------------------------------
2070 // wxMenuBar input handing
2071 // ----------------------------------------------------------------------------
2074 Note that wxMenuBar doesn't use wxInputHandler but handles keyboard and
2075 mouse in the same way under all platforms. This is because it doesn't derive
2076 from wxControl (which works with input handlers) but directly from wxWindow.
2078 Also, menu bar input handling is rather simple, so maybe it's not really
2079 worth making it themeable - at least I've decided against doing it now as it
2080 would merging the changes back into trunk more difficult. But it still could
2081 be done later if really needed.
2084 void wxMenuBar::OnKillFocus(wxFocusEvent
& event
)
2086 if ( m_current
!= -1 )
2088 RefreshItem((size_t)m_current
);
2096 void wxMenuBar::OnLeftDown(wxMouseEvent
& event
)
2104 else // we didn't have mouse capture, capture it now
2106 m_current
= GetMenuFromPoint(event
.GetPosition());
2107 if ( m_current
== -1 )
2109 // unfortunately, we can't prevent wxMSW from giving us the focus,
2110 // so we can only give it back
2115 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::OnLeftDown"));
2118 // show it as selected
2119 RefreshItem((size_t)m_current
);
2122 PopupCurrentMenu(false /* don't select first item - as Windows does */);
2127 void wxMenuBar::OnMouseMove(wxMouseEvent
& event
)
2131 (void)ProcessMouseEvent(event
.GetPosition());
2139 bool wxMenuBar::ProcessMouseEvent(const wxPoint
& pt
)
2141 // a hack to ignore the extra mouse events MSW sends us: this is similar to
2142 // wxUSE_MOUSEEVENT_HACK in wxWin itself but it isn't enough for us here as
2143 // we get the messages from different windows (old and new popup menus for
2146 static wxPoint s_ptLast
;
2147 if ( pt
== s_ptLast
)
2155 int currentNew
= GetMenuFromPoint(pt
);
2156 if ( (currentNew
== -1) || (currentNew
== m_current
) )
2161 // select the new active item
2162 DoSelectMenu(currentNew
);
2164 // show the menu if we know that we should, even if we hadn't been showing
2165 // it before (this may happen if the previous menu was disabled)
2166 if ( m_shouldShowMenu
&& !m_menuShown
)
2168 // open the new menu if the old one we closed had been opened
2169 PopupCurrentMenu(false /* don't select first item - as Windows does */);
2175 void wxMenuBar::OnKeyDown(wxKeyEvent
& event
)
2177 // ensure that we have a current item - we might not have it if we're
2178 // given the focus with Alt or F10 press (and under GTK+ the menubar
2179 // somehow gets the keyboard events even when it doesn't have focus...)
2180 if ( m_current
== -1 )
2182 if ( !HasCapture() )
2186 else // we do have capture
2188 // we always maintain a valid current item while we're in modal
2189 // state (i.e. have the capture)
2190 wxFAIL_MSG( _T("how did we manage to lose current item?") );
2196 int key
= event
.GetKeyCode();
2198 // first let the menu have it
2199 if ( IsShowingMenu() && m_menuShown
->ProcessKeyDown(key
) )
2204 // cycle through the menu items when left/right arrows are pressed and open
2205 // the menu when up/down one is
2209 // Alt must be processed at wxWindow level too
2214 // remove the selection and give the focus away
2215 if ( m_current
!= -1 )
2217 if ( IsShowingMenu() )
2229 size_t count
= GetCount();
2232 // the item won't change anyhow
2235 //else: otherwise, it will
2237 // remember if we were showing a menu - if we did, we should
2238 // show the new menu after changing the item
2239 bool wasMenuOpened
= IsShowingMenu();
2240 if ( wasMenuOpened
)
2245 // cast is safe as we tested for -1 above
2246 size_t currentNew
= (size_t)m_current
;
2248 if ( key
== WXK_LEFT
)
2250 if ( currentNew
-- == 0 )
2251 currentNew
= count
- 1;
2255 if ( ++currentNew
== count
)
2259 DoSelectMenu(currentNew
);
2261 if ( wasMenuOpened
)
2276 // letters open the corresponding menu
2279 int idxFound
= FindNextItemForAccel(m_current
, key
, &unique
);
2281 if ( idxFound
!= -1 )
2283 if ( IsShowingMenu() )
2288 DoSelectMenu((size_t)idxFound
);
2290 // if the item is not unique, just select it but don't
2291 // activate as the user might have wanted to activate
2294 // also, don't try to open a disabled menu
2295 if ( unique
&& IsEnabledTop((size_t)idxFound
) )
2301 // skip the "event.Skip()" below
2310 // ----------------------------------------------------------------------------
2311 // wxMenuBar accel handling
2312 // ----------------------------------------------------------------------------
2314 int wxMenuBar::FindNextItemForAccel(int idxStart
, int key
, bool *unique
) const
2316 if ( !wxIsalnum((wxChar
)key
) )
2318 // we only support letters/digits as accels
2322 // do we have more than one item with this accel?
2326 // translate everything to lower case before comparing
2327 wxChar chAccel
= (wxChar
)wxTolower(key
);
2329 // the index of the item with this accel
2332 // loop through all items searching for the item with this
2333 // accel starting at the item after the current one
2334 int count
= GetCount();
2335 int n
= idxStart
== -1 ? 0 : idxStart
+ 1;
2346 const wxMenuInfo
& info
= m_menuInfos
[n
];
2348 int idxAccel
= info
.GetAccelIndex();
2349 if ( idxAccel
!= -1 &&
2350 wxTolower(info
.GetLabel()[(size_t)idxAccel
])
2353 // ok, found an item with this accel
2354 if ( idxFound
== -1 )
2356 // store it but continue searching as we need to
2357 // know if it's the only item with this accel or if
2361 else // we already had found such item
2366 // no need to continue further, we won't find
2367 // anything we don't already know
2372 // we want to iterate over all items wrapping around if
2380 if ( n
== idxStart
)
2382 // we've seen all items
2392 bool wxMenuBar::ProcessAccelEvent(const wxKeyEvent
& event
)
2395 for ( wxMenuList::compatibility_iterator node
= m_menus
.GetFirst();
2397 node
= node
->GetNext(), n
++ )
2399 // accels of the items in the disabled menus shouldn't work
2400 if ( m_menuInfos
[n
].IsEnabled() )
2402 if ( node
->GetData()->ProcessAccelEvent(event
) )
2404 // menu processed it
2414 #endif // wxUSE_ACCEL
2416 // ----------------------------------------------------------------------------
2417 // wxMenuBar menus showing
2418 // ----------------------------------------------------------------------------
2420 void wxMenuBar::PopupCurrentMenu(bool selectFirst
)
2422 wxCHECK_RET( m_current
!= -1, _T("no menu to popup") );
2424 // forgot to call DismissMenu()?
2425 wxASSERT_MSG( !m_menuShown
, _T("shouldn't show two menus at once!") );
2427 // in any case, we should show it - even if we won't
2428 m_shouldShowMenu
= true;
2430 if ( IsEnabledTop(m_current
) )
2432 // remember the menu we show
2433 m_menuShown
= GetMenu(m_current
);
2435 // we don't show the menu at all if it has no items
2436 if ( !m_menuShown
->IsEmpty() )
2438 // position it correctly: note that we must use screen coords and
2439 // that we pass 0 as width to position the menu exactly below the
2440 // item, not to the right of it
2441 wxRect rectItem
= GetItemRect(m_current
);
2443 m_menuShown
->SetInvokingWindow(m_frameLast
);
2445 m_menuShown
->Popup(ClientToScreen(rectItem
.GetPosition()),
2446 wxSize(0, rectItem
.GetHeight()),
2451 // reset it back as no menu is shown
2455 //else: don't show disabled menu
2458 void wxMenuBar::DismissMenu()
2460 wxCHECK_RET( m_menuShown
, _T("can't dismiss menu if none is shown") );
2462 m_menuShown
->Dismiss();
2466 void wxMenuBar::OnDismissMenu(bool dismissMenuBar
)
2468 m_shouldShowMenu
= false;
2470 if ( dismissMenuBar
)
2476 void wxMenuBar::OnDismiss()
2478 if ( ReleaseMouseCapture() )
2479 wxLogTrace(_T("mousecapture"), _T("Releasing mouse from wxMenuBar::OnDismiss"));
2481 if ( m_current
!= -1 )
2483 size_t current
= m_current
;
2486 RefreshItem(current
);
2492 bool wxMenuBar::ReleaseMouseCapture()
2495 // With wxX11, when a menu is closed by clicking away from it, a control
2496 // under the click will still get an event, even though the menu has the
2497 // capture (bug?). So that control may already have taken the capture by
2498 // this point, preventing us from releasing the menu's capture. So to work
2499 // around this, we release both captures, then put back the control's
2501 wxWindow
*capture
= GetCapture();
2504 capture
->ReleaseMouse();
2506 if ( capture
== this )
2509 bool had
= HasCapture();
2514 capture
->CaptureMouse();
2528 void wxMenuBar::GiveAwayFocus()
2530 GetFrame()->SetFocus();
2533 // ----------------------------------------------------------------------------
2534 // popup menu support
2535 // ----------------------------------------------------------------------------
2537 wxEventLoop
*wxWindow::ms_evtLoopPopup
= NULL
;
2539 bool wxWindow::DoPopupMenu(wxMenu
*menu
, int x
, int y
)
2541 wxCHECK_MSG( !ms_evtLoopPopup
, false,
2542 _T("can't show more than one popup menu at a time") );
2545 // we need to change the cursor before showing the menu as, apparently, no
2546 // cursor changes took place while the mouse is captured
2547 wxCursor cursorOld
= GetCursor();
2548 SetCursor(wxCURSOR_ARROW
);
2552 // flash any delayed log messages before showing the menu, otherwise it
2553 // could be dismissed (because it would lose focus) immediately after being
2555 wxLog::FlushActive();
2557 // some controls update themselves from OnIdle() call - let them do it
2558 wxTheApp
->ProcessIdle();
2560 // if the window hadn't been refreshed yet, the menu can adversely affect
2561 // its next OnPaint() handler execution - i.e. scrolled window refresh
2562 // logic breaks then as it scrolls part of the menu which hadn't been there
2563 // when the update event was generated into view
2567 menu
->SetInvokingWindow(this);
2569 // wxLogDebug( "Name of invoking window %s", menu->GetInvokingWindow()->GetName().c_str() );
2571 menu
->Popup(ClientToScreen(wxPoint(x
, y
)), wxSize(0,0));
2573 // this is not very useful if the menu was popped up because of the mouse
2574 // click but I think it is nice to do when it appears because of a key
2575 // press (i.e. Windows menu key)
2577 // Windows itself doesn't do it, but IMHO this is nice
2580 // we have to redirect all keyboard input to the menu temporarily
2581 PushEventHandler(new wxMenuKbdRedirector(menu
));
2583 // enter the local modal loop
2584 ms_evtLoopPopup
= new wxEventLoop
;
2585 ms_evtLoopPopup
->Run();
2587 delete ms_evtLoopPopup
;
2588 ms_evtLoopPopup
= NULL
;
2590 // remove the handler
2591 PopEventHandler(true /* delete it */);
2593 menu
->SetInvokingWindow(NULL
);
2596 SetCursor(cursorOld
);
2602 void wxWindow::DismissPopupMenu()
2604 wxCHECK_RET( ms_evtLoopPopup
, _T("no popup menu shown") );
2606 ms_evtLoopPopup
->Exit();
2609 #endif // wxUSE_MENUS