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