Don't recurse into top level children in wxWindow::FindWindow().
[wxWidgets.git] / src / msw / menuitem.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/menuitem.cpp
3 // Purpose: wxMenuItem implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.11.97
7 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ===========================================================================
12 // declarations
13 // ===========================================================================
14
15 // ---------------------------------------------------------------------------
16 // headers
17 // ---------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_MENUS
27
28 #include "wx/menuitem.h"
29 #include "wx/stockitem.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/app.h"
33 #include "wx/dcmemory.h"
34 #include "wx/font.h"
35 #include "wx/bitmap.h"
36 #include "wx/settings.h"
37 #include "wx/window.h"
38 #include "wx/accel.h"
39 #include "wx/string.h"
40 #include "wx/log.h"
41 #include "wx/menu.h"
42 #endif
43
44 #if wxUSE_ACCEL
45 #include "wx/accel.h"
46 #endif // wxUSE_ACCEL
47
48 #include "wx/msw/private.h"
49 #include "wx/msw/dc.h"
50
51 #ifdef __WXWINCE__
52 // Implemented in menu.cpp
53 UINT GetMenuState(HMENU hMenu, UINT id, UINT flags) ;
54 #endif
55
56 #if wxUSE_UXTHEME
57 #include "wx/msw/uxtheme.h"
58 #endif
59
60 // ---------------------------------------------------------------------------
61 // macro
62 // ---------------------------------------------------------------------------
63
64 // hide the ugly cast
65 #define GetHMenuOf(menu) ((HMENU)menu->GetHMenu())
66
67 // ----------------------------------------------------------------------------
68 // helper classes for temporarily changing HDC parameters
69 // ----------------------------------------------------------------------------
70
71 namespace
72 {
73
74 // This class just stores an HDC.
75 class HDCHandler
76 {
77 protected:
78 HDCHandler(HDC hdc) : m_hdc(hdc) { }
79
80 const HDC m_hdc;
81 };
82
83 class HDCTextColChanger : HDCHandler
84 {
85 public:
86 HDCTextColChanger(HDC hdc, COLORREF col)
87 : HDCHandler(hdc),
88 m_colOld(::SetTextColor(hdc, col))
89 {
90 }
91
92 ~HDCTextColChanger()
93 {
94 ::SetTextColor(m_hdc, m_colOld);
95 }
96
97 private:
98 COLORREF m_colOld;
99 };
100
101 class HDCBgColChanger : HDCHandler
102 {
103 public:
104 HDCBgColChanger(HDC hdc, COLORREF col)
105 : HDCHandler(hdc),
106 m_colOld(::SetBkColor(hdc, col))
107 {
108 }
109
110 ~HDCBgColChanger()
111 {
112 ::SetBkColor(m_hdc, m_colOld);
113 }
114
115 private:
116 COLORREF m_colOld;
117 };
118
119 class HDCBgModeChanger : HDCHandler
120 {
121 public:
122 HDCBgModeChanger(HDC hdc, int mode)
123 : HDCHandler(hdc),
124 m_modeOld(::SetBkMode(hdc, mode))
125 {
126 }
127
128 ~HDCBgModeChanger()
129 {
130 ::SetBkMode(m_hdc, m_modeOld);
131 }
132
133 private:
134 int m_modeOld;
135 };
136
137 } // anonymous namespace
138
139 // ============================================================================
140 // implementation
141 // ============================================================================
142
143 #if wxUSE_OWNER_DRAWN
144
145 #include "wx/fontutil.h"
146 #include "wx/msw/private/metrics.h"
147
148 #ifndef SPI_GETKEYBOARDCUES
149 #define SPI_GETKEYBOARDCUES 0x100A
150 #endif
151
152 #ifndef DSS_HIDEPREFIX
153 #define DSS_HIDEPREFIX 0x0200
154 #endif
155
156 #if wxUSE_UXTHEME
157
158 enum MENUPARTS
159 {
160 MENU_MENUITEM_TMSCHEMA = 1,
161 MENU_SEPARATOR_TMSCHEMA = 6,
162 MENU_POPUPBACKGROUND = 9,
163 MENU_POPUPBORDERS = 10,
164 MENU_POPUPCHECK = 11,
165 MENU_POPUPCHECKBACKGROUND = 12,
166 MENU_POPUPGUTTER = 13,
167 MENU_POPUPITEM = 14,
168 MENU_POPUPSEPARATOR = 15,
169 MENU_POPUPSUBMENU = 16,
170 };
171
172
173 enum POPUPITEMSTATES
174 {
175 MPI_NORMAL = 1,
176 MPI_HOT = 2,
177 MPI_DISABLED = 3,
178 MPI_DISABLEDHOT = 4,
179 };
180
181 enum POPUPCHECKBACKGROUNDSTATES
182 {
183 MCB_DISABLED = 1,
184 MCB_NORMAL = 2,
185 MCB_BITMAP = 3,
186 };
187
188 enum POPUPCHECKSTATES
189 {
190 MC_CHECKMARKNORMAL = 1,
191 MC_CHECKMARKDISABLED = 2,
192 MC_BULLETNORMAL = 3,
193 MC_BULLETDISABLED = 4,
194 };
195
196 const int TMT_MENUFONT = 803;
197 const int TMT_BORDERSIZE = 2403;
198 const int TMT_CONTENTMARGINS = 3602;
199 const int TMT_SIZINGMARGINS = 3601;
200
201 #endif // wxUSE_UXTHEME
202
203 #endif // wxUSE_OWNER_DRAWN
204
205 // ----------------------------------------------------------------------------
206 // dynamic classes implementation
207 // ----------------------------------------------------------------------------
208
209 // ----------------------------------------------------------------------------
210 // wxMenuItem
211 // ----------------------------------------------------------------------------
212
213 #if wxUSE_OWNER_DRAWN
214
215 namespace
216 {
217
218 // helper class to keep information about metrics and other stuff
219 // needed for measuring and drawing menu item
220 class MenuDrawData
221 {
222 public:
223 // Wrapper around standard MARGINS structure providing some helper
224 // functions and automatically initializing the margin fields to 0.
225 struct Margins : MARGINS
226 {
227 Margins()
228 {
229 cxLeftWidth =
230 cxRightWidth =
231 cyTopHeight =
232 cyBottomHeight = 0;
233 }
234
235 int GetTotalX() const { return cxLeftWidth + cxRightWidth; }
236 int GetTotalY() const { return cyTopHeight + cyBottomHeight; }
237
238 void ApplyTo(RECT& rect) const
239 {
240 rect.top += cyTopHeight;
241 rect.left += cxLeftWidth;
242 rect.right -= cyTopHeight;
243 rect.bottom -= cyBottomHeight;
244 }
245
246 void UnapplyFrom(RECT& rect) const
247 {
248 rect.top -= cyTopHeight;
249 rect.left -= cxLeftWidth;
250 rect.right += cyTopHeight;
251 rect.bottom += cyBottomHeight;
252 }
253 };
254
255 Margins ItemMargin; // popup item margins
256
257 Margins CheckMargin; // popup check margins
258 Margins CheckBgMargin; // popup check background margins
259
260 Margins ArrowMargin; // popup submenu arrow margins
261
262 Margins SeparatorMargin; // popup separator margins
263
264 SIZE CheckSize; // popup check size metric
265 SIZE ArrowSize; // popup submenu arrow size metric
266 SIZE SeparatorSize; // popup separator size metric
267
268 int TextBorder; // popup border space between
269 // item text and gutter
270
271 int AccelBorder; // popup border space between
272 // item text and accelerator
273
274 int ArrowBorder; // popup border space between
275 // item accelerator and submenu arrow
276
277 int Offset; // system added space at the end of the menu,
278 // add this offset for remove the extra space
279
280 wxFont Font; // default menu font
281
282 bool AlwaysShowCues; // must keyboard cues always be shown?
283
284 bool Theme; // is data initialized for FullTheme?
285
286 static const MenuDrawData* Get()
287 {
288 // notice that s_menuData can't be created as a global variable because
289 // it needs a window to initialize and no windows exist at the time of
290 // globals initialization yet
291 if ( !ms_instance )
292 {
293 static MenuDrawData s_menuData;
294 ms_instance = &s_menuData;
295 }
296
297 #if wxUSE_UXTHEME
298 bool theme = MenuLayout() == FullTheme;
299 if ( ms_instance->Theme != theme )
300 ms_instance->Init();
301 #endif // wxUSE_UXTHEME
302 return ms_instance;
303 }
304
305 MenuDrawData()
306 {
307 Init();
308 }
309
310
311 // get the theme engine or NULL if themes
312 // are not available or not supported on menu
313 static wxUxThemeEngine *GetUxThemeEngine()
314 {
315 #if wxUSE_UXTHEME
316 if ( MenuLayout() == FullTheme )
317 return wxUxThemeEngine::GetIfActive();
318 #endif // wxUSE_UXTHEME
319 return NULL;
320 }
321
322
323 enum MenuLayoutType
324 {
325 FullTheme, // full menu themes (Vista or new)
326 PseudoTheme, // pseudo menu themes (on XP)
327 Classic
328 };
329
330 static MenuLayoutType MenuLayout()
331 {
332 MenuLayoutType menu = Classic;
333 #if wxUSE_UXTHEME
334 if ( wxUxThemeEngine::GetIfActive() != NULL )
335 {
336 static wxWinVersion ver = wxGetWinVersion();
337 if ( ver >= wxWinVersion_Vista )
338 menu = FullTheme;
339 else if ( ver == wxWinVersion_XP )
340 menu = PseudoTheme;
341 }
342 #endif // wxUSE_UXTHEME
343 return menu;
344 }
345
346 private:
347 void Init();
348
349 static MenuDrawData* ms_instance;
350 };
351
352 MenuDrawData* MenuDrawData::ms_instance = NULL;
353
354 void MenuDrawData::Init()
355 {
356 #if wxUSE_UXTHEME
357 wxUxThemeEngine* theme = GetUxThemeEngine();
358 if ( theme )
359 {
360 wxWindow* window = static_cast<wxApp*>(wxApp::GetInstance())->GetTopWindow();
361 wxUxThemeHandle hTheme(window, L"MENU");
362
363 theme->GetThemeMargins(hTheme, NULL, MENU_POPUPITEM, 0,
364 TMT_CONTENTMARGINS, NULL,
365 &ItemMargin);
366
367 theme->GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0,
368 TMT_CONTENTMARGINS, NULL,
369 &CheckMargin);
370 theme->GetThemeMargins(hTheme, NULL, MENU_POPUPCHECKBACKGROUND, 0,
371 TMT_CONTENTMARGINS, NULL,
372 &CheckBgMargin);
373
374 theme->GetThemeMargins(hTheme, NULL, MENU_POPUPSUBMENU, 0,
375 TMT_CONTENTMARGINS, NULL,
376 &ArrowMargin);
377
378 theme->GetThemeMargins(hTheme, NULL, MENU_POPUPSEPARATOR, 0,
379 TMT_SIZINGMARGINS, NULL,
380 &SeparatorMargin);
381
382 theme->GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0,
383 NULL, TS_TRUE, &CheckSize);
384
385 theme->GetThemePartSize(hTheme, NULL, MENU_POPUPSUBMENU, 0,
386 NULL, TS_TRUE, &ArrowSize);
387
388 theme->GetThemePartSize(hTheme, NULL, MENU_POPUPSEPARATOR, 0,
389 NULL, TS_TRUE, &SeparatorSize);
390
391 theme->GetThemeInt(hTheme, MENU_POPUPBACKGROUND, 0, TMT_BORDERSIZE, &TextBorder);
392
393 AccelBorder = 34;
394 ArrowBorder = 0;
395
396 Offset = -14;
397
398 wxUxThemeFont themeFont;
399 theme->GetThemeSysFont(hTheme, TMT_MENUFONT, themeFont.GetPtr());
400 Font = wxFont(themeFont.GetLOGFONT());
401
402 Theme = true;
403
404 // native menu doesn't uses the vertical margins
405 ItemMargin.cyTopHeight =
406 ItemMargin.cyBottomHeight = 0;
407
408 // native menu uses small top margin for separator
409 if ( SeparatorMargin.cyTopHeight >= 2 )
410 SeparatorMargin.cyTopHeight -= 2;
411 }
412 else
413 #endif // wxUSE_UXTHEME
414 {
415 const NONCLIENTMETRICS& metrics = wxMSWImpl::GetNonClientMetrics();
416
417 CheckMargin.cxLeftWidth =
418 CheckMargin.cxRightWidth = ::GetSystemMetrics(SM_CXEDGE);
419 CheckMargin.cyTopHeight =
420 CheckMargin.cyBottomHeight = ::GetSystemMetrics(SM_CYEDGE);
421
422 CheckSize.cx = ::GetSystemMetrics(SM_CXMENUCHECK);
423 CheckSize.cy = ::GetSystemMetrics(SM_CYMENUCHECK);
424
425 ArrowSize = CheckSize;
426
427 // separator height with margins
428 int sepFullSize = metrics.iMenuHeight / 2;
429
430 SeparatorMargin.cxLeftWidth =
431 SeparatorMargin.cxRightWidth = 1;
432 SeparatorMargin.cyTopHeight =
433 SeparatorMargin.cyBottomHeight = sepFullSize / 2 - 1;
434
435 SeparatorSize.cx = 1;
436 SeparatorSize.cy = sepFullSize - SeparatorMargin.GetTotalY();
437
438 TextBorder = 0;
439 AccelBorder = 8;
440 ArrowBorder = 6;
441
442 Offset = -12;
443
444 Font = wxFont(wxNativeFontInfo(metrics.lfMenuFont));
445
446 Theme = false;
447 }
448
449 int value;
450 if ( ::SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &value, 0) == 0 )
451 {
452 // if it's not supported, we must be on an old Windows version
453 // which always shows them
454 value = 1;
455 }
456
457 AlwaysShowCues = value == 1;
458
459 }
460
461 } // anonymous namespace
462
463 #endif // wxUSE_OWNER_DRAWN
464
465
466 // ctor & dtor
467 // -----------
468
469 wxMenuItem::wxMenuItem(wxMenu *pParentMenu,
470 int id,
471 const wxString& text,
472 const wxString& strHelp,
473 wxItemKind kind,
474 wxMenu *pSubMenu)
475 : wxMenuItemBase(pParentMenu, id, text, strHelp, kind, pSubMenu)
476 {
477 Init();
478 }
479
480 #if WXWIN_COMPATIBILITY_2_8
481 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
482 int id,
483 const wxString& text,
484 const wxString& help,
485 bool isCheckable,
486 wxMenu *subMenu)
487 : wxMenuItemBase(parentMenu, id, text, help,
488 isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
489 {
490 Init();
491 }
492 #endif
493
494 void wxMenuItem::Init()
495 {
496 #if wxUSE_OWNER_DRAWN
497
498 // when the color is not valid, wxOwnerDraw takes the default ones.
499 // If we set the colors here and they are changed by the user during
500 // the execution, then the colors are not updated until the application
501 // is restarted and our menus look bad
502 SetTextColour(wxNullColour);
503 SetBackgroundColour(wxNullColour);
504
505 // setting default colors switched ownerdraw on: switch it off again
506 SetOwnerDrawn(false);
507
508 // switch ownerdraw back on if using a non default margin
509 if ( !IsSeparator() )
510 SetMarginWidth(GetMarginWidth());
511
512 #endif // wxUSE_OWNER_DRAWN
513 }
514
515 wxMenuItem::~wxMenuItem()
516 {
517 }
518
519 // misc
520 // ----
521
522 // return the id for calling Win32 API functions
523 WXWPARAM wxMenuItem::GetMSWId() const
524 {
525 // we must use ids in unsigned short range with Windows functions, if we
526 // pass ids > USHRT_MAX to them they get very confused (e.g. start
527 // generating WM_COMMAND messages with negative high word of wParam), so
528 // use the cast to ensure the id is in range
529 return m_subMenu ? wxPtrToUInt(m_subMenu->GetHMenu())
530 : static_cast<unsigned short>(GetId());
531 }
532
533 // get item state
534 // --------------
535
536 bool wxMenuItem::IsChecked() const
537 {
538 // fix that RTTI is always getting the correct state (separators cannot be
539 // checked, but the Windows call below returns true
540 if ( IsSeparator() )
541 return false;
542
543 // the item might not be attached to a menu yet
544 //
545 // TODO: shouldn't we just always call the base class version? It seems
546 // like it ought to always be in sync
547 if ( !m_parentMenu )
548 return wxMenuItemBase::IsChecked();
549
550 HMENU hmenu = GetHMenuOf(m_parentMenu);
551 int flag = ::GetMenuState(hmenu, GetMSWId(), MF_BYCOMMAND);
552
553 return (flag & MF_CHECKED) != 0;
554 }
555
556 // change item state
557 // -----------------
558
559 void wxMenuItem::Enable(bool enable)
560 {
561 if ( m_isEnabled == enable )
562 return;
563
564 if ( m_parentMenu )
565 {
566 long rc = EnableMenuItem(GetHMenuOf(m_parentMenu),
567 GetMSWId(),
568 MF_BYCOMMAND |
569 (enable ? MF_ENABLED : MF_GRAYED));
570
571 if ( rc == -1 )
572 {
573 wxLogLastError(wxT("EnableMenuItem"));
574 }
575 }
576
577 wxMenuItemBase::Enable(enable);
578 }
579
580 void wxMenuItem::Check(bool check)
581 {
582 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
583
584 if ( m_isChecked == check )
585 return;
586
587 if ( m_parentMenu )
588 {
589 int flags = check ? MF_CHECKED : MF_UNCHECKED;
590 HMENU hmenu = GetHMenuOf(m_parentMenu);
591
592 if ( GetKind() == wxITEM_RADIO )
593 {
594 // it doesn't make sense to uncheck a radio item -- what would this
595 // do?
596 if ( !check )
597 return;
598
599 // get the index of this item in the menu
600 const wxMenuItemList& items = m_parentMenu->GetMenuItems();
601 int pos = items.IndexOf(this);
602 wxCHECK_RET( pos != wxNOT_FOUND,
603 wxT("menuitem not found in the menu items list?") );
604
605 // get the radio group range
606 int start,
607 end;
608
609 if ( !m_parentMenu->MSWGetRadioGroupRange(pos, &start, &end) )
610 {
611 wxFAIL_MSG( wxT("Menu radio item not part of radio group?") );
612 return;
613 }
614
615 #ifdef __WIN32__
616 // calling CheckMenuRadioItem() with such parameters hangs my system
617 // (NT4 SP6) and I suspect this could happen to the others as well,
618 // so don't do it!
619 wxCHECK_RET( start != -1 && end != -1,
620 wxT("invalid ::CheckMenuRadioItem() parameter(s)") );
621
622 if ( !::CheckMenuRadioItem(hmenu,
623 start, // the first radio group item
624 end, // the last one
625 pos, // the one to check
626 MF_BYPOSITION) )
627 {
628 wxLogLastError(wxT("CheckMenuRadioItem"));
629 }
630 #endif // __WIN32__
631
632 // also uncheck all the other items in this radio group
633 wxMenuItemList::compatibility_iterator node = items.Item(start);
634 for ( int n = start; n <= end && node; n++ )
635 {
636 if ( n != pos )
637 {
638 node->GetData()->m_isChecked = false;
639 }
640
641 node = node->GetNext();
642 }
643 }
644 else // check item
645 {
646 if ( ::CheckMenuItem(hmenu,
647 GetMSWId(),
648 MF_BYCOMMAND | flags) == (DWORD)-1 )
649 {
650 wxFAIL_MSG(wxT("CheckMenuItem() failed, item not in the menu?"));
651 }
652 }
653 }
654
655 wxMenuItemBase::Check(check);
656 }
657
658 void wxMenuItem::SetItemLabel(const wxString& txt)
659 {
660 wxString text = txt;
661
662 // don't do anything if label didn't change
663 if ( m_text == txt )
664 return;
665
666 // wxMenuItemBase will do stock ID checks
667 wxMenuItemBase::SetItemLabel(text);
668
669 // the item can be not attached to any menu yet and SetItemLabel() is still
670 // valid to call in this case and should do nothing else
671 if ( !m_parentMenu )
672 return;
673
674 #if wxUSE_ACCEL
675 m_parentMenu->UpdateAccel(this);
676 #endif // wxUSE_ACCEL
677
678 const UINT id = GetMSWId();
679 HMENU hMenu = GetHMenuOf(m_parentMenu);
680 if ( !hMenu || ::GetMenuState(hMenu, id, MF_BYCOMMAND) == (UINT)-1 )
681 return;
682
683 // update the text of the native menu item
684 WinStruct<MENUITEMINFO> info;
685
686 // surprisingly, calling SetMenuItemInfo() with just MIIM_STRING doesn't
687 // work as it resets the menu bitmap, so we need to first get the old item
688 // state and then modify it
689 const bool isLaterThanWin95 = wxGetWinVersion() > wxWinVersion_95;
690 info.fMask = MIIM_STATE |
691 MIIM_ID |
692 MIIM_SUBMENU |
693 MIIM_CHECKMARKS |
694 MIIM_DATA;
695 if ( isLaterThanWin95 )
696 info.fMask |= MIIM_BITMAP | MIIM_FTYPE;
697 else
698 info.fMask |= MIIM_TYPE;
699 if ( !::GetMenuItemInfo(hMenu, id, FALSE, &info) )
700 {
701 wxLogLastError(wxT("GetMenuItemInfo"));
702 return;
703 }
704
705 #if wxUSE_OWNER_DRAWN
706 // Don't set the text for the owner drawn items, they don't use it and even
707 // though setting it doesn't seem to actually do any harm under Windows 7,
708 // avoid doing this relatively nonsensical operation just in case it does
709 // break something on other, past or future, Windows versions.
710 //
711 // Notice that we do need to call SetMenuItemInfo() even for the ownerdrawn
712 // items however as otherwise their size wouldn't be recalculated as
713 // WM_MEASUREITEM wouldn't be sent and this could result in display
714 // problems if the length of the menu item changed significantly.
715 if ( !IsOwnerDrawn() )
716 #endif // wxUSE_OWNER_DRAWN
717 {
718 if ( isLaterThanWin95 )
719 info.fMask |= MIIM_STRING;
720 //else: MIIM_TYPE already specified
721 info.dwTypeData = wxMSW_CONV_LPTSTR(m_text);
722 info.cch = m_text.length();
723 }
724
725 if ( !::SetMenuItemInfo(hMenu, id, FALSE, &info) )
726 {
727 wxLogLastError(wxT("SetMenuItemInfo"));
728 }
729 }
730
731 #if wxUSE_OWNER_DRAWN
732
733 int wxMenuItem::MeasureAccelWidth() const
734 {
735 wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
736
737 wxMemoryDC dc;
738 wxFont font;
739 GetFontToUse(font);
740 dc.SetFont(font);
741
742 wxCoord w;
743 dc.GetTextExtent(accel, &w, NULL);
744
745 return w;
746 }
747
748 wxString wxMenuItem::GetName() const
749 {
750 return GetItemLabelText();
751 }
752
753 bool wxMenuItem::OnMeasureItem(size_t *width, size_t *height)
754 {
755 const MenuDrawData* data = MenuDrawData::Get();
756
757 if ( IsOwnerDrawn() )
758 {
759 *width = data->ItemMargin.GetTotalX();
760 *height = data->ItemMargin.GetTotalY();
761
762 if ( IsSeparator() )
763 {
764 *width += data->SeparatorSize.cx
765 + data->SeparatorMargin.GetTotalX();
766 *height += data->SeparatorSize.cy
767 + data->SeparatorMargin.GetTotalY();
768 return true;
769 }
770
771 wxString str = GetName();
772
773 wxMemoryDC dc;
774 wxFont font;
775 GetFontToUse(font);
776 dc.SetFont(font);
777
778 wxCoord w, h;
779 dc.GetTextExtent(str, &w, &h);
780
781 *width = data->TextBorder + w + data->AccelBorder;
782 *height = h;
783
784 w = m_parentMenu->GetMaxAccelWidth();
785 if ( w > 0 )
786 *width += w + data->ArrowBorder;
787
788 *width += data->Offset;
789 *width += data->ArrowMargin.GetTotalX() + data->ArrowSize.cx;
790 }
791 else // don't draw the text, just the bitmap (if any)
792 {
793 *width = 0;
794 *height = 0;
795 }
796
797 // bitmap
798
799 if ( IsOwnerDrawn() )
800 {
801 // width of menu icon with margins in ownerdrawn menu
802 // if any bitmap is not set, the width of space reserved for icon
803 // image is equal to the width of std check mark,
804 // if bitmap is set, then the width is set to the width of the widest
805 // bitmap in menu (GetMarginWidth()) unless std check mark is wider,
806 // then it's is set to std mark's width
807 int imgWidth = wxMax(GetMarginWidth(), data->CheckSize.cx)
808 + data->CheckMargin.GetTotalX();
809
810 *width += imgWidth + data->CheckBgMargin.GetTotalX();
811 }
812
813 if ( m_bmpChecked.IsOk() || m_bmpUnchecked.IsOk() )
814 {
815 // get size of bitmap always return valid value (0 for invalid bitmap),
816 // so we don't needed check if bitmap is valid ;)
817 size_t heightBmp = wxMax(m_bmpChecked.GetHeight(), m_bmpUnchecked.GetHeight());
818 size_t widthBmp = wxMax(m_bmpChecked.GetWidth(), m_bmpUnchecked.GetWidth());
819
820 if ( IsOwnerDrawn() )
821 {
822 heightBmp += data->CheckMargin.GetTotalY();
823 }
824 else
825 {
826 // we must allocate enough space for the bitmap
827 *width += widthBmp;
828 }
829
830 // Is BMP height larger than text height?
831 if ( *height < heightBmp )
832 *height = heightBmp;
833 }
834
835 // make sure that this item is at least as tall as the system menu height
836 const size_t menuHeight = data->CheckMargin.GetTotalY()
837 + data->CheckSize.cy;
838 if (*height < menuHeight)
839 *height = menuHeight;
840
841 return true;
842 }
843
844 bool wxMenuItem::OnDrawItem(wxDC& dc, const wxRect& rc,
845 wxODAction WXUNUSED(act), wxODStatus stat)
846 {
847 const MenuDrawData* data = MenuDrawData::Get();
848
849 wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
850 HDC hdc = GetHdcOf(*impl);
851
852 RECT rect;
853 wxCopyRectToRECT(rc, rect);
854
855 int imgWidth = wxMax(GetMarginWidth(), data->CheckSize.cx);
856
857 if ( IsOwnerDrawn() )
858 {
859 // font and colors to use
860 wxFont font;
861 GetFontToUse(font);
862
863 wxColour colText, colBack;
864 GetColourToUse(stat, colText, colBack);
865
866 // calculate metrics of item parts
867 RECT rcSelection = rect;
868 data->ItemMargin.ApplyTo(rcSelection);
869
870 RECT rcSeparator = rcSelection;
871 data->SeparatorMargin.ApplyTo(rcSeparator);
872
873 RECT rcGutter = rcSelection;
874 rcGutter.right = data->ItemMargin.cxLeftWidth
875 + data->CheckBgMargin.cxLeftWidth
876 + data->CheckMargin.cxLeftWidth
877 + imgWidth
878 + data->CheckMargin.cxRightWidth
879 + data->CheckBgMargin.cxRightWidth;
880
881 RECT rcText = rcSelection;
882 rcText.left = rcGutter.right + data->TextBorder;
883
884 // we draw the text label vertically centered, but this results in it
885 // being 1px too low compared to native menus for some reason, fix it
886 if ( data->MenuLayout() != MenuDrawData::FullTheme )
887 rcText.top--;
888
889 #if wxUSE_UXTHEME
890 // If a custom background colour is explicitly specified, we should use
891 // it instead of the default theme background.
892 wxUxThemeEngine* const theme = GetBackgroundColour().IsOk()
893 ? NULL
894 : MenuDrawData::GetUxThemeEngine();
895 if ( theme )
896 {
897 POPUPITEMSTATES state;
898 if ( stat & wxODDisabled )
899 {
900 state = (stat & wxODSelected) ? MPI_DISABLEDHOT
901 : MPI_DISABLED;
902 }
903 else if ( stat & wxODSelected )
904 {
905 state = MPI_HOT;
906 }
907 else
908 {
909 state = MPI_NORMAL;
910 }
911
912 wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
913
914 if ( theme->IsThemeBackgroundPartiallyTransparent(hTheme,
915 MENU_POPUPITEM, state) )
916 {
917 theme->DrawThemeBackground(hTheme, hdc,
918 MENU_POPUPBACKGROUND,
919 0, &rect, NULL);
920 }
921
922 theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPGUTTER,
923 0, &rcGutter, NULL);
924
925 if ( IsSeparator() )
926 {
927 rcSeparator.left = rcGutter.right;
928 theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPSEPARATOR,
929 0, &rcSeparator, NULL);
930 return true;
931 }
932
933 theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPITEM,
934 state, &rcSelection, NULL);
935
936 }
937 else
938 #endif // wxUSE_UXTHEME
939 {
940 if ( IsSeparator() )
941 {
942 DrawEdge(hdc, &rcSeparator, EDGE_ETCHED, BF_TOP);
943 return true;
944 }
945
946 AutoHBRUSH hbr(colBack.GetPixel());
947 SelectInHDC selBrush(hdc, hbr);
948 ::FillRect(hdc, &rcSelection, hbr);
949 }
950
951
952 // draw text label
953 // using native API because it recognizes '&'
954
955 HDCTextColChanger changeTextCol(hdc, colText.GetPixel());
956 HDCBgColChanger changeBgCol(hdc, colBack.GetPixel());
957 HDCBgModeChanger changeBgMode(hdc, TRANSPARENT);
958
959 SelectInHDC selFont(hdc, GetHfontOf(font));
960
961
962 // item text name without mnemonic for calculating size
963 wxString text = GetName();
964
965 SIZE textSize;
966 ::GetTextExtentPoint32(hdc, text.c_str(), text.length(), &textSize);
967
968 // item text name with mnemonic
969 text = GetItemLabel().BeforeFirst('\t');
970
971 int flags = DST_PREFIXTEXT;
972 // themes menu is using specified color for disabled labels
973 if ( data->MenuLayout() == MenuDrawData::Classic &&
974 (stat & wxODDisabled) && !(stat & wxODSelected) )
975 flags |= DSS_DISABLED;
976
977 if ( (stat & wxODHidePrefix) && !data->AlwaysShowCues )
978 flags |= DSS_HIDEPREFIX;
979
980 int x = rcText.left;
981 int y = rcText.top + (rcText.bottom - rcText.top - textSize.cy) / 2;
982
983 ::DrawState(hdc, NULL, NULL, wxMSW_CONV_LPARAM(text),
984 text.length(), x, y, 0, 0, flags);
985
986 // ::SetTextAlign(hdc, TA_RIGHT) doesn't work with DSS_DISABLED or DSS_MONO
987 // as the last parameter in DrawState() (at least with Windows98). So we have
988 // to take care of right alignment ourselves.
989 wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
990 if ( !accel.empty() )
991 {
992 SIZE accelSize;
993 ::GetTextExtentPoint32(hdc, accel.c_str(), accel.length(), &accelSize);
994
995 int flags = DST_TEXT;
996 // themes menu is using specified color for disabled labels
997 if ( data->MenuLayout() == MenuDrawData::Classic &&
998 (stat & wxODDisabled) && !(stat & wxODSelected) )
999 flags |= DSS_DISABLED;
1000
1001 int x = rcText.right - data->ArrowMargin.GetTotalX()
1002 - data->ArrowSize.cx
1003 - data->ArrowBorder;
1004
1005 // right align accel on FullTheme menu, left otherwise
1006 if ( data->MenuLayout() == MenuDrawData::FullTheme)
1007 x -= accelSize.cx;
1008 else
1009 x -= m_parentMenu->GetMaxAccelWidth();
1010
1011 int y = rcText.top + (rcText.bottom - rcText.top - accelSize.cy) / 2;
1012
1013 ::DrawState(hdc, NULL, NULL, wxMSW_CONV_LPARAM(accel),
1014 accel.length(), x, y, 0, 0, flags);
1015 }
1016 }
1017
1018
1019 // draw the bitmap
1020
1021 RECT rcImg;
1022 SetRect(&rcImg,
1023 rect.left + data->ItemMargin.cxLeftWidth
1024 + data->CheckBgMargin.cxLeftWidth
1025 + data->CheckMargin.cxLeftWidth,
1026 rect.top + data->ItemMargin.cyTopHeight
1027 + data->CheckBgMargin.cyTopHeight
1028 + data->CheckMargin.cyTopHeight,
1029 rect.left + data->ItemMargin.cxLeftWidth
1030 + data->CheckBgMargin.cxLeftWidth
1031 + data->CheckMargin.cxLeftWidth
1032 + imgWidth,
1033 rect.bottom - data->ItemMargin.cyBottomHeight
1034 - data->CheckBgMargin.cyBottomHeight
1035 - data->CheckMargin.cyBottomHeight);
1036
1037 if ( IsCheckable() && !m_bmpChecked.IsOk() )
1038 {
1039 if ( stat & wxODChecked )
1040 {
1041 DrawStdCheckMark((WXHDC)hdc, &rcImg, stat);
1042 }
1043 }
1044 else
1045 {
1046 wxBitmap bmp;
1047
1048 if ( stat & wxODDisabled )
1049 {
1050 bmp = GetDisabledBitmap();
1051 }
1052
1053 if ( !bmp.IsOk() )
1054 {
1055 // for not checkable bitmaps we should always use unchecked one
1056 // because their checked bitmap is not set
1057 bmp = GetBitmap(!IsCheckable() || (stat & wxODChecked));
1058
1059 #if wxUSE_IMAGE
1060 if ( bmp.IsOk() && stat & wxODDisabled )
1061 {
1062 // we need to grey out the bitmap as we don't have any specific
1063 // disabled bitmap
1064 wxImage imgGrey = bmp.ConvertToImage().ConvertToGreyscale();
1065 if ( imgGrey.IsOk() )
1066 bmp = wxBitmap(imgGrey);
1067 }
1068 #endif // wxUSE_IMAGE
1069 }
1070
1071 if ( bmp.IsOk() )
1072 {
1073 wxMemoryDC dcMem(&dc);
1074 dcMem.SelectObjectAsSource(bmp);
1075
1076 // center bitmap
1077 int nBmpWidth = bmp.GetWidth(),
1078 nBmpHeight = bmp.GetHeight();
1079
1080 int x = rcImg.left + (imgWidth - nBmpWidth) / 2;
1081 int y = rcImg.top + (rcImg.bottom - rcImg.top - nBmpHeight) / 2;
1082 dc.Blit(x, y, nBmpWidth, nBmpHeight, &dcMem, 0, 0, wxCOPY, true);
1083 }
1084 }
1085
1086 return true;
1087
1088 }
1089
1090 namespace
1091 {
1092
1093 // helper function for draw coloured check mark
1094 void DrawColorCheckMark(HDC hdc, int x, int y, int cx, int cy, HDC hdcCheckMask, int idxColor)
1095 {
1096 const COLORREF colBlack = RGB(0, 0, 0);
1097 const COLORREF colWhite = RGB(255, 255, 255);
1098
1099 HDCTextColChanger changeTextCol(hdc, colBlack);
1100 HDCBgColChanger changeBgCol(hdc, colWhite);
1101 HDCBgModeChanger changeBgMode(hdc, TRANSPARENT);
1102
1103 // memory DC for color bitmap
1104 MemoryHDC hdcMem(hdc);
1105 CompatibleBitmap hbmpMem(hdc, cx, cy);
1106 SelectInHDC selMem(hdcMem, hbmpMem);
1107
1108 RECT rect = { 0, 0, cx, cy };
1109 ::FillRect(hdcMem, &rect, ::GetSysColorBrush(idxColor));
1110
1111 const COLORREF colCheck = ::GetSysColor(idxColor);
1112 if ( colCheck == colWhite )
1113 {
1114 ::BitBlt(hdc, x, y, cx, cy, hdcCheckMask, 0, 0, MERGEPAINT);
1115 ::BitBlt(hdc, x, y, cx, cy, hdcMem, 0, 0, SRCAND);
1116 }
1117 else
1118 {
1119 if ( colCheck != colBlack )
1120 {
1121 const DWORD ROP_DSna = 0x00220326; // dest = (NOT src) AND dest
1122 ::BitBlt(hdcMem, 0, 0, cx, cy, hdcCheckMask, 0, 0, ROP_DSna);
1123 }
1124
1125 ::BitBlt(hdc, x, y, cx, cy, hdcCheckMask, 0, 0, SRCAND);
1126 ::BitBlt(hdc, x, y, cx, cy, hdcMem, 0, 0, SRCPAINT);
1127 }
1128 }
1129
1130 } // anonymous namespace
1131
1132 void wxMenuItem::DrawStdCheckMark(WXHDC hdc_, const RECT* rc, wxODStatus stat)
1133 {
1134 HDC hdc = (HDC)hdc_;
1135
1136 #if wxUSE_UXTHEME
1137 wxUxThemeEngine* theme = MenuDrawData::GetUxThemeEngine();
1138 if ( theme )
1139 {
1140 wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
1141
1142 const MenuDrawData* data = MenuDrawData::Get();
1143
1144 // rect for background must be without check margins
1145 RECT rcBg = *rc;
1146 data->CheckMargin.UnapplyFrom(rcBg);
1147
1148 POPUPCHECKBACKGROUNDSTATES stateCheckBg = (stat & wxODDisabled)
1149 ? MCB_DISABLED
1150 : MCB_NORMAL;
1151
1152 theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECKBACKGROUND,
1153 stateCheckBg, &rcBg, NULL);
1154
1155 POPUPCHECKSTATES stateCheck;
1156 if ( GetKind() == wxITEM_CHECK )
1157 {
1158 stateCheck = (stat & wxODDisabled) ? MC_CHECKMARKDISABLED
1159 : MC_CHECKMARKNORMAL;
1160 }
1161 else
1162 {
1163 stateCheck = (stat & wxODDisabled) ? MC_BULLETDISABLED
1164 : MC_BULLETNORMAL;
1165 }
1166
1167 theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECK,
1168 stateCheck, rc, NULL);
1169 }
1170 else
1171 #endif // wxUSE_UXTHEME
1172 {
1173 int cx = rc->right - rc->left;
1174 int cy = rc->bottom - rc->top;
1175
1176 // first create mask of check mark
1177 MemoryHDC hdcMask(hdc);
1178 MonoBitmap hbmpMask(cx, cy);
1179 SelectInHDC selMask(hdcMask,hbmpMask);
1180
1181 // then draw a check mark into it
1182 UINT stateCheck = (GetKind() == wxITEM_CHECK) ? DFCS_MENUCHECK
1183 : DFCS_MENUBULLET;
1184 RECT rect = { 0, 0, cx, cy };
1185 ::DrawFrameControl(hdcMask, &rect, DFC_MENU, stateCheck);
1186
1187 // first draw shadow if disabled
1188 if ( (stat & wxODDisabled) && !(stat & wxODSelected) )
1189 {
1190 DrawColorCheckMark(hdc, rc->left + 1, rc->top + 1,
1191 cx, cy, hdcMask, COLOR_3DHILIGHT);
1192 }
1193
1194 // then draw a check mark
1195 int color = COLOR_MENUTEXT;
1196 if ( stat & wxODDisabled )
1197 color = COLOR_BTNSHADOW;
1198 else if ( stat & wxODSelected )
1199 color = COLOR_HIGHLIGHTTEXT;
1200
1201 DrawColorCheckMark(hdc, rc->left, rc->top, cx, cy, hdcMask, color);
1202 }
1203 }
1204
1205 void wxMenuItem::GetFontToUse(wxFont& font) const
1206 {
1207 font = GetFont();
1208 if ( !font.IsOk() )
1209 font = MenuDrawData::Get()->Font;
1210 }
1211
1212 void wxMenuItem::GetColourToUse(wxODStatus stat, wxColour& colText, wxColour& colBack) const
1213 {
1214 #if wxUSE_UXTHEME
1215 wxUxThemeEngine* theme = MenuDrawData::GetUxThemeEngine();
1216 if ( theme )
1217 {
1218 wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
1219
1220 if ( stat & wxODDisabled)
1221 {
1222 wxRGBToColour(colText, theme->GetThemeSysColor(hTheme, COLOR_GRAYTEXT));
1223 }
1224 else
1225 {
1226 colText = GetTextColour();
1227 if ( !colText.IsOk() )
1228 wxRGBToColour(colText, theme->GetThemeSysColor(hTheme, COLOR_MENUTEXT));
1229 }
1230
1231 if ( stat & wxODSelected )
1232 {
1233 wxRGBToColour(colBack, theme->GetThemeSysColor(hTheme, COLOR_HIGHLIGHT));
1234 }
1235 else
1236 {
1237 colBack = GetBackgroundColour();
1238 if ( !colBack.IsOk() )
1239 wxRGBToColour(colBack, theme->GetThemeSysColor(hTheme, COLOR_MENU));
1240 }
1241 }
1242 else
1243 #endif // wxUSE_UXTHEME
1244 {
1245 wxOwnerDrawn::GetColourToUse(stat, colText, colBack);
1246 }
1247 }
1248 #endif // wxUSE_OWNER_DRAWN
1249
1250 // ----------------------------------------------------------------------------
1251 // wxMenuItemBase
1252 // ----------------------------------------------------------------------------
1253
1254 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
1255 int id,
1256 const wxString& name,
1257 const wxString& help,
1258 wxItemKind kind,
1259 wxMenu *subMenu)
1260 {
1261 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
1262 }
1263
1264 #endif // wxUSE_MENUS