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