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