]> git.saurik.com Git - wxWidgets.git/blame - src/univ/menu.cpp
implementation streamlined
[wxWidgets.git] / src / univ / menu.cpp
CommitLineData
1e6feb95 1/////////////////////////////////////////////////////////////////////////////
78f93365 2// Name: src/univ/menu.cpp
1e6feb95
VZ
3// Purpose: wxMenuItem, wxMenu and wxMenuBar implementation
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 25.08.00
7// RCS-ID: $Id$
442b35b5 8// Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
65571936 9// Licence: wxWindows licence
1e6feb95
VZ
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
1e6feb95
VZ
20#include "wx/wxprec.h"
21
22#ifdef __BORLANDC__
23 #pragma hdrstop
24#endif
25
78f93365
WS
26#if wxUSE_MENUS
27
1e6feb95
VZ
28#ifndef WX_PRECOMP
29 #include "wx/dynarray.h"
30 #include "wx/control.h" // for FindAccelIndex()
31 #include "wx/menu.h"
32 #include "wx/settings.h"
33 #include "wx/accel.h"
34 #include "wx/log.h"
35#endif // WX_PRECOMP
36
1e6feb95
VZ
37#include "wx/popupwin.h"
38#include "wx/evtloop.h"
8cb172b4
JS
39#include "wx/dcclient.h"
40#include "wx/frame.h"
1e6feb95
VZ
41
42#include "wx/univ/renderer.h"
43
44#ifdef __WXMSW__
45 #include "wx/msw/private.h"
46#endif // __WXMSW__
47
27b43624
VZ
48typedef wxMenuItemList::compatibility_iterator wxMenuItemIter;
49
1e6feb95
VZ
50// ----------------------------------------------------------------------------
51// wxMenuInfo contains all extra information about top level menus we need
52// ----------------------------------------------------------------------------
53
54class WXDLLEXPORT wxMenuInfo
55{
56public:
57 // ctor
58 wxMenuInfo(const wxString& text)
59 {
60 SetLabel(text);
61 SetEnabled();
62 }
63
64 // modifiers
65
66 void SetLabel(const wxString& text)
67 {
68 // remember the accel char (may be -1 if none)
69 m_indexAccel = wxControl::FindAccelIndex(text, &m_label);
70
71 // calculate the width later, after the menu bar is created
72 m_width = 0;
73 }
74
a290fa5a 75 void SetEnabled(bool enabled = true) { m_isEnabled = enabled; }
1e6feb95
VZ
76
77 // accessors
78
79 const wxString& GetLabel() const { return m_label; }
80 bool IsEnabled() const { return m_isEnabled; }
81 wxCoord GetWidth(wxMenuBar *menubar) const
82 {
83 if ( !m_width )
84 {
85 wxConstCast(this, wxMenuInfo)->CalcWidth(menubar);
86 }
87
88 return m_width;
89 }
90
91 int GetAccelIndex() const { return m_indexAccel; }
92
93private:
94 void CalcWidth(wxMenuBar *menubar)
95 {
96 wxSize size;
97 wxClientDC dc(menubar);
a756f210 98 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1e6feb95
VZ
99 dc.GetTextExtent(m_label, &size.x, &size.y);
100
101 // adjust for the renderer we use and store the width
102 m_width = menubar->GetRenderer()->GetMenuBarItemSize(size).x;
103 }
104
105 wxString m_label;
106 wxCoord m_width;
107 int m_indexAccel;
108 bool m_isEnabled;
109};
110
111#include "wx/arrimpl.cpp"
112
113WX_DEFINE_OBJARRAY(wxMenuInfoArray);
114
115// ----------------------------------------------------------------------------
116// wxPopupMenuWindow: a popup window showing a menu
117// ----------------------------------------------------------------------------
118
119class wxPopupMenuWindow : public wxPopupTransientWindow
120{
121public:
122 wxPopupMenuWindow(wxWindow *parent, wxMenu *menu);
e441e1f4 123
ea1ad04b 124 ~wxPopupMenuWindow();
1e6feb95
VZ
125
126 // override the base class version to select the first item initially
127 virtual void Popup(wxWindow *focus = NULL);
128
129 // override the base class version to dismiss any open submenus
130 virtual void Dismiss();
131
1e6feb95 132 // called when a submenu is dismissed
9f9f75b3 133 void OnSubmenuDismiss(bool dismissParent);
1e6feb95 134
ed5903b6
MW
135 // the default wxMSW wxPopupTransientWindow::OnIdle disables the capture
136 // when the cursor is inside the popup, which dsables the menu tracking
137 // so override it to do nothing
138#ifdef __WXMSW__
3ac88cb5 139 void OnIdle(wxIdleEvent& WXUNUSED(event)) { }
ed5903b6
MW
140#endif
141
1e6feb95
VZ
142 // get the currently selected item (may be NULL)
143 wxMenuItem *GetCurrentItem() const
144 {
145 return m_nodeCurrent ? m_nodeCurrent->GetData() : NULL;
146 }
147
148 // find the menu item at given position
27b43624 149 wxMenuItemIter GetMenuItemFromPoint(const wxPoint& pt) const;
1e6feb95
VZ
150
151 // refresh the given item
152 void RefreshItem(wxMenuItem *item);
153
154 // preselect the first item
27b43624 155 void SelectFirst() { SetCurrentItem(m_menu->GetMenuItems().GetFirst()); }
1e6feb95 156
a290fa5a 157 // process the key event, return true if done
1e6feb95
VZ
158 bool ProcessKeyDown(int key);
159
160 // process mouse move event
161 void ProcessMouseMove(const wxPoint& pt);
162
163 // don't dismiss the popup window if the parent menu was clicked
164 virtual bool ProcessLeftDown(wxMouseEvent& event);
165
166protected:
167 // how did we perform this operation?
168 enum InputMethod
169 {
170 WithKeyboard,
171 WithMouse
172 };
173
6f02a879
VZ
174 // notify the menu when the window disappears from screen
175 virtual void OnDismiss();
176
1e6feb95
VZ
177 // draw the menu inside this window
178 virtual void DoDraw(wxControlRenderer *renderer);
179
180 // event handlers
181 void OnLeftUp(wxMouseEvent& event);
182 void OnMouseMove(wxMouseEvent& event);
183 void OnMouseLeave(wxMouseEvent& event);
184 void OnKeyDown(wxKeyEvent& event);
185
186 // reset the current item and node
187 void ResetCurrent();
188
27b43624
VZ
189 // set the current node and item without refreshing anything
190 void SetCurrentItem(wxMenuItemIter node);
1e6feb95
VZ
191
192 // change the current item refreshing the old and new items
27b43624 193 void ChangeCurrent(wxMenuItemIter node);
1e6feb95
VZ
194
195 // activate item, i.e. call either ClickItem() or OpenSubmenu() depending
a290fa5a 196 // on what it is, return true if something was done (i.e. it's not a
1e6feb95
VZ
197 // separator...)
198 bool ActivateItem(wxMenuItem *item, InputMethod how = WithKeyboard);
199
200 // send the event about the item click
201 void ClickItem(wxMenuItem *item);
202
203 // show the submenu for this item
204 void OpenSubmenu(wxMenuItem *item, InputMethod how = WithKeyboard);
205
206 // can this tiem be opened?
207 bool CanOpen(wxMenuItem *item)
208 {
209 return item && item->IsEnabled() && item->IsSubMenu();
210 }
211
212 // dismiss the menu and all parent menus too
213 void DismissAndNotify();
214
215 // react to dimissing this menu and also dismiss the parent if
216 // dismissParent
217 void HandleDismiss(bool dismissParent);
218
219 // do we have an open submenu?
220 bool HasOpenSubmenu() const { return m_hasOpenSubMenu; }
221
222 // get previous node after the current one
27b43624 223 wxMenuItemIter GetPrevNode() const;
1e6feb95
VZ
224
225 // get previous node before the given one, wrapping if it's the first one
27b43624 226 wxMenuItemIter GetPrevNode(wxMenuItemIter node) const;
1e6feb95
VZ
227
228 // get next node after the current one
27b43624 229 wxMenuItemIter GetNextNode() const;
1e6feb95
VZ
230
231 // get next node after the given one, wrapping if it's the last one
27b43624 232 wxMenuItemIter GetNextNode(wxMenuItemIter node) const;
1e6feb95
VZ
233
234private:
235 // the menu we show
236 wxMenu *m_menu;
237
238 // the menu node corresponding to the current item
27b43624 239 wxMenuItemIter m_nodeCurrent;
1e6feb95
VZ
240
241 // do we currently have an opened submenu?
242 bool m_hasOpenSubMenu;
243
244 DECLARE_EVENT_TABLE()
245};
246
247// ----------------------------------------------------------------------------
248// wxMenuKbdRedirector: an event handler which redirects kbd input to wxMenu
249// ----------------------------------------------------------------------------
250
251class wxMenuKbdRedirector : public wxEvtHandler
252{
253public:
254 wxMenuKbdRedirector(wxMenu *menu) { m_menu = menu; }
255
256 virtual bool ProcessEvent(wxEvent& event)
257 {
258 if ( event.GetEventType() == wxEVT_KEY_DOWN )
259 {
260 return m_menu->ProcessKeyDown(((wxKeyEvent &)event).GetKeyCode());
261 }
262 else
263 {
a290fa5a 264 // return false;
e441e1f4 265
1e6feb95
VZ
266 return wxEvtHandler::ProcessEvent(event);
267 }
268 }
269
270private:
271 wxMenu *m_menu;
272};
273
274// ----------------------------------------------------------------------------
275// wxWin macros
276// ----------------------------------------------------------------------------
277
278IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
279IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow)
280IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
281
282BEGIN_EVENT_TABLE(wxPopupMenuWindow, wxPopupTransientWindow)
283 EVT_KEY_DOWN(wxPopupMenuWindow::OnKeyDown)
284
285 EVT_LEFT_UP(wxPopupMenuWindow::OnLeftUp)
286 EVT_MOTION(wxPopupMenuWindow::OnMouseMove)
287 EVT_LEAVE_WINDOW(wxPopupMenuWindow::OnMouseLeave)
ed5903b6
MW
288#ifdef __WXMSW__
289 EVT_IDLE(wxPopupMenuWindow::OnIdle)
290#endif
1e6feb95
VZ
291END_EVENT_TABLE()
292
293BEGIN_EVENT_TABLE(wxMenuBar, wxMenuBarBase)
294 EVT_KILL_FOCUS(wxMenuBar::OnKillFocus)
295
296 EVT_KEY_DOWN(wxMenuBar::OnKeyDown)
297
298 EVT_LEFT_DOWN(wxMenuBar::OnLeftDown)
299 EVT_MOTION(wxMenuBar::OnMouseMove)
300END_EVENT_TABLE()
301
302// ============================================================================
303// implementation
304// ============================================================================
305
306// ----------------------------------------------------------------------------
307// wxPopupMenuWindow
308// ----------------------------------------------------------------------------
309
310wxPopupMenuWindow::wxPopupMenuWindow(wxWindow *parent, wxMenu *menu)
311{
312 m_menu = menu;
a290fa5a 313 m_hasOpenSubMenu = false;
1e6feb95
VZ
314
315 ResetCurrent();
316
317 (void)Create(parent, wxBORDER_RAISED);
318
319 SetCursor(wxCURSOR_ARROW);
320}
321
ea1ad04b
RR
322wxPopupMenuWindow::~wxPopupMenuWindow()
323{
90b18154
JS
324 // When m_popupMenu in wxMenu is deleted because it
325 // is a child of an old menu bar being deleted (note: it does
326 // not get destroyed by the wxMenu destructor, but
327 // by DestroyChildren()), m_popupMenu should be reset to NULL.
328
329 m_menu->m_popupMenu = NULL;
ea1ad04b
RR
330}
331
1e6feb95
VZ
332// ----------------------------------------------------------------------------
333// wxPopupMenuWindow current item/node handling
334// ----------------------------------------------------------------------------
335
336void wxPopupMenuWindow::ResetCurrent()
337{
27b43624 338 SetCurrentItem(wxMenuItemIter());
1e6feb95
VZ
339}
340
27b43624 341void wxPopupMenuWindow::SetCurrentItem(wxMenuItemIter node)
1e6feb95
VZ
342{
343 m_nodeCurrent = node;
344}
345
27b43624 346void wxPopupMenuWindow::ChangeCurrent(wxMenuItemIter node)
1e6feb95
VZ
347{
348 if ( node != m_nodeCurrent )
349 {
27b43624 350 wxMenuItemIter nodeOldCurrent = m_nodeCurrent;
23d8bb2c
VZ
351
352 m_nodeCurrent = node;
353
354 if ( nodeOldCurrent )
1e6feb95 355 {
23d8bb2c 356 wxMenuItem *item = nodeOldCurrent->GetData();
1e6feb95
VZ
357 wxCHECK_RET( item, _T("no current item?") );
358
359 // if it was the currently opened menu, close it
360 if ( item->IsSubMenu() && item->GetSubMenu()->IsShown() )
361 {
362 item->GetSubMenu()->Dismiss();
9f9f75b3 363 OnSubmenuDismiss( false );
1e6feb95
VZ
364 }
365
366 RefreshItem(item);
367 }
368
1e6feb95
VZ
369 if ( m_nodeCurrent )
370 RefreshItem(m_nodeCurrent->GetData());
371 }
372}
373
27b43624 374wxMenuItemIter wxPopupMenuWindow::GetPrevNode() const
1e6feb95 375{
86171954
VZ
376 // return the last node if there had been no previously selected one
377 return m_nodeCurrent ? GetPrevNode(m_nodeCurrent)
378 : m_menu->GetMenuItems().GetLast();
1e6feb95
VZ
379}
380
27b43624
VZ
381wxMenuItemIter
382wxPopupMenuWindow::GetPrevNode(wxMenuItemIter node) const
1e6feb95
VZ
383{
384 if ( node )
385 {
386 node = node->GetPrevious();
387 if ( !node )
388 {
389 node = m_menu->GetMenuItems().GetLast();
390 }
391 }
392 //else: the menu is empty
393
394 return node;
395}
396
27b43624 397wxMenuItemIter wxPopupMenuWindow::GetNextNode() const
1e6feb95 398{
86171954
VZ
399 // return the first node if there had been no previously selected one
400 return m_nodeCurrent ? GetNextNode(m_nodeCurrent)
401 : m_menu->GetMenuItems().GetFirst();
1e6feb95
VZ
402}
403
27b43624
VZ
404wxMenuItemIter
405wxPopupMenuWindow::GetNextNode(wxMenuItemIter node) const
1e6feb95
VZ
406{
407 if ( node )
408 {
409 node = node->GetNext();
410 if ( !node )
411 {
412 node = m_menu->GetMenuItems().GetFirst();
413 }
414 }
415 //else: the menu is empty
416
417 return node;
418}
419
420// ----------------------------------------------------------------------------
421// wxPopupMenuWindow popup/dismiss
422// ----------------------------------------------------------------------------
423
424void wxPopupMenuWindow::Popup(wxWindow *focus)
425{
426 // check that the current item had been properly reset before
427 wxASSERT_MSG( !m_nodeCurrent ||
428 m_nodeCurrent == m_menu->GetMenuItems().GetFirst(),
429 _T("menu current item preselected incorrectly") );
430
431 wxPopupTransientWindow::Popup(focus);
432
ed5903b6
MW
433 // the base class no-longer captures the mouse automatically when Popup
434 // is called, so do it here to allow the menu tracking to work
435 if ( !HasCapture() )
436 CaptureMouse();
437
1e6feb95
VZ
438#ifdef __WXMSW__
439 // ensure that this window is really on top of everything: without using
440 // SetWindowPos() it can be covered by its parent menu which is not
441 // really what we want
442 wxMenu *menuParent = m_menu->GetParent();
443 if ( menuParent )
444 {
445 wxPopupMenuWindow *win = menuParent->m_popupMenu;
446
447 // if we're shown, the parent menu must be also shown
448 wxCHECK_RET( win, _T("parent menu is not shown?") );
449
450 if ( !::SetWindowPos(GetHwndOf(win), GetHwnd(),
451 0, 0, 0, 0,
452 SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW) )
453 {
454 wxLogLastError(_T("SetWindowPos(HWND_TOP)"));
455 }
456
457 Refresh();
458 }
459#endif // __WXMSW__
460}
461
462void wxPopupMenuWindow::Dismiss()
463{
464 if ( HasOpenSubmenu() )
465 {
466 wxMenuItem *item = GetCurrentItem();
467 wxCHECK_RET( item && item->IsSubMenu(), _T("where is our open submenu?") );
468
469 wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
470 wxCHECK_RET( win, _T("opened submenu is not opened?") );
471
472 win->Dismiss();
9f9f75b3 473 OnSubmenuDismiss( false );
1e6feb95
VZ
474 }
475
476 wxPopupTransientWindow::Dismiss();
d8809820
MW
477
478 ResetCurrent();
1e6feb95
VZ
479}
480
481void wxPopupMenuWindow::OnDismiss()
482{
483 // when we are dismissed because the user clicked elsewhere or we lost
484 // focus in any other way, hide the parent menu as well
a290fa5a 485 HandleDismiss(true);
1e6feb95
VZ
486}
487
d8809820 488void wxPopupMenuWindow::OnSubmenuDismiss(bool WXUNUSED(dismissParent))
9f9f75b3
WS
489{
490 m_hasOpenSubMenu = false;
9f9f75b3
WS
491}
492
1e6feb95
VZ
493void wxPopupMenuWindow::HandleDismiss(bool dismissParent)
494{
1e6feb95
VZ
495 m_menu->OnDismiss(dismissParent);
496}
497
498void wxPopupMenuWindow::DismissAndNotify()
499{
500 Dismiss();
a290fa5a 501 HandleDismiss(true);
1e6feb95
VZ
502}
503
504// ----------------------------------------------------------------------------
505// wxPopupMenuWindow geometry
506// ----------------------------------------------------------------------------
507
27b43624 508wxMenuItemIter
1e6feb95
VZ
509wxPopupMenuWindow::GetMenuItemFromPoint(const wxPoint& pt) const
510{
511 // we only use the y coord normally, but still check x in case the point is
512 // outside the window completely
513 if ( wxWindow::HitTest(pt) == wxHT_WINDOW_INSIDE )
514 {
515 wxCoord y = 0;
27b43624 516 for ( wxMenuItemIter node = m_menu->GetMenuItems().GetFirst();
1e6feb95
VZ
517 node;
518 node = node->GetNext() )
519 {
520 wxMenuItem *item = node->GetData();
521 y += item->GetHeight();
522 if ( y > pt.y )
523 {
524 // found
525 return node;
526 }
527 }
528 }
529
27b43624 530 return wxMenuItemIter();
1e6feb95
VZ
531}
532
533// ----------------------------------------------------------------------------
534// wxPopupMenuWindow drawing
535// ----------------------------------------------------------------------------
536
537void wxPopupMenuWindow::RefreshItem(wxMenuItem *item)
538{
539 wxCHECK_RET( item, _T("can't refresh NULL item") );
540
541 wxASSERT_MSG( IsShown(), _T("can't refresh menu which is not shown") );
542
543 // FIXME: -1 here because of SetLogicalOrigin(1, 1) in DoDraw()
544 RefreshRect(wxRect(0, item->GetPosition() - 1,
545 m_menu->GetGeometryInfo().GetSize().x, item->GetHeight()));
546}
547
548void wxPopupMenuWindow::DoDraw(wxControlRenderer *renderer)
549{
550 // no clipping so far - do we need it? I don't think so as the menu is
551 // never partially covered as it is always on top of everything
552
553 wxDC& dc = renderer->GetDC();
a756f210 554 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
1e6feb95
VZ
555
556 // FIXME: this should be done in the renderer, however when it is fixed
557 // wxPopupMenuWindow::RefreshItem() should be changed too!
558 dc.SetLogicalOrigin(1, 1);
559
560 wxRenderer *rend = renderer->GetRenderer();
561
562 wxCoord y = 0;
563 const wxMenuGeometryInfo& gi = m_menu->GetGeometryInfo();
27b43624 564 for ( wxMenuItemIter node = m_menu->GetMenuItems().GetFirst();
1e6feb95
VZ
565 node;
566 node = node->GetNext() )
567 {
568 wxMenuItem *item = node->GetData();
569
570 if ( item->IsSeparator() )
571 {
572 rend->DrawMenuSeparator(dc, y, gi);
573 }
574 else // not a separator
575 {
576 int flags = 0;
577 if ( item->IsCheckable() )
578 {
579 flags |= wxCONTROL_CHECKABLE;
580
581 if ( item->IsChecked() )
582 {
583 flags |= wxCONTROL_CHECKED;
584 }
585 }
586
587 if ( !item->IsEnabled() )
588 flags |= wxCONTROL_DISABLED;
589
590 if ( item->IsSubMenu() )
591 flags |= wxCONTROL_ISSUBMENU;
592
593 if ( item == GetCurrentItem() )
594 flags |= wxCONTROL_SELECTED;
595
dba6b4f8
JS
596 wxBitmap bmp;
597
598 if ( !item->IsEnabled() )
599 {
600 bmp = item->GetDisabledBitmap();
601 }
602
603 if ( !bmp.Ok() )
604 {
605 // strangely enough, for unchecked item we use the
606 // "checked" bitmap because this is the default one - this
607 // explains this strange boolean expression
608 bmp = item->GetBitmap(!item->IsCheckable() || item->IsChecked());
609 }
610
1e6feb95
VZ
611 rend->DrawMenuItem
612 (
613 dc,
614 y,
615 gi,
616 item->GetLabel(),
617 item->GetAccelString(),
dba6b4f8 618 bmp,
1e6feb95
VZ
619 flags,
620 item->GetAccelIndex()
621 );
622 }
623
624 y += item->GetHeight();
625 }
626}
627
628// ----------------------------------------------------------------------------
629// wxPopupMenuWindow actions
630// ----------------------------------------------------------------------------
631
632void wxPopupMenuWindow::ClickItem(wxMenuItem *item)
633{
634 wxCHECK_RET( item, _T("can't click NULL item") );
635
636 wxASSERT_MSG( !item->IsSeparator() && !item->IsSubMenu(),
637 _T("can't click this item") );
638
1f752a22 639 wxMenu* menu = m_menu;
1e6feb95
VZ
640
641 // close all menus
642 DismissAndNotify();
e441e1f4 643
1f752a22 644 menu->ClickItem(item);
1e6feb95
VZ
645}
646
647void wxPopupMenuWindow::OpenSubmenu(wxMenuItem *item, InputMethod how)
648{
649 wxCHECK_RET( item, _T("can't open NULL submenu") );
650
651 wxMenu *submenu = item->GetSubMenu();
652 wxCHECK_RET( submenu, _T("can only open submenus!") );
653
654 // FIXME: should take into account the border width
655 submenu->Popup(ClientToScreen(wxPoint(0, item->GetPosition())),
656 wxSize(m_menu->GetGeometryInfo().GetSize().x, 0),
657 how == WithKeyboard /* preselect first item then */);
658
a290fa5a 659 m_hasOpenSubMenu = true;
1e6feb95
VZ
660}
661
662bool wxPopupMenuWindow::ActivateItem(wxMenuItem *item, InputMethod how)
663{
664 // don't activate disabled items
665 if ( !item || !item->IsEnabled() )
666 {
a290fa5a 667 return false;
1e6feb95
VZ
668 }
669
670 // normal menu items generate commands, submenus can be opened and
671 // the separators don't do anything
672 if ( item->IsSubMenu() )
673 {
674 OpenSubmenu(item, how);
675 }
676 else if ( !item->IsSeparator() )
677 {
678 ClickItem(item);
679 }
680 else // separator, can't activate
681 {
a290fa5a 682 return false;
1e6feb95
VZ
683 }
684
a290fa5a 685 return true;
1e6feb95
VZ
686}
687
688// ----------------------------------------------------------------------------
689// wxPopupMenuWindow input handling
690// ----------------------------------------------------------------------------
691
692bool wxPopupMenuWindow::ProcessLeftDown(wxMouseEvent& event)
693{
694 // wxPopupWindowHandler dismisses the window when the mouse is clicked
695 // outside it which is usually just fine, but there is one case when we
696 // don't want to do it: if the mouse was clicked on the parent submenu item
697 // which opens this menu, so check for it
698
699 wxPoint pos = event.GetPosition();
700 if ( HitTest(pos.x, pos.y) == wxHT_WINDOW_OUTSIDE )
701 {
702 wxMenu *menu = m_menu->GetParent();
703 if ( menu )
704 {
705 wxPopupMenuWindow *win = menu->m_popupMenu;
706
a290fa5a 707 wxCHECK_MSG( win, false, _T("parent menu not shown?") );
e441e1f4 708
1e6feb95
VZ
709 pos = ClientToScreen(pos);
710 if ( win->GetMenuItemFromPoint(win->ScreenToClient(pos)) )
711 {
712 // eat the event
a290fa5a 713 return true;
1e6feb95
VZ
714 }
715 //else: it is outside the parent menu as well, do dismiss this one
716 }
717 }
718
a290fa5a 719 return false;
1e6feb95
VZ
720}
721
722void wxPopupMenuWindow::OnLeftUp(wxMouseEvent& event)
723{
27b43624 724 wxMenuItemIter node = GetMenuItemFromPoint(event.GetPosition());
1e6feb95
VZ
725 if ( node )
726 {
727 ActivateItem(node->GetData(), WithMouse);
728 }
729}
730
731void wxPopupMenuWindow::OnMouseMove(wxMouseEvent& event)
732{
733 const wxPoint pt = event.GetPosition();
734
735 // we need to ignore extra mouse events: example when this happens is when
736 // the mouse is on the menu and we open a submenu from keyboard - Windows
737 // then sends us a dummy mouse move event, we (correctly) determine that it
738 // happens in the parent menu and so immediately close the just opened
739 // submenu!
740#ifdef __WXMSW__
741 static wxPoint s_ptLast;
742 wxPoint ptCur = ClientToScreen(pt);
743 if ( ptCur == s_ptLast )
744 {
745 return;
746 }
747
748 s_ptLast = ptCur;
749#endif // __WXMSW__
750
751 ProcessMouseMove(pt);
752
753 event.Skip();
754}
755
756void wxPopupMenuWindow::ProcessMouseMove(const wxPoint& pt)
757{
27b43624 758 wxMenuItemIter node = GetMenuItemFromPoint(pt);
1e6feb95
VZ
759
760 // don't reset current to NULL here, we only do it when the mouse leaves
761 // the window (see below)
762 if ( node )
763 {
764 if ( node != m_nodeCurrent )
765 {
766 ChangeCurrent(node);
767
768 wxMenuItem *item = GetCurrentItem();
769 if ( CanOpen(item) )
770 {
771 OpenSubmenu(item, WithMouse);
772 }
773 }
774 //else: same item, nothing to do
775 }
776 else // not on an item
777 {
778 // the last open submenu forwards the mouse move messages to its
779 // parent, so if the mouse moves to another item of the parent menu,
780 // this menu is closed and this other item is selected - in the similar
781 // manner, the top menu forwards the mouse moves to the menubar which
782 // allows to select another top level menu by just moving the mouse
783
784 // we need to translate our client coords to the client coords of the
785 // window we forward this event to
786 wxPoint ptScreen = ClientToScreen(pt);
787
788 // if the mouse is outside this menu, let the parent one to
789 // process it
790 wxMenu *menuParent = m_menu->GetParent();
791 if ( menuParent )
792 {
793 wxPopupMenuWindow *win = menuParent->m_popupMenu;
794
795 // if we're shown, the parent menu must be also shown
796 wxCHECK_RET( win, _T("parent menu is not shown?") );
797
798 win->ProcessMouseMove(win->ScreenToClient(ptScreen));
799 }
800 else // no parent menu
801 {
802 wxMenuBar *menubar = m_menu->GetMenuBar();
803 if ( menubar )
804 {
805 if ( menubar->ProcessMouseEvent(
806 menubar->ScreenToClient(ptScreen)) )
807 {
808 // menubar has closed this menu and opened another one, probably
809 return;
810 }
811 }
812 }
813 //else: top level popup menu, no other processing to do
814 }
815}
816
817void wxPopupMenuWindow::OnMouseLeave(wxMouseEvent& event)
818{
819 // due to the artefact of mouse events generation under MSW, we actually
820 // may get the mouse leave event after the menu had been already dismissed
821 // and calling ChangeCurrent() would then assert, so don't do it
822 if ( IsShown() )
823 {
824 // we shouldn't change the current them if our submenu is opened and
825 // mouse moved there, in this case the submenu is responsable for
826 // handling it
827 bool resetCurrent;
828 if ( HasOpenSubmenu() )
829 {
830 wxMenuItem *item = GetCurrentItem();
831 wxCHECK_RET( CanOpen(item), _T("where is our open submenu?") );
832
833 wxPopupMenuWindow *win = item->GetSubMenu()->m_popupMenu;
834 wxCHECK_RET( win, _T("submenu is opened but not shown?") );
835
836 // only handle this event if the mouse is not inside the submenu
837 wxPoint pt = ClientToScreen(event.GetPosition());
838 resetCurrent =
839 win->HitTest(win->ScreenToClient(pt)) == wxHT_WINDOW_OUTSIDE;
840 }
841 else
842 {
843 // this menu is the last opened
a290fa5a 844 resetCurrent = true;
1e6feb95
VZ
845 }
846
847 if ( resetCurrent )
848 {
27b43624 849 ChangeCurrent(wxMenuItemIter());
1e6feb95
VZ
850 }
851 }
852
853 event.Skip();
854}
855
856void wxPopupMenuWindow::OnKeyDown(wxKeyEvent& event)
857{
d8809820
MW
858 wxMenuBar *menubar = m_menu->GetMenuBar();
859
860 if ( menubar )
861 {
862 menubar->ProcessEvent(event);
863 }
864 else if ( !ProcessKeyDown(event.GetKeyCode()) )
1e6feb95
VZ
865 {
866 event.Skip();
867 }
868}
869
870bool wxPopupMenuWindow::ProcessKeyDown(int key)
871{
872 wxMenuItem *item = GetCurrentItem();
873
874 // first let the opened submenu to have it (no test for IsEnabled() here,
875 // the keys navigate even in a disabled submenu if we had somehow managed
876 // to open it inspit of this)
877 if ( HasOpenSubmenu() )
878 {
a290fa5a 879 wxCHECK_MSG( CanOpen(item), false,
1e6feb95
VZ
880 _T("has open submenu but another item selected?") );
881
882 if ( item->GetSubMenu()->ProcessKeyDown(key) )
a290fa5a 883 return true;
1e6feb95
VZ
884 }
885
a290fa5a 886 bool processed = true;
1e6feb95
VZ
887
888 // handle the up/down arrows, home, end, esc and return here, pass the
889 // left/right arrows to the menu bar except when the right arrow can be
890 // used to open a submenu
891 switch ( key )
892 {
893 case WXK_LEFT:
894 // if we're not a top level menu, close us, else leave this to the
895 // menubar
896 if ( !m_menu->GetParent() )
897 {
a290fa5a 898 processed = false;
1e6feb95
VZ
899 break;
900 }
901
902 // fall through
903
904 case WXK_ESCAPE:
905 // close just this menu
906 Dismiss();
a290fa5a 907 HandleDismiss(false);
1e6feb95
VZ
908 break;
909
910 case WXK_RETURN:
911 processed = ActivateItem(item);
912 break;
913
914 case WXK_HOME:
915 ChangeCurrent(m_menu->GetMenuItems().GetFirst());
916 break;
917
918 case WXK_END:
919 ChangeCurrent(m_menu->GetMenuItems().GetLast());
920 break;
921
922 case WXK_UP:
923 case WXK_DOWN:
924 {
925 bool up = key == WXK_UP;
926
27b43624
VZ
927 wxMenuItemIter nodeStart = up ? GetPrevNode() : GetNextNode(),
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
27b43624 937 node = wxMenuItemIter();
1e6feb95
VZ
938 }
939 }
940
941 if ( node )
942 {
943 ChangeCurrent(node);
944 }
945 else
946 {
a290fa5a 947 processed = false;
1e6feb95
VZ
948 }
949 }
950 break;
951
952 case WXK_RIGHT:
953 // don't try to reopen an already opened menu
954 if ( !HasOpenSubmenu() && CanOpen(item) )
955 {
956 OpenSubmenu(item);
957 }
958 else
959 {
a290fa5a 960 processed = false;
1e6feb95
VZ
961 }
962 break;
963
964 default:
965 // look for the menu item starting with this letter
32b13913 966 if ( wxIsalnum((wxChar)key) )
1e6feb95
VZ
967 {
968 // we want to start from the item after this one because
969 // if we're already on the item with the given accel we want to
970 // go to the next one, not to stay in place
27b43624 971 wxMenuItemIter nodeStart = GetNextNode();
1e6feb95
VZ
972
973 // do we have more than one item with this accel?
a290fa5a 974 bool notUnique = false;
1e6feb95
VZ
975
976 // translate everything to lower case before comparing
de516ffe 977 wxChar chAccel = (wxChar)wxTolower(key);
1e6feb95
VZ
978
979 // loop through all items searching for the item with this
980 // accel
27b43624
VZ
981 wxMenuItemIter nodeFound,
982 node = nodeStart;
1e6feb95
VZ
983 for ( ;; )
984 {
985 item = node->GetData();
986
987 int idxAccel = item->GetAccelIndex();
988 if ( idxAccel != -1 &&
989 wxTolower(item->GetLabel()[(size_t)idxAccel])
990 == chAccel )
991 {
992 // ok, found an item with this accel
993 if ( !nodeFound )
994 {
995 // store it but continue searching as we need to
996 // know if it's the only item with this accel or if
997 // there are more
998 nodeFound = node;
999 }
1000 else // we already had found such item
1001 {
a290fa5a 1002 notUnique = true;
1e6feb95
VZ
1003
1004 // no need to continue further, we won't find
1005 // anything we don't already know
1006 break;
1007 }
1008 }
1009
1010 // we want to iterate over all items wrapping around if
1011 // necessary
1012 node = GetNextNode(node);
1013 if ( node == nodeStart )
1014 {
1015 // we've seen all nodes
1016 break;
1017 }
1018 }
1019
1020 if ( nodeFound )
1021 {
1022 item = nodeFound->GetData();
1023
1024 // go to this item anyhow
1025 ChangeCurrent(nodeFound);
1026
1027 if ( !notUnique && item->IsEnabled() )
1028 {
1029 // unique item with this accel - activate it
1030 processed = ActivateItem(item);
1031 }
1032 //else: just select it but don't activate as the user might
1033 // have wanted to activate another item
1034
a290fa5a 1035 // skip "processed = false" below
1e6feb95
VZ
1036 break;
1037 }
1038 }
1039
a290fa5a 1040 processed = false;
1e6feb95
VZ
1041 }
1042
1043 return processed;
1044}
1045
1046// ----------------------------------------------------------------------------
1047// wxMenu
1048// ----------------------------------------------------------------------------
1049
1050void wxMenu::Init()
1051{
1052 m_geometry = NULL;
1053
1054 m_popupMenu = NULL;
6f7c5199
JS
1055
1056 m_startRadioGroup = -1;
1e6feb95
VZ
1057}
1058
1059wxMenu::~wxMenu()
1060{
1061 delete m_geometry;
1062 delete m_popupMenu;
1063}
1064
1065// ----------------------------------------------------------------------------
1066// wxMenu and wxMenuGeometryInfo
1067// ----------------------------------------------------------------------------
1068
1069wxMenuGeometryInfo::~wxMenuGeometryInfo()
1070{
1071}
1072
1073const wxMenuGeometryInfo& wxMenu::GetGeometryInfo() const
1074{
1075 if ( !m_geometry )
1076 {
1077 if ( m_popupMenu )
1078 {
1079 wxConstCast(this, wxMenu)->m_geometry =
1080 m_popupMenu->GetRenderer()->GetMenuGeometry(m_popupMenu, *this);
1081 }
1082 else
1083 {
1084 wxFAIL_MSG( _T("can't get geometry without window") );
1085 }
1086 }
1087
1088 return *m_geometry;
1089}
1090
1091void wxMenu::InvalidateGeometryInfo()
1092{
1093 if ( m_geometry )
1094 {
1095 delete m_geometry;
1096 m_geometry = NULL;
1097 }
1098}
1099
1100// ----------------------------------------------------------------------------
1101// wxMenu adding/removing items
1102// ----------------------------------------------------------------------------
1103
1104void wxMenu::OnItemAdded(wxMenuItem *item)
1105{
1106 InvalidateGeometryInfo();
1107
1108#if wxUSE_ACCEL
1109 AddAccelFor(item);
1110#endif // wxUSE_ACCEL
1111
1112 // the submenus of a popup menu should have the same invoking window as it
1113 // has
1114 if ( m_invokingWindow && item->IsSubMenu() )
1115 {
1116 item->GetSubMenu()->SetInvokingWindow(m_invokingWindow);
1117 }
1118}
1119
6f7c5199
JS
1120void wxMenu::EndRadioGroup()
1121{
1122 // we're not inside a radio group any longer
1123 m_startRadioGroup = -1;
1124}
1125
9add9367 1126wxMenuItem* wxMenu::DoAppend(wxMenuItem *item)
1e6feb95 1127{
6f7c5199
JS
1128 if ( item->GetKind() == wxITEM_RADIO )
1129 {
1130 int count = GetMenuItemCount();
1131
1132 if ( m_startRadioGroup == -1 )
1133 {
1134 // start a new radio group
1135 m_startRadioGroup = count;
1136
1137 // for now it has just one element
1138 item->SetAsRadioGroupStart();
1139 item->SetRadioGroupEnd(m_startRadioGroup);
6f7c5199
JS
1140 }
1141 else // extend the current radio group
1142 {
1143 // we need to update its end item
1144 item->SetRadioGroupStart(m_startRadioGroup);
27b43624 1145 wxMenuItemIter node = GetMenuItems().Item(m_startRadioGroup);
6f7c5199
JS
1146
1147 if ( node )
1148 {
1149 node->GetData()->SetRadioGroupEnd(count);
1150 }
1151 else
1152 {
1153 wxFAIL_MSG( _T("where is the radio group start item?") );
1154 }
1155 }
1156 }
1157 else // not a radio item
1158 {
1159 EndRadioGroup();
1160 }
1161
1e6feb95 1162 if ( !wxMenuBase::DoAppend(item) )
9add9367 1163 return NULL;
1e6feb95
VZ
1164
1165 OnItemAdded(item);
1166
9add9367 1167 return item;
1e6feb95
VZ
1168}
1169
9add9367 1170wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
1e6feb95
VZ
1171{
1172 if ( !wxMenuBase::DoInsert(pos, item) )
9add9367 1173 return NULL;
1e6feb95
VZ
1174
1175 OnItemAdded(item);
1176
9add9367 1177 return item;
1e6feb95
VZ
1178}
1179
1180wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
1181{
1182 wxMenuItem *itemOld = wxMenuBase::DoRemove(item);
1183
1184 if ( itemOld )
1185 {
1186 InvalidateGeometryInfo();
1187
1188#if wxUSE_ACCEL
1189 RemoveAccelFor(item);
1190#endif // wxUSE_ACCEL
1191 }
1192
1193 return itemOld;
1194}
1195
1196// ----------------------------------------------------------------------------
1197// wxMenu attaching/detaching
1198// ----------------------------------------------------------------------------
1199
1200void wxMenu::Attach(wxMenuBarBase *menubar)
1201{
1202 wxMenuBase::Attach(menubar);
1203
1204 wxCHECK_RET( m_menuBar, _T("menubar can't be NULL after attaching") );
1205
1206 // unfortunately, we can't use m_menuBar->GetEventHandler() here because,
1207 // if the menubar is currently showing a menu, its event handler is a
1208 // temporary one installed by wxPopupWindow and so will disappear soon any
1209 // any attempts to use it from the newly attached menu would result in a
1210 // crash
1211 //
1212 // so we use the menubar itself, even if it's a pity as it means we can't
1213 // redirect all menu events by changing the menubar handler (FIXME)
1214 SetNextHandler(m_menuBar);
1215}
1216
1217void wxMenu::Detach()
1218{
1219 wxMenuBase::Detach();
1220}
1221
1222// ----------------------------------------------------------------------------
1223// wxMenu misc functions
1224// ----------------------------------------------------------------------------
1225
1226wxWindow *wxMenu::GetRootWindow() const
1227{
4224f059 1228 if ( GetMenuBar() )
1e6feb95
VZ
1229 {
1230 // simple case - a normal menu attached to the menubar
4224f059 1231 return GetMenuBar();
1e6feb95
VZ
1232 }
1233
1234 // we're a popup menu but the trouble is that only the top level popup menu
1235 // has a pointer to the invoking window, so we must walk up the menu chain
1236 // if needed
1237 wxWindow *win = GetInvokingWindow();
1238 if ( win )
1239 {
1240 // we already have it
1241 return win;
1242 }
1243
1244 wxMenu *menu = GetParent();
1245 while ( menu )
1246 {
ea1ad04b
RR
1247 // We are a submenu of a menu of a menubar
1248 if (menu->GetMenuBar())
1249 return menu->GetMenuBar();
e441e1f4 1250
1e6feb95
VZ
1251 win = menu->GetInvokingWindow();
1252 if ( win )
1253 break;
1254
1255 menu = menu->GetParent();
1256 }
1257
1258 // we're probably going to crash in the caller anyhow, but try to detect
1259 // this error as soon as possible
1260 wxASSERT_MSG( win, _T("menu without any associated window?") );
e441e1f4 1261
1e6feb95
VZ
1262 // also remember it in this menu so that we don't have to search for it the
1263 // next time
1264 wxConstCast(this, wxMenu)->m_invokingWindow = win;
1265
1266 return win;
1267}
1268
1269wxRenderer *wxMenu::GetRenderer() const
1270{
1271 // we're going to crash without renderer!
1272 wxCHECK_MSG( m_popupMenu, NULL, _T("neither popup nor menubar menu?") );
1273
1274 return m_popupMenu->GetRenderer();
1275}
1276
1277void wxMenu::RefreshItem(wxMenuItem *item)
1278{
1279 // the item geometry changed, so our might have changed as well
1280 InvalidateGeometryInfo();
1281
1282 if ( IsShown() )
1283 {
1284 // this would be a bug in IsShown()
1285 wxCHECK_RET( m_popupMenu, _T("must have popup window if shown!") );
1286
1287 // recalc geometry to update the item height and such
1288 (void)GetGeometryInfo();
1289
1290 m_popupMenu->RefreshItem(item);
1291 }
1292}
1293
1294// ----------------------------------------------------------------------------
1295// wxMenu showing and hiding
1296// ----------------------------------------------------------------------------
1297
1298bool wxMenu::IsShown() const
1299{
1300 return m_popupMenu && m_popupMenu->IsShown();
1301}
1302
1303void wxMenu::OnDismiss(bool dismissParent)
1304{
1305 if ( m_menuParent )
1306 {
1307 // always notify the parent about submenu disappearance
1308 wxPopupMenuWindow *win = m_menuParent->m_popupMenu;
1309 if ( win )
1310 {
9f9f75b3 1311 win->OnSubmenuDismiss( true );
1e6feb95
VZ
1312 }
1313 else
1314 {
1315 wxFAIL_MSG( _T("parent menu not shown?") );
1316 }
1317
1318 // and if we dismiss everything, propagate to parent
1319 if ( dismissParent )
1320 {
1321 // dismissParent is recursive
1322 m_menuParent->Dismiss();
a290fa5a 1323 m_menuParent->OnDismiss(true);
1e6feb95
VZ
1324 }
1325 }
1326 else // no parent menu
1327 {
1328 // notify the menu bar if we're a top level menu
1329 if ( m_menuBar )
1330 {
1331 m_menuBar->OnDismissMenu(dismissParent);
1332 }
1333 else // popup menu
1334 {
1335 wxCHECK_RET( m_invokingWindow, _T("what kind of menu is this?") );
1336
1337 m_invokingWindow->DismissPopupMenu();
e441e1f4 1338
ee351013
RR
1339 // Why reset it here? We need it for sending the event to...
1340 // SetInvokingWindow(NULL);
1e6feb95
VZ
1341 }
1342 }
1343}
1344
1345void wxMenu::Popup(const wxPoint& pos, const wxSize& size, bool selectFirst)
1346{
1347 // create the popup window if not done yet
1348 if ( !m_popupMenu )
1349 {
1350 m_popupMenu = new wxPopupMenuWindow(GetRootWindow(), this);
1351 }
1352
1353 // select the first item unless disabled
1354 if ( selectFirst )
1355 {
1356 m_popupMenu->SelectFirst();
1357 }
e441e1f4 1358
1e6feb95
VZ
1359 // the geometry might have changed since the last time we were shown, so
1360 // always resize
1361 m_popupMenu->SetClientSize(GetGeometryInfo().GetSize());
1362
1363 // position it as specified
1364 m_popupMenu->Position(pos, size);
1365
1366 // the menu can't have the focus itself (it is a Windows limitation), so
1367 // always keep the focus at the originating window
1368 wxWindow *focus = GetRootWindow();
1369
1370 wxASSERT_MSG( focus, _T("no window to keep focus on?") );
1371
1372 // and show it
1373 m_popupMenu->Popup(focus);
1374}
1375
1376void wxMenu::Dismiss()
1377{
1378 wxCHECK_RET( IsShown(), _T("can't dismiss hidden menu") );
1379
1380 m_popupMenu->Dismiss();
1381}
1382
1383// ----------------------------------------------------------------------------
1384// wxMenu event processing
1385// ----------------------------------------------------------------------------
1386
1387bool wxMenu::ProcessKeyDown(int key)
1388{
a290fa5a 1389 wxCHECK_MSG( m_popupMenu, false,
1e6feb95
VZ
1390 _T("can't process key events if not shown") );
1391
1392 return m_popupMenu->ProcessKeyDown(key);
1393}
1394
1395bool wxMenu::ClickItem(wxMenuItem *item)
1396{
1397 int isChecked;
1398 if ( item->IsCheckable() )
1399 {
1400 // update the item state
1401 isChecked = !item->IsChecked();
1402
1403 item->Check(isChecked != 0);
1404 }
1405 else
1406 {
1407 // not applicabled
1408 isChecked = -1;
1409 }
e441e1f4 1410
1e6feb95
VZ
1411 return SendEvent(item->GetId(), isChecked);
1412}
1413
1414// ----------------------------------------------------------------------------
1415// wxMenu accel support
1416// ----------------------------------------------------------------------------
1417
1418#if wxUSE_ACCEL
1419
1420bool wxMenu::ProcessAccelEvent(const wxKeyEvent& event)
1421{
1422 // do we have an item for this accel?
1423 wxMenuItem *item = m_accelTable.GetMenuItem(event);
1424 if ( item && item->IsEnabled() )
1425 {
1426 return ClickItem(item);
1427 }
1428
1429 // try our submenus
27b43624 1430 for ( wxMenuItemIter node = GetMenuItems().GetFirst();
1e6feb95
VZ
1431 node;
1432 node = node->GetNext() )
1433 {
1434 const wxMenuItem *item = node->GetData();
1435 if ( item->IsSubMenu() && item->IsEnabled() )
1436 {
1437 // try its elements
1438 if ( item->GetSubMenu()->ProcessAccelEvent(event) )
1439 {
a290fa5a 1440 return true;
1e6feb95
VZ
1441 }
1442 }
1443 }
1444
a290fa5a 1445 return false;
1e6feb95
VZ
1446}
1447
1448void wxMenu::AddAccelFor(wxMenuItem *item)
1449{
1450 wxAcceleratorEntry *accel = item->GetAccel();
1451 if ( accel )
1452 {
1453 accel->SetMenuItem(item);
1454
1455 m_accelTable.Add(*accel);
1456
1457 delete accel;
1458 }
1459}
1460
1461void wxMenu::RemoveAccelFor(wxMenuItem *item)
1462{
1463 wxAcceleratorEntry *accel = item->GetAccel();
1464 if ( accel )
1465 {
1466 m_accelTable.Remove(*accel);
1467
1468 delete accel;
1469 }
1470}
1471
1472#endif // wxUSE_ACCEL
1473
1474// ----------------------------------------------------------------------------
1475// wxMenuItem construction
1476// ----------------------------------------------------------------------------
1477
1478wxMenuItem::wxMenuItem(wxMenu *parentMenu,
1479 int id,
1480 const wxString& text,
1481 const wxString& help,
d65c269b 1482 wxItemKind kind,
1e6feb95 1483 wxMenu *subMenu)
d65c269b 1484 : wxMenuItemBase(parentMenu, id, text, help, kind, subMenu)
1e6feb95 1485{
1e6feb95 1486 m_posY =
a290fa5a 1487 m_height = wxDefaultCoord;
1e6feb95 1488
6f7c5199 1489 m_radioGroup.start = -1;
a290fa5a 1490 m_isRadioGroupStart = false;
6f7c5199 1491
dba6b4f8
JS
1492 m_bmpDisabled = wxNullBitmap;
1493
1e6feb95
VZ
1494 UpdateAccelInfo();
1495}
1496
1497wxMenuItem::~wxMenuItem()
1498{
1499}
1500
1501// ----------------------------------------------------------------------------
1502// wxMenuItemBase methods implemented here
1503// ----------------------------------------------------------------------------
1504
1505/* static */
1506wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
1507 int id,
1508 const wxString& name,
1509 const wxString& help,
e4052bd3 1510 wxItemKind kind,
1e6feb95
VZ
1511 wxMenu *subMenu)
1512{
e4052bd3 1513 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
1e6feb95
VZ
1514}
1515
1516/* static */
1517wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
1518{
1519 return wxStripMenuCodes(text);
1520}
1521
1522// ----------------------------------------------------------------------------
1523// wxMenuItem operations
1524// ----------------------------------------------------------------------------
1525
1526void wxMenuItem::NotifyMenu()
1527{
1528 m_parentMenu->RefreshItem(this);
1529}
1530
1531void wxMenuItem::UpdateAccelInfo()
1532{
1533 m_indexAccel = wxControl::FindAccelIndex(m_text);
1534
1535 // will be empty if the text contains no TABs - ok
1536 m_strAccel = m_text.AfterFirst(_T('\t'));
1537}
1538
1539void wxMenuItem::SetText(const wxString& text)
1540{
1541 if ( text != m_text )
1542 {
1543 // first call the base class version to change m_text
1544 wxMenuItemBase::SetText(text);
1545
1546 UpdateAccelInfo();
1547
1548 NotifyMenu();
1549 }
1550}
1551
1552void wxMenuItem::SetCheckable(bool checkable)
1553{
d65c269b 1554 if ( checkable != IsCheckable() )
1e6feb95
VZ
1555 {
1556 wxMenuItemBase::SetCheckable(checkable);
1557
1558 NotifyMenu();
1559 }
1560}
1561
1562void wxMenuItem::SetBitmaps(const wxBitmap& bmpChecked,
1563 const wxBitmap& bmpUnchecked)
1564{
1565 m_bmpChecked = bmpChecked;
1566 m_bmpUnchecked = bmpUnchecked;
1567
1568 NotifyMenu();
1569}
1570
1571void wxMenuItem::Enable(bool enable)
1572{
1573 if ( enable != m_isEnabled )
1574 {
1575 wxMenuItemBase::Enable(enable);
1576
1577 NotifyMenu();
1578 }
1579}
1580
1581void wxMenuItem::Check(bool check)
1582{
6f7c5199
JS
1583 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
1584
1585 if ( m_isChecked == check )
1586 return;
1587
1588 if ( GetKind() == wxITEM_RADIO )
1e6feb95 1589 {
6f7c5199
JS
1590 // it doesn't make sense to uncheck a radio item - what would this do?
1591 if ( !check )
1592 return;
1e6feb95 1593
6f7c5199
JS
1594 // get the index of this item in the menu
1595 const wxMenuItemList& items = m_parentMenu->GetMenuItems();
1596 int pos = items.IndexOf(this);
1597 wxCHECK_RET( pos != wxNOT_FOUND,
1598 _T("menuitem not found in the menu items list?") );
1599
1600 // get the radio group range
1601 int start,
1602 end;
1603
1604 if ( m_isRadioGroupStart )
1605 {
1606 // we already have all information we need
1607 start = pos;
1608 end = m_radioGroup.end;
1609 }
1610 else // next radio group item
1611 {
1612 // get the radio group end from the start item
1613 start = m_radioGroup.start;
1614 end = items.Item(start)->GetData()->m_radioGroup.end;
1615 }
1616
1617 // also uncheck all the other items in this radio group
27b43624 1618 wxMenuItemIter node = items.Item(start);
6f7c5199
JS
1619 for ( int n = start; n <= end && node; n++ )
1620 {
1621 if ( n != pos )
1622 {
a290fa5a 1623 node->GetData()->m_isChecked = false;
6f7c5199
JS
1624 }
1625 node = node->GetNext();
1626 }
1e6feb95 1627 }
6f7c5199
JS
1628
1629 wxMenuItemBase::Check(check);
1630
1631 NotifyMenu();
1632}
1633
1634// radio group stuff
1635// -----------------
1636
1637void wxMenuItem::SetAsRadioGroupStart()
1638{
a290fa5a 1639 m_isRadioGroupStart = true;
6f7c5199
JS
1640}
1641
1642void wxMenuItem::SetRadioGroupStart(int start)
1643{
1644 wxASSERT_MSG( !m_isRadioGroupStart,
1645 _T("should only be called for the next radio items") );
1646
1647 m_radioGroup.start = start;
1648}
1649
1650void wxMenuItem::SetRadioGroupEnd(int end)
1651{
1652 wxASSERT_MSG( m_isRadioGroupStart,
1653 _T("should only be called for the first radio item") );
1654
1655 m_radioGroup.end = end;
1e6feb95
VZ
1656}
1657
1658// ----------------------------------------------------------------------------
1659// wxMenuBar creation
1660// ----------------------------------------------------------------------------
1661
1662void wxMenuBar::Init()
1663{
1664 m_frameLast = NULL;
1665
1666 m_current = -1;
1667
1668 m_menuShown = NULL;
1669
a290fa5a 1670 m_shouldShowMenu = false;
1e6feb95
VZ
1671}
1672
294ea16d
VZ
1673wxMenuBar::wxMenuBar(size_t n, wxMenu *menus[], const wxString titles[], long WXUNUSED(style))
1674{
1675 Init();
1676
1677 for (size_t i = 0; i < n; ++i )
1678 Append(menus[i], titles[i]);
1679}
1680
1e6feb95
VZ
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 {
f52c3131 2188 case WXK_ALT:
1e6feb95
VZ
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{
5b3166fd 2458 if ( ReleaseMouseCapture() )
5a750e5a 2459 wxLogTrace(_T("mousecapture"), _T("Releasing mouse from wxMenuBar::OnDismiss"));
d8809820 2460
1e6feb95
VZ
2461 if ( m_current != -1 )
2462 {
23d8bb2c 2463 size_t current = m_current;
1e6feb95 2464 m_current = -1;
23d8bb2c
VZ
2465
2466 RefreshItem(current);
1e6feb95
VZ
2467 }
2468
2469 GiveAwayFocus();
2470}
2471
5b3166fd
MW
2472bool wxMenuBar::ReleaseMouseCapture()
2473{
78f93365 2474#ifdef __WXX11__
5b3166fd
MW
2475 // With wxX11, when a menu is closed by clicking away from it, a control
2476 // under the click will still get an event, even though the menu has the
2477 // capture (bug?). So that control may already have taken the capture by
2478 // this point, preventing us from releasing the menu's capture. So to work
2479 // around this, we release both captures, then put back the control's
2480 // capture.
2481 wxWindow *capture = GetCapture();
2482 if ( capture )
2483 {
2484 capture->ReleaseMouse();
2485
2486 if ( capture == this )
2487 return true;
2488
2489 bool had = HasCapture();
2490
2491 if ( had )
2492 ReleaseMouse();
d8809820 2493
5b3166fd
MW
2494 capture->CaptureMouse();
2495
2496 return had;
2497 }
2498#else
2499 if ( HasCapture() )
2500 {
2501 ReleaseMouse();
2502 return true;
2503 }
2504#endif
2505 return false;
2506}
2507
1e6feb95
VZ
2508void wxMenuBar::GiveAwayFocus()
2509{
2510 GetFrame()->SetFocus();
2511}
2512
2513// ----------------------------------------------------------------------------
2514// popup menu support
2515// ----------------------------------------------------------------------------
2516
2517wxEventLoop *wxWindow::ms_evtLoopPopup = NULL;
2518
2519bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
2520{
a290fa5a 2521 wxCHECK_MSG( !ms_evtLoopPopup, false,
1e6feb95
VZ
2522 _T("can't show more than one popup menu at a time") );
2523
2524#ifdef __WXMSW__
2525 // we need to change the cursor before showing the menu as, apparently, no
2526 // cursor changes took place while the mouse is captured
2527 wxCursor cursorOld = GetCursor();
2528 SetCursor(wxCURSOR_ARROW);
2529#endif // __WXMSW__
2530
2531#if 0
2532 // flash any delayed log messages before showing the menu, otherwise it
2533 // could be dismissed (because it would lose focus) immediately after being
2534 // shown
2535 wxLog::FlushActive();
2536
2537 // some controls update themselves from OnIdle() call - let them do it
e39af974 2538 wxTheApp->ProcessIdle();
1e6feb95
VZ
2539
2540 // if the window hadn't been refreshed yet, the menu can adversely affect
2541 // its next OnPaint() handler execution - i.e. scrolled window refresh
2542 // logic breaks then as it scrolls part of the menu which hadn't been there
2543 // when the update event was generated into view
2544 Update();
2545#endif // 0
2546
2547 menu->SetInvokingWindow(this);
e441e1f4 2548
ee351013 2549 // wxLogDebug( "Name of invoking window %s", menu->GetInvokingWindow()->GetName().c_str() );
e441e1f4 2550
c47addef 2551 menu->Popup(ClientToScreen(wxPoint(x, y)), wxSize(0,0));
1e6feb95
VZ
2552
2553 // this is not very useful if the menu was popped up because of the mouse
2554 // click but I think it is nice to do when it appears because of a key
2555 // press (i.e. Windows menu key)
2556 //
2557 // Windows itself doesn't do it, but IMHO this is nice
2558 WarpPointer(x, y);
2559
2560 // we have to redirect all keyboard input to the menu temporarily
2561 PushEventHandler(new wxMenuKbdRedirector(menu));
2562
2563 // enter the local modal loop
2564 ms_evtLoopPopup = new wxEventLoop;
2565 ms_evtLoopPopup->Run();
2566
2567 delete ms_evtLoopPopup;
2568 ms_evtLoopPopup = NULL;
2569
2570 // remove the handler
a290fa5a 2571 PopEventHandler(true /* delete it */);
1e6feb95
VZ
2572
2573 menu->SetInvokingWindow(NULL);
2574
2575#ifdef __WXMSW__
2576 SetCursor(cursorOld);
2577#endif // __WXMSW__
2578
a290fa5a 2579 return true;
1e6feb95
VZ
2580}
2581
2582void wxWindow::DismissPopupMenu()
2583{
2584 wxCHECK_RET( ms_evtLoopPopup, _T("no popup menu shown") );
e441e1f4 2585
1e6feb95
VZ
2586 ms_evtLoopPopup->Exit();
2587}
2588
2589#endif // wxUSE_MENUS