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