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