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