]> git.saurik.com Git - wxWidgets.git/blob - src/univ/menu.cpp
oops, forgot do add fullscreen stuff to wxTLWBase
[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
1553 // remember the last frame which had us to avoid unnecessarily reparenting
1554 // above
1555 m_frameLast = frame;
1556 }
1557
1558 void wxMenuBar::Detach()
1559 {
1560 // don't delete the window because we may be reattached later, just hide it
1561 if ( m_frameLast )
1562 {
1563 Hide();
1564 }
1565
1566 wxMenuBarBase::Detach();
1567 }
1568
1569 wxMenuBar::~wxMenuBar()
1570 {
1571 }
1572
1573 // ----------------------------------------------------------------------------
1574 // wxMenuBar adding/removing items
1575 // ----------------------------------------------------------------------------
1576
1577 bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
1578 {
1579 return Insert(GetCount(), menu, title);
1580 }
1581
1582 bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
1583 {
1584 if ( !wxMenuBarBase::Insert(pos, menu, title) )
1585 return FALSE;
1586
1587 wxMenuInfo *info = new wxMenuInfo(title);
1588 m_menuInfos.Insert(info, pos);
1589
1590 RefreshAllItemsAfter(pos);
1591
1592 return TRUE;
1593 }
1594
1595 wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
1596 {
1597 wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
1598
1599 if ( menuOld )
1600 {
1601 wxMenuInfo& info = m_menuInfos[pos];
1602
1603 info.SetLabel(title);
1604
1605 // even if the old menu was disabled, the new one is not any more
1606 info.SetEnabled();
1607
1608 // even if we change only this one, the new label has different width,
1609 // so we need to refresh everything beyond this item as well
1610 RefreshAllItemsAfter(pos);
1611 }
1612
1613 return menuOld;
1614 }
1615
1616 wxMenu *wxMenuBar::Remove(size_t pos)
1617 {
1618 wxMenu *menuOld = wxMenuBarBase::Remove(pos);
1619
1620 if ( menuOld )
1621 {
1622 m_menuInfos.RemoveAt(pos);
1623
1624 // this doesn't happen too often, so don't try to be too smart - just
1625 // refresh everything
1626 Refresh();
1627 }
1628
1629 return menuOld;
1630 }
1631
1632 // ----------------------------------------------------------------------------
1633 // wxMenuBar top level menus access
1634 // ----------------------------------------------------------------------------
1635
1636 wxCoord wxMenuBar::GetItemWidth(size_t pos) const
1637 {
1638 return m_menuInfos[pos].GetWidth(wxConstCast(this, wxMenuBar));
1639 }
1640
1641 void wxMenuBar::EnableTop(size_t pos, bool enable)
1642 {
1643 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1644
1645 if ( enable != m_menuInfos[pos].IsEnabled() )
1646 {
1647 m_menuInfos[pos].SetEnabled(enable);
1648
1649 RefreshItem(pos);
1650 }
1651 //else: nothing to do
1652 }
1653
1654 bool wxMenuBar::IsEnabledTop(size_t pos) const
1655 {
1656 wxCHECK_MSG( pos < GetCount(), FALSE, _T("invalid index in IsEnabledTop") );
1657
1658 return m_menuInfos[pos].IsEnabled();
1659 }
1660
1661 void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
1662 {
1663 wxCHECK_RET( pos < GetCount(), _T("invalid index in EnableTop") );
1664
1665 if ( label != m_menuInfos[pos].GetLabel() )
1666 {
1667 m_menuInfos[pos].SetLabel(label);
1668
1669 RefreshItem(pos);
1670 }
1671 //else: nothing to do
1672 }
1673
1674 wxString wxMenuBar::GetLabelTop(size_t pos) const
1675 {
1676 wxCHECK_MSG( pos < GetCount(), _T(""), _T("invalid index in GetLabelTop") );
1677
1678 return m_menuInfos[pos].GetLabel();
1679 }
1680
1681 // ----------------------------------------------------------------------------
1682 // wxMenuBar drawing
1683 // ----------------------------------------------------------------------------
1684
1685 void wxMenuBar::RefreshAllItemsAfter(size_t pos)
1686 {
1687 if ( !IsCreated() )
1688 {
1689 // no need to refresh if nothing is shown yet
1690 return;
1691 }
1692
1693 wxRect rect = GetItemRect(pos);
1694 rect.width = GetClientSize().x - rect.x;
1695 RefreshRect(rect);
1696 }
1697
1698 void wxMenuBar::RefreshItem(size_t pos)
1699 {
1700 wxCHECK_RET( pos != (size_t)-1,
1701 _T("invalid item in wxMenuBar::RefreshItem") );
1702
1703 if ( !IsCreated() )
1704 {
1705 // no need to refresh if nothing is shown yet
1706 return;
1707 }
1708
1709 RefreshRect(GetItemRect(pos));
1710 }
1711
1712 void wxMenuBar::DoDraw(wxControlRenderer *renderer)
1713 {
1714 wxDC& dc = renderer->GetDC();
1715 dc.SetFont(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT));
1716
1717 // redraw only the items which must be redrawn
1718
1719 // we don't have to use GetUpdateClientRect() here because our client rect
1720 // is the same as total one
1721 wxRect rectUpdate = GetUpdateRegion().GetBox();
1722
1723 int flagsMenubar = GetStateFlags();
1724
1725 wxRect rect;
1726 rect.y = 0;
1727 rect.height = GetClientSize().y;
1728
1729 wxCoord x = 0;
1730 size_t count = GetCount();
1731 for ( size_t n = 0; n < count; n++ )
1732 {
1733 if ( x > rectUpdate.GetRight() )
1734 {
1735 // all remaining items are to the right of rectUpdate
1736 break;
1737 }
1738
1739 rect.x = x;
1740 rect.width = GetItemWidth(n);
1741 x += rect.width;
1742 if ( x < rectUpdate.x )
1743 {
1744 // this item is still to the left of rectUpdate
1745 continue;
1746 }
1747
1748 int flags = flagsMenubar;
1749 if ( m_current != -1 && n == (size_t)m_current )
1750 {
1751 flags |= wxCONTROL_SELECTED;
1752 }
1753
1754 if ( !IsEnabledTop(n) )
1755 {
1756 flags |= wxCONTROL_DISABLED;
1757 }
1758
1759 GetRenderer()->DrawMenuBarItem
1760 (
1761 dc,
1762 rect,
1763 m_menuInfos[n].GetLabel(),
1764 flags,
1765 m_menuInfos[n].GetAccelIndex()
1766 );
1767 }
1768 }
1769
1770 // ----------------------------------------------------------------------------
1771 // wxMenuBar geometry
1772 // ----------------------------------------------------------------------------
1773
1774 wxRect wxMenuBar::GetItemRect(size_t pos) const
1775 {
1776 wxASSERT_MSG( pos < GetCount(), _T("invalid menu bar item index") );
1777 wxASSERT_MSG( IsCreated(), _T("can't call this method yet") );
1778
1779 wxRect rect;
1780 rect.x =
1781 rect.y = 0;
1782 rect.height = GetClientSize().y;
1783
1784 for ( size_t n = 0; n < pos; n++ )
1785 {
1786 rect.x += GetItemWidth(n);
1787 }
1788
1789 rect.width = GetItemWidth(pos);
1790
1791 return rect;
1792 }
1793
1794 wxSize wxMenuBar::DoGetBestClientSize() const
1795 {
1796 wxSize size;
1797 if ( GetMenuCount() > 0 )
1798 {
1799 wxClientDC dc(wxConstCast(this, wxMenuBar));
1800 dc.SetFont(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT));
1801 dc.GetTextExtent(GetLabelTop(0), &size.x, &size.y);
1802
1803 // adjust for the renderer we use
1804 size = GetRenderer()->GetMenuBarItemSize(size);
1805 }
1806 else // empty menubar
1807 {
1808 size.x =
1809 size.y = 0;
1810 }
1811
1812 // the width is arbitrary, of course, for horizontal menubar
1813 size.x = 100;
1814
1815 return size;
1816 }
1817
1818 int wxMenuBar::GetMenuFromPoint(const wxPoint& pos) const
1819 {
1820 if ( pos.x < 0 || pos.y < 0 || pos.y > GetClientSize().y )
1821 return -1;
1822
1823 // do find it
1824 wxCoord x = 0;
1825 size_t count = GetCount();
1826 for ( size_t item = 0; item < count; item++ )
1827 {
1828 x += GetItemWidth(item);
1829
1830 if ( x > pos.x )
1831 {
1832 return item;
1833 }
1834 }
1835
1836 // to the right of the last menu item
1837 return -1;
1838 }
1839
1840 // ----------------------------------------------------------------------------
1841 // wxMenuBar menu operations
1842 // ----------------------------------------------------------------------------
1843
1844 void wxMenuBar::SelectMenu(size_t pos)
1845 {
1846 SetFocus();
1847 CaptureMouse();
1848
1849 DoSelectMenu(pos);
1850 }
1851
1852 void wxMenuBar::DoSelectMenu(size_t pos)
1853 {
1854 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in DoSelectMenu") );
1855
1856 int posOld = m_current;
1857
1858 m_current = pos;
1859
1860 if ( posOld != -1 )
1861 {
1862 // close the previous menu
1863 if ( IsShowingMenu() )
1864 {
1865 // restore m_shouldShowMenu flag after DismissMenu() which resets
1866 // it to FALSE
1867 bool old = m_shouldShowMenu;
1868
1869 DismissMenu();
1870
1871 m_shouldShowMenu = old;
1872 }
1873
1874 RefreshItem((size_t)posOld);
1875 }
1876
1877 RefreshItem(pos);
1878 }
1879
1880 void wxMenuBar::PopupMenu(size_t pos)
1881 {
1882 wxCHECK_RET( pos < GetCount(), _T("invalid menu index in PopupCurrentMenu") );
1883
1884 SetFocus();
1885 DoSelectMenu(pos);
1886 PopupCurrentMenu();
1887 }
1888
1889 // ----------------------------------------------------------------------------
1890 // wxMenuBar input handing
1891 // ----------------------------------------------------------------------------
1892
1893 /*
1894 Note that wxMenuBar doesn't use wxInputHandler but handles keyboard and
1895 mouse in the same way under all platforms. This is because it doesn't derive
1896 from wxControl (which works with input handlers) but directly from wxWindow.
1897
1898 Also, menu bar input handling is rather simple, so maybe it's not really
1899 worth making it themeable - at least I've decided against doing it now as it
1900 would merging the changes back into trunk more difficult. But it still could
1901 be done later if really needed.
1902 */
1903
1904 void wxMenuBar::OnKillFocus(wxFocusEvent& event)
1905 {
1906 if ( m_current != -1 )
1907 {
1908 RefreshItem((size_t)m_current);
1909
1910 m_current = -1;
1911 }
1912
1913 event.Skip();
1914 }
1915
1916 void wxMenuBar::OnLeftDown(wxMouseEvent& event)
1917 {
1918 if ( HasCapture() )
1919 {
1920 OnDismiss();
1921
1922 event.Skip();
1923 }
1924 else // we didn't have mouse capture, capture it now
1925 {
1926 m_current = GetMenuFromPoint(event.GetPosition());
1927 if ( m_current == -1 )
1928 {
1929 // unfortunately, we can't prevent wxMSW from giving us the focus,
1930 // so we can only give it back
1931 GiveAwayFocus();
1932 }
1933 else // on item
1934 {
1935 CaptureMouse();
1936
1937 // show it as selected
1938 RefreshItem((size_t)m_current);
1939
1940 // show the menu
1941 PopupCurrentMenu(FALSE /* don't select first item - as Windows does */);
1942 }
1943 }
1944 }
1945
1946 void wxMenuBar::OnMouseMove(wxMouseEvent& event)
1947 {
1948 if ( HasCapture() )
1949 {
1950 (void)ProcessMouseEvent(event.GetPosition());
1951 }
1952 else
1953 {
1954 event.Skip();
1955 }
1956 }
1957
1958 bool wxMenuBar::ProcessMouseEvent(const wxPoint& pt)
1959 {
1960 // a hack to ignore the extra mouse events MSW sends us: this is similar to
1961 // wxUSE_MOUSEEVENT_HACK in wxWin itself but it isn't enough for us here as
1962 // we get the messages from different windows (old and new popup menus for
1963 // example)
1964 #ifdef __WXMSW__
1965 static wxPoint s_ptLast;
1966 if ( pt == s_ptLast )
1967 {
1968 return FALSE;
1969 }
1970
1971 s_ptLast = pt;
1972 #endif // __WXMSW__
1973
1974 int currentNew = GetMenuFromPoint(pt);
1975 if ( (currentNew == -1) || (currentNew == m_current) )
1976 {
1977 return FALSE;
1978 }
1979
1980 // select the new active item
1981 DoSelectMenu(currentNew);
1982
1983 // show the menu if we know that we should, even if we hadn't been showing
1984 // it before (this may happen if the previous menu was disabled)
1985 if ( m_shouldShowMenu && !m_menuShown)
1986 {
1987 // open the new menu if the old one we closed had been opened
1988 PopupCurrentMenu(FALSE /* don't select first item - as Windows does */);
1989 }
1990
1991 return TRUE;
1992 }
1993
1994 void wxMenuBar::OnKeyDown(wxKeyEvent& event)
1995 {
1996 // ensure that we have a current item - we might not have it if we're
1997 // given the focus with Alt or F10 press (and under GTK+ the menubar
1998 // somehow gets the keyboard events even when it doesn't have focus...)
1999 if ( m_current == -1 )
2000 {
2001 if ( !HasCapture() )
2002 {
2003 SelectMenu(0);
2004 }
2005 else // we do have capture
2006 {
2007 // we always maintain a valid current item while we're in modal
2008 // state (i.e. have the capture)
2009 wxFAIL_MSG( _T("how did we manage to lose current item?") );
2010
2011 return;
2012 }
2013 }
2014
2015 int key = event.GetKeyCode();
2016
2017 // first let the menu have it
2018 if ( IsShowingMenu() && m_menuShown->ProcessKeyDown(key) )
2019 {
2020 return;
2021 }
2022
2023 // cycle through the menu items when left/right arrows are pressed and open
2024 // the menu when up/down one is
2025 switch ( key )
2026 {
2027 case WXK_MENU:
2028 // Alt must be processed at wxWindow level too
2029 event.Skip();
2030 // fall through
2031
2032 case WXK_ESCAPE:
2033 // remove the selection and give the focus away
2034 if ( m_current != -1 )
2035 {
2036 if ( IsShowingMenu() )
2037 {
2038 DismissMenu();
2039 }
2040
2041 OnDismiss();
2042 }
2043 break;
2044
2045 case WXK_LEFT:
2046 case WXK_RIGHT:
2047 {
2048 size_t count = GetCount();
2049 if ( count == 1 )
2050 {
2051 // the item won't change anyhow
2052 break;
2053 }
2054 //else: otherwise, it will
2055
2056 // remember if we were showing a menu - if we did, we should
2057 // show the new menu after changing the item
2058 bool wasMenuOpened = IsShowingMenu();
2059 if ( wasMenuOpened )
2060 {
2061 DismissMenu();
2062 }
2063
2064 // cast is safe as we tested for -1 above
2065 size_t currentNew = (size_t)m_current;
2066
2067 if ( key == WXK_LEFT )
2068 {
2069 if ( currentNew-- == 0 )
2070 currentNew = count - 1;
2071 }
2072 else // right
2073 {
2074 if ( ++currentNew == count )
2075 currentNew = 0;
2076 }
2077
2078 DoSelectMenu(currentNew);
2079
2080 if ( wasMenuOpened )
2081 {
2082 PopupCurrentMenu();
2083 }
2084 }
2085 break;
2086
2087 case WXK_DOWN:
2088 case WXK_UP:
2089 case WXK_RETURN:
2090 // open the menu
2091 PopupCurrentMenu();
2092 break;
2093
2094 default:
2095 // letters open the corresponding menu
2096 {
2097 bool unique;
2098 int idxFound = FindNextItemForAccel(m_current, key, &unique);
2099
2100 if ( idxFound != -1 )
2101 {
2102 if ( IsShowingMenu() )
2103 {
2104 DismissMenu();
2105 }
2106
2107 DoSelectMenu((size_t)idxFound);
2108
2109 // if the item is not unique, just select it but don't
2110 // activate as the user might have wanted to activate
2111 // another item
2112 //
2113 // also, don't try to open a disabled menu
2114 if ( unique && IsEnabledTop((size_t)idxFound) )
2115 {
2116 // open the menu
2117 PopupCurrentMenu();
2118 }
2119
2120 // skip the "event.Skip()" below
2121 break;
2122 }
2123 }
2124
2125 event.Skip();
2126 }
2127 }
2128
2129 // ----------------------------------------------------------------------------
2130 // wxMenuBar accel handling
2131 // ----------------------------------------------------------------------------
2132
2133 int wxMenuBar::FindNextItemForAccel(int idxStart, int key, bool *unique) const
2134 {
2135 if ( !wxIsalnum(key) )
2136 {
2137 // we only support letters/digits as accels
2138 return -1;
2139 }
2140
2141 // do we have more than one item with this accel?
2142 if ( unique )
2143 *unique = TRUE;
2144
2145 // translate everything to lower case before comparing
2146 wxChar chAccel = wxTolower(key);
2147
2148 // the index of the item with this accel
2149 int idxFound = -1;
2150
2151 // loop through all items searching for the item with this
2152 // accel starting at the item after the current one
2153 int count = GetCount();
2154 int n = idxStart == -1 ? 0 : idxStart + 1;
2155
2156 if ( n == count )
2157 {
2158 // wrap
2159 n = 0;
2160 }
2161
2162 idxStart = n;
2163 for ( ;; )
2164 {
2165 const wxMenuInfo& info = m_menuInfos[n];
2166
2167 int idxAccel = info.GetAccelIndex();
2168 if ( idxAccel != -1 &&
2169 wxTolower(info.GetLabel()[(size_t)idxAccel])
2170 == chAccel )
2171 {
2172 // ok, found an item with this accel
2173 if ( idxFound == -1 )
2174 {
2175 // store it but continue searching as we need to
2176 // know if it's the only item with this accel or if
2177 // there are more
2178 idxFound = n;
2179 }
2180 else // we already had found such item
2181 {
2182 if ( unique )
2183 *unique = FALSE;
2184
2185 // no need to continue further, we won't find
2186 // anything we don't already know
2187 break;
2188 }
2189 }
2190
2191 // we want to iterate over all items wrapping around if
2192 // necessary
2193 if ( ++n == count )
2194 {
2195 // wrap
2196 n = 0;
2197 }
2198
2199 if ( n == idxStart )
2200 {
2201 // we've seen all items
2202 break;
2203 }
2204 }
2205
2206 return idxFound;
2207 }
2208
2209 #if wxUSE_ACCEL
2210
2211 bool wxMenuBar::ProcessAccelEvent(const wxKeyEvent& event)
2212 {
2213 size_t n = 0;
2214 for ( wxMenuList::Node *node = m_menus.GetFirst();
2215 node;
2216 node = node->GetNext(), n++ )
2217 {
2218 // accels of the items in the disabled menus shouldn't work
2219 if ( m_menuInfos[n].IsEnabled() )
2220 {
2221 if ( node->GetData()->ProcessAccelEvent(event) )
2222 {
2223 // menu processed it
2224 return TRUE;
2225 }
2226 }
2227 }
2228
2229 // not found
2230 return FALSE;
2231 }
2232
2233 #endif // wxUSE_ACCEL
2234
2235 // ----------------------------------------------------------------------------
2236 // wxMenuBar menus showing
2237 // ----------------------------------------------------------------------------
2238
2239 void wxMenuBar::PopupCurrentMenu(bool selectFirst)
2240 {
2241 wxCHECK_RET( m_current != -1, _T("no menu to popup") );
2242
2243 // forgot to call DismissMenu()?
2244 wxASSERT_MSG( !m_menuShown, _T("shouldn't show two menu at once!") );
2245
2246 // in any case, we should show it - even if we won't
2247 m_shouldShowMenu = TRUE;
2248
2249 if ( IsEnabledTop(m_current) )
2250 {
2251 // remember the menu we show
2252 m_menuShown = GetMenu(m_current);
2253
2254 // we don't show the menu at all if it has no items
2255 if ( !m_menuShown->IsEmpty() )
2256 {
2257 // position it correctly: note that we must use screen coords and
2258 // that we pass 0 as width to position the menu exactly below the
2259 // item, not to the right of it
2260 wxRect rectItem = GetItemRect(m_current);
2261
2262 m_menuShown->Popup(ClientToScreen(rectItem.GetPosition()),
2263 wxSize(0, rectItem.GetHeight()),
2264 selectFirst);
2265 }
2266 else
2267 {
2268 // reset it back as no menu is shown
2269 m_menuShown = NULL;
2270 }
2271 }
2272 //else: don't show disabled menu
2273 }
2274
2275 void wxMenuBar::DismissMenu()
2276 {
2277 wxCHECK_RET( m_menuShown, _T("can't dismiss menu if none is shown") );
2278
2279 m_menuShown->Dismiss();
2280 OnDismissMenu();
2281 }
2282
2283 void wxMenuBar::OnDismissMenu(bool dismissMenuBar)
2284 {
2285 m_shouldShowMenu = FALSE;
2286 m_menuShown = NULL;
2287 if ( dismissMenuBar )
2288 {
2289 OnDismiss();
2290 }
2291 }
2292
2293 void wxMenuBar::OnDismiss()
2294 {
2295 ReleaseMouse();
2296
2297 if ( m_current != -1 )
2298 {
2299 size_t current = m_current;
2300 m_current = -1;
2301
2302 RefreshItem(current);
2303 }
2304
2305 GiveAwayFocus();
2306 }
2307
2308 void wxMenuBar::GiveAwayFocus()
2309 {
2310 GetFrame()->SetFocus();
2311 }
2312
2313 // ----------------------------------------------------------------------------
2314 // popup menu support
2315 // ----------------------------------------------------------------------------
2316
2317 wxEventLoop *wxWindow::ms_evtLoopPopup = NULL;
2318
2319 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
2320 {
2321 wxCHECK_MSG( !ms_evtLoopPopup, FALSE,
2322 _T("can't show more than one popup menu at a time") );
2323
2324 #ifdef __WXMSW__
2325 // we need to change the cursor before showing the menu as, apparently, no
2326 // cursor changes took place while the mouse is captured
2327 wxCursor cursorOld = GetCursor();
2328 SetCursor(wxCURSOR_ARROW);
2329 #endif // __WXMSW__
2330
2331 #if 0
2332 // flash any delayed log messages before showing the menu, otherwise it
2333 // could be dismissed (because it would lose focus) immediately after being
2334 // shown
2335 wxLog::FlushActive();
2336
2337 // some controls update themselves from OnIdle() call - let them do it
2338 wxIdleEvent event;
2339 wxTheApp->ProcessEvent(event);
2340
2341 // if the window hadn't been refreshed yet, the menu can adversely affect
2342 // its next OnPaint() handler execution - i.e. scrolled window refresh
2343 // logic breaks then as it scrolls part of the menu which hadn't been there
2344 // when the update event was generated into view
2345 Update();
2346 #endif // 0
2347
2348 menu->SetInvokingWindow(this);
2349 menu->Popup(ClientToScreen(wxPoint(x, y)), wxSize(0, 0));
2350
2351 // this is not very useful if the menu was popped up because of the mouse
2352 // click but I think it is nice to do when it appears because of a key
2353 // press (i.e. Windows menu key)
2354 //
2355 // Windows itself doesn't do it, but IMHO this is nice
2356 WarpPointer(x, y);
2357
2358 // we have to redirect all keyboard input to the menu temporarily
2359 PushEventHandler(new wxMenuKbdRedirector(menu));
2360
2361 // enter the local modal loop
2362 ms_evtLoopPopup = new wxEventLoop;
2363 ms_evtLoopPopup->Run();
2364
2365 delete ms_evtLoopPopup;
2366 ms_evtLoopPopup = NULL;
2367
2368 // remove the handler
2369 PopEventHandler(TRUE /* delete it */);
2370
2371 menu->SetInvokingWindow(NULL);
2372
2373 #ifdef __WXMSW__
2374 SetCursor(cursorOld);
2375 #endif // __WXMSW__
2376
2377 return TRUE;
2378 }
2379
2380 void wxWindow::DismissPopupMenu()
2381 {
2382 wxCHECK_RET( ms_evtLoopPopup, _T("no popup menu shown") );
2383
2384 ms_evtLoopPopup->Exit();
2385 }
2386
2387 #endif // wxUSE_MENUS
2388