]> git.saurik.com Git - wxWidgets.git/blame - src/univ/menu.cpp
Better resizing code fro wxComboBox.
[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
1681void wxMenuBar::Attach(wxFrame *frame)
1682{
1683 // maybe you really wanted to call Detach()?
1684 wxCHECK_RET( frame, _T("wxMenuBar::Attach(NULL) called") );
1685
1686 wxMenuBarBase::Attach(frame);
1687
1688 if ( IsCreated() )
1689 {
1690 // reparent if necessary
1691 if ( m_frameLast != frame )
1692 {
1693 Reparent(frame);
1694 }
1695
1696 // show it back - was hidden by Detach()
1697 Show();
1698 }
1699 else // not created yet, do it now
1700 {
1701 // we have no way to return the error from here anyhow :-(
a290fa5a 1702 (void)Create(frame, wxID_ANY);
1e6feb95
VZ
1703
1704 SetCursor(wxCURSOR_ARROW);
1705
a756f210 1706 SetFont(wxSystemSettings::GetFont(wxSYS_SYSTEM_FONT));
02cbc27a
VZ
1707
1708 // calculate and set our height (it won't be changed any more)
a290fa5a 1709 SetSize(wxDefaultCoord, GetBestSize().y);
1e6feb95
VZ
1710 }
1711
1712 // remember the last frame which had us to avoid unnecessarily reparenting
1713 // above
1714 m_frameLast = frame;
1715}
1716
1717void wxMenuBar::Detach()
1718{
1719 // don't delete the window because we may be reattached later, just hide it
1720 if ( m_frameLast )
1721 {
1722 Hide();
1723 }
1724
1725 wxMenuBarBase::Detach();
1726}
1727
1728wxMenuBar::~wxMenuBar()
1729{
1730}
1731
1732// ----------------------------------------------------------------------------
1733// wxMenuBar adding/removing items
1734// ----------------------------------------------------------------------------
1735
1736bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1737{
1738 return Insert(GetCount(), menu, title);
1739}
1740
1741bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1742{
1743 if ( !wxMenuBarBase::Insert(pos, menu, title) )
a290fa5a 1744 return false;
1e6feb95
VZ
1745
1746 wxMenuInfo *info = new wxMenuInfo(title);
1747 m_menuInfos.Insert(info, pos);
1748
1749 RefreshAllItemsAfter(pos);
1750
a290fa5a 1751 return true;
1e6feb95
VZ
1752}
1753
1754wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
1755{
1756 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
1757
1758 if ( menuOld )
1759 {
1760 wxMenuInfo& info = m_menuInfos[pos];
1761
1762 info.SetLabel(title);
1763
1764 // even if the old menu was disabled, the new one is not any more
1765 info.SetEnabled();
1766
1767 // even if we change only this one, the new label has different width,
1768 // so we need to refresh everything beyond this item as well
1769 RefreshAllItemsAfter(pos);
1770 }
1771
1772 return menuOld;
1773}
1774
1775wxMenu *wxMenuBar::Remove(size_t pos)
1776{
1777 wxMenu *menuOld = wxMenuBarBase::Remove(pos);
1778
1779 if ( menuOld )
1780 {
1781 m_menuInfos.RemoveAt(pos);
1782
1783 // this doesn't happen too often, so don't try to be too smart - just
1784 // refresh everything
1785 Refresh();
1786 }
1787
1788 return menuOld;
1789}
1790
1791// ----------------------------------------------------------------------------
1792// wxMenuBar top level menus access
1793// ----------------------------------------------------------------------------
1794
1795wxCoord wxMenuBar::GetItemWidth(size_t pos) const
1796{
1797 return m_menuInfos[pos].GetWidth(wxConstCast(this, wxMenuBar));
1798}
1799
1800void wxMenuBar::EnableTop(size_t pos, bool enable)
1801{
1802 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1803
1804 if ( enable != m_menuInfos[pos].IsEnabled() )
1805 {
1806 m_menuInfos[pos].SetEnabled(enable);
1807
1808 RefreshItem(pos);
1809 }
1810 //else: nothing to do
1811}
1812
1813bool wxMenuBar::IsEnabledTop(size_t pos) const
1814{
a290fa5a 1815 wxCHECK_MSG( pos < GetCount(), false, _T("invalid index in IsEnabledTop") );
1e6feb95
VZ
1816
1817 return m_menuInfos[pos].IsEnabled();
1818}
1819
1820void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
1821{
1822 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1823
1824 if ( label != m_menuInfos[pos].GetLabel() )
1825 {
1826 m_menuInfos[pos].SetLabel(label);
1827
1828 RefreshItem(pos);
1829 }
1830 //else: nothing to do
1831}
1832
1833wxString wxMenuBar::GetLabelTop(size_t pos) const
1834{
0966aee3 1835 wxCHECK_MSG( pos < GetCount(), wxEmptyString, _T("invalid index in GetLabelTop") );
1e6feb95
VZ
1836
1837 return m_menuInfos[pos].GetLabel();
1838}
1839
1840// ----------------------------------------------------------------------------
1841// wxMenuBar drawing
1842// ----------------------------------------------------------------------------
1843
1844void wxMenuBar::RefreshAllItemsAfter(size_t pos)
1845{
4087064a
VZ
1846 if ( !IsCreated() )
1847 {
1848 // no need to refresh if nothing is shown yet
1849 return;
1850 }
1851
1e6feb95
VZ
1852 wxRect rect = GetItemRect(pos);
1853 rect.width = GetClientSize().x - rect.x;
1854 RefreshRect(rect);
1855}
1856
1857void wxMenuBar::RefreshItem(size_t pos)
1858{
1859 wxCHECK_RET( pos != (size_t)-1,
1860 _T("invalid item in wxMenuBar::RefreshItem") );
1861
4087064a
VZ
1862 if ( !IsCreated() )
1863 {
1864 // no need to refresh if nothing is shown yet
1865 return;
1866 }
1867
1e6feb95
VZ
1868 RefreshRect(GetItemRect(pos));
1869}
1870
1871void wxMenuBar::DoDraw(wxControlRenderer *renderer)
1872{
1873 wxDC& dc = renderer->GetDC();
a756f210 1874 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1e6feb95
VZ
1875
1876 // redraw only the items which must be redrawn
1877
1878 // we don't have to use GetUpdateClientRect() here because our client rect
1879 // is the same as total one
1880 wxRect rectUpdate = GetUpdateRegion().GetBox();
1881
1882 int flagsMenubar = GetStateFlags();
1883
1884 wxRect rect;
1885 rect.y = 0;
1886 rect.height = GetClientSize().y;
1887
1888 wxCoord x = 0;
1889 size_t count = GetCount();
1890 for ( size_t n = 0; n < count; n++ )
1891 {
1892 if ( x > rectUpdate.GetRight() )
1893 {
1894 // all remaining items are to the right of rectUpdate
1895 break;
1896 }
1897
1898 rect.x = x;
1899 rect.width = GetItemWidth(n);
1900 x += rect.width;
1901 if ( x < rectUpdate.x )
1902 {
1903 // this item is still to the left of rectUpdate
1904 continue;
1905 }
1906
1907 int flags = flagsMenubar;
1908 if ( m_current != -1 && n == (size_t)m_current )
1909 {
1910 flags |= wxCONTROL_SELECTED;
1911 }
1912
1913 if ( !IsEnabledTop(n) )
1914 {
1915 flags |= wxCONTROL_DISABLED;
1916 }
1917
1918 GetRenderer()->DrawMenuBarItem
1919 (
1920 dc,
1921 rect,
1922 m_menuInfos[n].GetLabel(),
1923 flags,
1924 m_menuInfos[n].GetAccelIndex()
1925 );
1926 }
1927}
1928
1929// ----------------------------------------------------------------------------
1930// wxMenuBar geometry
1931// ----------------------------------------------------------------------------
1932
1933wxRect wxMenuBar::GetItemRect(size_t pos) const
1934{
1935 wxASSERT_MSG( pos < GetCount(), _T("invalid menu bar item index") );
4087064a 1936 wxASSERT_MSG( IsCreated(), _T("can't call this method yet") );
1e6feb95
VZ
1937
1938 wxRect rect;
1939 rect.x =
1940 rect.y = 0;
1941 rect.height = GetClientSize().y;
1942
1943 for ( size_t n = 0; n < pos; n++ )
1944 {
1945 rect.x += GetItemWidth(n);
1946 }
1947
1948 rect.width = GetItemWidth(pos);
1949
1950 return rect;
1951}
1952
1953wxSize wxMenuBar::DoGetBestClientSize() const
1954{
1955 wxSize size;
1956 if ( GetMenuCount() > 0 )
1957 {
1958 wxClientDC dc(wxConstCast(this, wxMenuBar));
a756f210 1959 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1e6feb95
VZ
1960 dc.GetTextExtent(GetLabelTop(0), &size.x, &size.y);
1961
1962 // adjust for the renderer we use
1963 size = GetRenderer()->GetMenuBarItemSize(size);
1964 }
1965 else // empty menubar
1966 {
1967 size.x =
1968 size.y = 0;
1969 }
1970
1971 // the width is arbitrary, of course, for horizontal menubar
1972 size.x = 100;
1973
1974 return size;
1975}
1976
1977int wxMenuBar::GetMenuFromPoint(const wxPoint& pos) const
1978{
1979 if ( pos.x < 0 || pos.y < 0 || pos.y > GetClientSize().y )
1980 return -1;
1981
1982 // do find it
1983 wxCoord x = 0;
1984 size_t count = GetCount();
1985 for ( size_t item = 0; item < count; item++ )
1986 {
1987 x += GetItemWidth(item);
1988
1989 if ( x > pos.x )
1990 {
1991 return item;
1992 }
1993 }
1994
1995 // to the right of the last menu item
1996 return -1;
1997}
1998
1999// ----------------------------------------------------------------------------
2000// wxMenuBar menu operations
2001// ----------------------------------------------------------------------------
2002
2003void wxMenuBar::SelectMenu(size_t pos)
2004{
2005 SetFocus();
5a750e5a 2006 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::SelectMenu"));
1e6feb95
VZ
2007 CaptureMouse();
2008
2009 DoSelectMenu(pos);
2010}
2011
2012void wxMenuBar::DoSelectMenu(size_t pos)
2013{
2014 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in DoSelectMenu") );
2015
23d8bb2c
VZ
2016 int posOld = m_current;
2017
2018 m_current = pos;
2019
2020 if ( posOld != -1 )
1e6feb95
VZ
2021 {
2022 // close the previous menu
2023 if ( IsShowingMenu() )
2024 {
2025 // restore m_shouldShowMenu flag after DismissMenu() which resets
a290fa5a 2026 // it to false
1e6feb95
VZ
2027 bool old = m_shouldShowMenu;
2028
2029 DismissMenu();
2030
2031 m_shouldShowMenu = old;
2032 }
2033
23d8bb2c 2034 RefreshItem((size_t)posOld);
1e6feb95
VZ
2035 }
2036
1e6feb95
VZ
2037 RefreshItem(pos);
2038}
2039
2040void wxMenuBar::PopupMenu(size_t pos)
2041{
2042 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in PopupCurrentMenu") );
2043
2044 SetFocus();
2045 DoSelectMenu(pos);
2046 PopupCurrentMenu();
2047}
2048
2049// ----------------------------------------------------------------------------
2050// wxMenuBar input handing
2051// ----------------------------------------------------------------------------
2052
2053/*
2054 Note that wxMenuBar doesn't use wxInputHandler but handles keyboard and
2055 mouse in the same way under all platforms. This is because it doesn't derive
2056 from wxControl (which works with input handlers) but directly from wxWindow.
2057
2058 Also, menu bar input handling is rather simple, so maybe it's not really
2059 worth making it themeable - at least I've decided against doing it now as it
2060 would merging the changes back into trunk more difficult. But it still could
2061 be done later if really needed.
2062 */
2063
2064void wxMenuBar::OnKillFocus(wxFocusEvent& event)
2065{
2066 if ( m_current != -1 )
2067 {
2068 RefreshItem((size_t)m_current);
2069
2070 m_current = -1;
2071 }
2072
2073 event.Skip();
2074}
2075
2076void wxMenuBar::OnLeftDown(wxMouseEvent& event)
2077{
2078 if ( HasCapture() )
2079 {
2080 OnDismiss();
2081
2082 event.Skip();
2083 }
2084 else // we didn't have mouse capture, capture it now
2085 {
2086 m_current = GetMenuFromPoint(event.GetPosition());
2087 if ( m_current == -1 )
2088 {
2089 // unfortunately, we can't prevent wxMSW from giving us the focus,
2090 // so we can only give it back
2091 GiveAwayFocus();
2092 }
2093 else // on item
2094 {
a290fa5a 2095 wxLogTrace(_T("mousecapture"), _T("Capturing mouse from wxMenuBar::OnLeftDown"));
1e6feb95
VZ
2096 CaptureMouse();
2097
2098 // show it as selected
2099 RefreshItem((size_t)m_current);
2100
2101 // show the menu
a290fa5a 2102 PopupCurrentMenu(false /* don't select first item - as Windows does */);
1e6feb95
VZ
2103 }
2104 }
2105}
2106
2107void wxMenuBar::OnMouseMove(wxMouseEvent& event)
2108{
2109 if ( HasCapture() )
2110 {
2111 (void)ProcessMouseEvent(event.GetPosition());
2112 }
2113 else
2114 {
2115 event.Skip();
2116 }
2117}
2118
2119bool wxMenuBar::ProcessMouseEvent(const wxPoint& pt)
2120{
2121 // a hack to ignore the extra mouse events MSW sends us: this is similar to
2122 // wxUSE_MOUSEEVENT_HACK in wxWin itself but it isn't enough for us here as
2123 // we get the messages from different windows (old and new popup menus for
2124 // example)
2125#ifdef __WXMSW__
2126 static wxPoint s_ptLast;
2127 if ( pt == s_ptLast )
2128 {
a290fa5a 2129 return false;
1e6feb95
VZ
2130 }
2131
2132 s_ptLast = pt;
2133#endif // __WXMSW__
2134
2135 int currentNew = GetMenuFromPoint(pt);
2136 if ( (currentNew == -1) || (currentNew == m_current) )
2137 {
a290fa5a 2138 return false;
1e6feb95
VZ
2139 }
2140
2141 // select the new active item
2142 DoSelectMenu(currentNew);
2143
2144 // show the menu if we know that we should, even if we hadn't been showing
2145 // it before (this may happen if the previous menu was disabled)
54800df8 2146 if ( m_shouldShowMenu && !m_menuShown)
1e6feb95
VZ
2147 {
2148 // open the new menu if the old one we closed had been opened
a290fa5a 2149 PopupCurrentMenu(false /* don't select first item - as Windows does */);
1e6feb95
VZ
2150 }
2151
a290fa5a 2152 return true;
1e6feb95
VZ
2153}
2154
2155void wxMenuBar::OnKeyDown(wxKeyEvent& event)
2156{
23d8bb2c
VZ
2157 // ensure that we have a current item - we might not have it if we're
2158 // given the focus with Alt or F10 press (and under GTK+ the menubar
2159 // somehow gets the keyboard events even when it doesn't have focus...)
2160 if ( m_current == -1 )
2161 {
2162 if ( !HasCapture() )
2163 {
2164 SelectMenu(0);
2165 }
2166 else // we do have capture
2167 {
2168 // we always maintain a valid current item while we're in modal
2169 // state (i.e. have the capture)
2170 wxFAIL_MSG( _T("how did we manage to lose current item?") );
2171
2172 return;
2173 }
2174 }
1e6feb95
VZ
2175
2176 int key = event.GetKeyCode();
2177
2178 // first let the menu have it
2179 if ( IsShowingMenu() && m_menuShown->ProcessKeyDown(key) )
2180 {
2181 return;
2182 }
2183
2184 // cycle through the menu items when left/right arrows are pressed and open
2185 // the menu when up/down one is
2186 switch ( key )
2187 {
2188 case WXK_MENU:
2189 // Alt must be processed at wxWindow level too
2190 event.Skip();
2191 // fall through
2192
2193 case WXK_ESCAPE:
2194 // remove the selection and give the focus away
2195 if ( m_current != -1 )
2196 {
2197 if ( IsShowingMenu() )
2198 {
2199 DismissMenu();
2200 }
2201
2202 OnDismiss();
2203 }
2204 break;
2205
2206 case WXK_LEFT:
2207 case WXK_RIGHT:
2208 {
2209 size_t count = GetCount();
2210 if ( count == 1 )
2211 {
2212 // the item won't change anyhow
2213 break;
2214 }
2215 //else: otherwise, it will
2216
2217 // remember if we were showing a menu - if we did, we should
2218 // show the new menu after changing the item
2219 bool wasMenuOpened = IsShowingMenu();
2220 if ( wasMenuOpened )
2221 {
2222 DismissMenu();
2223 }
2224
2225 // cast is safe as we tested for -1 above
2226 size_t currentNew = (size_t)m_current;
2227
2228 if ( key == WXK_LEFT )
2229 {
2230 if ( currentNew-- == 0 )
2231 currentNew = count - 1;
2232 }
2233 else // right
2234 {
6522713c 2235 if ( ++currentNew == count )
1e6feb95
VZ
2236 currentNew = 0;
2237 }
2238
2239 DoSelectMenu(currentNew);
2240
2241 if ( wasMenuOpened )
2242 {
2243 PopupCurrentMenu();
2244 }
2245 }
2246 break;
2247
2248 case WXK_DOWN:
2249 case WXK_UP:
2250 case WXK_RETURN:
2251 // open the menu
2252 PopupCurrentMenu();
2253 break;
2254
2255 default:
2256 // letters open the corresponding menu
2257 {
2258 bool unique;
2259 int idxFound = FindNextItemForAccel(m_current, key, &unique);
2260
2261 if ( idxFound != -1 )
2262 {
2263 if ( IsShowingMenu() )
2264 {
2265 DismissMenu();
2266 }
2267
2268 DoSelectMenu((size_t)idxFound);
2269
2270 // if the item is not unique, just select it but don't
2271 // activate as the user might have wanted to activate
2272 // another item
2273 //
2274 // also, don't try to open a disabled menu
2275 if ( unique && IsEnabledTop((size_t)idxFound) )
2276 {
2277 // open the menu
2278 PopupCurrentMenu();
2279 }
2280
2281 // skip the "event.Skip()" below
2282 break;
2283 }
2284 }
2285
2286 event.Skip();
2287 }
2288}
2289
2290// ----------------------------------------------------------------------------
2291// wxMenuBar accel handling
2292// ----------------------------------------------------------------------------
2293
2294int wxMenuBar::FindNextItemForAccel(int idxStart, int key, bool *unique) const
2295{
32b13913 2296 if ( !wxIsalnum((wxChar)key) )
1e6feb95
VZ
2297 {
2298 // we only support letters/digits as accels
2299 return -1;
2300 }
2301
2302 // do we have more than one item with this accel?
2303 if ( unique )
a290fa5a 2304 *unique = true;
1e6feb95
VZ
2305
2306 // translate everything to lower case before comparing
de516ffe 2307 wxChar chAccel = (wxChar)wxTolower(key);
1e6feb95
VZ
2308
2309 // the index of the item with this accel
2310 int idxFound = -1;
2311
2312 // loop through all items searching for the item with this
2313 // accel starting at the item after the current one
2314 int count = GetCount();
2315 int n = idxStart == -1 ? 0 : idxStart + 1;
2316
2317 if ( n == count )
2318 {
2319 // wrap
2320 n = 0;
2321 }
2322
2323 idxStart = n;
2324 for ( ;; )
2325 {
2326 const wxMenuInfo& info = m_menuInfos[n];
2327
2328 int idxAccel = info.GetAccelIndex();
2329 if ( idxAccel != -1 &&
2330 wxTolower(info.GetLabel()[(size_t)idxAccel])
2331 == chAccel )
2332 {
2333 // ok, found an item with this accel
2334 if ( idxFound == -1 )
2335 {
2336 // store it but continue searching as we need to
2337 // know if it's the only item with this accel or if
2338 // there are more
2339 idxFound = n;
2340 }
2341 else // we already had found such item
2342 {
2343 if ( unique )
a290fa5a 2344 *unique = false;
1e6feb95
VZ
2345
2346 // no need to continue further, we won't find
2347 // anything we don't already know
2348 break;
2349 }
2350 }
2351
2352 // we want to iterate over all items wrapping around if
2353 // necessary
2354 if ( ++n == count )
2355 {
2356 // wrap
2357 n = 0;
2358 }
2359
2360 if ( n == idxStart )
2361 {
2362 // we've seen all items
2363 break;
2364 }
2365 }
2366
2367 return idxFound;
2368}
2369
2370#if wxUSE_ACCEL
2371
2372bool wxMenuBar::ProcessAccelEvent(const wxKeyEvent& event)
2373{
2374 size_t n = 0;
ac32ba44 2375 for ( wxMenuList::compatibility_iterator node = m_menus.GetFirst();
1e6feb95
VZ
2376 node;
2377 node = node->GetNext(), n++ )
2378 {
2379 // accels of the items in the disabled menus shouldn't work
2380 if ( m_menuInfos[n].IsEnabled() )
2381 {
2382 if ( node->GetData()->ProcessAccelEvent(event) )
2383 {
2384 // menu processed it
a290fa5a 2385 return true;
1e6feb95
VZ
2386 }
2387 }
2388 }
2389
2390 // not found
a290fa5a 2391 return false;
1e6feb95
VZ
2392}
2393
2394#endif // wxUSE_ACCEL
2395
2396// ----------------------------------------------------------------------------
2397// wxMenuBar menus showing
2398// ----------------------------------------------------------------------------
2399
2400void wxMenuBar::PopupCurrentMenu(bool selectFirst)
2401{
2402 wxCHECK_RET( m_current != -1, _T("no menu to popup") );
2403
2404 // forgot to call DismissMenu()?
8601b2e1 2405 wxASSERT_MSG( !m_menuShown, _T("shouldn't show two menus at once!") );
1e6feb95
VZ
2406
2407 // in any case, we should show it - even if we won't
a290fa5a 2408 m_shouldShowMenu = true;
1e6feb95
VZ
2409
2410 if ( IsEnabledTop(m_current) )
2411 {
2412 // remember the menu we show
2413 m_menuShown = GetMenu(m_current);
2414
2415 // we don't show the menu at all if it has no items
2416 if ( !m_menuShown->IsEmpty() )
2417 {
2418 // position it correctly: note that we must use screen coords and
2419 // that we pass 0 as width to position the menu exactly below the
2420 // item, not to the right of it
2421 wxRect rectItem = GetItemRect(m_current);
1f752a22 2422
c5536589
WS
2423 m_menuShown->SetInvokingWindow(m_frameLast);
2424
1e6feb95
VZ
2425 m_menuShown->Popup(ClientToScreen(rectItem.GetPosition()),
2426 wxSize(0, rectItem.GetHeight()),
2427 selectFirst);
2428 }
2429 else
2430 {
2431 // reset it back as no menu is shown
2432 m_menuShown = NULL;
2433 }
2434 }
2435 //else: don't show disabled menu
2436}
2437
2438void wxMenuBar::DismissMenu()
2439{
2440 wxCHECK_RET( m_menuShown, _T("can't dismiss menu if none is shown") );
2441
2442 m_menuShown->Dismiss();
2443 OnDismissMenu();
2444}
2445
2446void wxMenuBar::OnDismissMenu(bool dismissMenuBar)
2447{
a290fa5a 2448 m_shouldShowMenu = false;
1e6feb95
VZ
2449 m_menuShown = NULL;
2450 if ( dismissMenuBar )
2451 {
2452 OnDismiss();
2453 }
2454}
2455
2456void wxMenuBar::OnDismiss()
2457{
a9a05059 2458 if ( GetCapture() )
7edcafa4 2459 {
5a750e5a 2460 wxLogTrace(_T("mousecapture"), _T("Releasing mouse from wxMenuBar::OnDismiss"));
a9a05059 2461 GetCapture()->ReleaseMouse();
7edcafa4 2462 }
1e6feb95
VZ
2463
2464 if ( m_current != -1 )
2465 {
23d8bb2c 2466 size_t current = m_current;
1e6feb95 2467 m_current = -1;
23d8bb2c
VZ
2468
2469 RefreshItem(current);
1e6feb95
VZ
2470 }
2471
2472 GiveAwayFocus();
2473}
2474
2475void wxMenuBar::GiveAwayFocus()
2476{
2477 GetFrame()->SetFocus();
2478}
2479
2480// ----------------------------------------------------------------------------
2481// popup menu support
2482// ----------------------------------------------------------------------------
2483
2484wxEventLoop *wxWindow::ms_evtLoopPopup = NULL;
2485
2486bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
2487{
a290fa5a 2488 wxCHECK_MSG( !ms_evtLoopPopup, false,
1e6feb95
VZ
2489 _T("can't show more than one popup menu at a time") );
2490
2491#ifdef __WXMSW__
2492 // we need to change the cursor before showing the menu as, apparently, no
2493 // cursor changes took place while the mouse is captured
2494 wxCursor cursorOld = GetCursor();
2495 SetCursor(wxCURSOR_ARROW);
2496#endif // __WXMSW__
2497
2498#if 0
2499 // flash any delayed log messages before showing the menu, otherwise it
2500 // could be dismissed (because it would lose focus) immediately after being
2501 // shown
2502 wxLog::FlushActive();
2503
2504 // some controls update themselves from OnIdle() call - let them do it
e39af974 2505 wxTheApp->ProcessIdle();
1e6feb95
VZ
2506
2507 // if the window hadn't been refreshed yet, the menu can adversely affect
2508 // its next OnPaint() handler execution - i.e. scrolled window refresh
2509 // logic breaks then as it scrolls part of the menu which hadn't been there
2510 // when the update event was generated into view
2511 Update();
2512#endif // 0
2513
2514 menu->SetInvokingWindow(this);
e441e1f4 2515
ee351013 2516 // wxLogDebug( "Name of invoking window %s", menu->GetInvokingWindow()->GetName().c_str() );
e441e1f4 2517
8b5d5223 2518 menu->Popup(ClientToScreen(wxPoint(x, y)), wxSize());
1e6feb95
VZ
2519
2520 // this is not very useful if the menu was popped up because of the mouse
2521 // click but I think it is nice to do when it appears because of a key
2522 // press (i.e. Windows menu key)
2523 //
2524 // Windows itself doesn't do it, but IMHO this is nice
2525 WarpPointer(x, y);
2526
2527 // we have to redirect all keyboard input to the menu temporarily
2528 PushEventHandler(new wxMenuKbdRedirector(menu));
2529
2530 // enter the local modal loop
2531 ms_evtLoopPopup = new wxEventLoop;
2532 ms_evtLoopPopup->Run();
2533
2534 delete ms_evtLoopPopup;
2535 ms_evtLoopPopup = NULL;
2536
2537 // remove the handler
a290fa5a 2538 PopEventHandler(true /* delete it */);
1e6feb95
VZ
2539
2540 menu->SetInvokingWindow(NULL);
2541
2542#ifdef __WXMSW__
2543 SetCursor(cursorOld);
2544#endif // __WXMSW__
2545
a290fa5a 2546 return true;
1e6feb95
VZ
2547}
2548
2549void wxWindow::DismissPopupMenu()
2550{
2551 wxCHECK_RET( ms_evtLoopPopup, _T("no popup menu shown") );
e441e1f4 2552
1e6feb95
VZ
2553 ms_evtLoopPopup->Exit();
2554}
2555
2556#endif // wxUSE_MENUS
2557