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