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