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