1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/menuitem.cpp
3 // Purpose: wxMenuItem implementation
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 // ===========================================================================
14 // ===========================================================================
16 // ---------------------------------------------------------------------------
18 // ---------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
29 #include "wx/menuitem.h"
30 #include "wx/stockitem.h"
34 #include "wx/bitmap.h"
35 #include "wx/settings.h"
36 #include "wx/window.h"
38 #include "wx/string.h"
47 #include "wx/msw/private.h"
48 #include "wx/msw/dc.h"
51 // Implemented in menu.cpp
52 UINT
GetMenuState(HMENU hMenu
, UINT id
, UINT flags
) ;
55 // ---------------------------------------------------------------------------
57 // ---------------------------------------------------------------------------
60 #define GetHMenuOf(menu) ((HMENU)menu->GetHMenu())
62 // ============================================================================
64 // ============================================================================
68 #include "wx/fontutil.h"
69 #include "wx/msw/private/metrics.h"
71 #ifndef SPI_GETKEYBOARDCUES
72 #define SPI_GETKEYBOARDCUES 0x100A
75 #ifndef DSS_HIDEPREFIX
76 #define DSS_HIDEPREFIX 0x0200
79 #endif // wxUSE_OWNER_DRAWN
81 // ----------------------------------------------------------------------------
82 // dynamic classes implementation
83 // ----------------------------------------------------------------------------
85 #if wxUSE_EXTENDED_RTTI
87 bool wxMenuItemStreamingCallback( const wxObject
*object
, wxWriter
* , wxPersister
* , wxxVariantArray
& )
89 const wxMenuItem
* mitem
= dynamic_cast<const wxMenuItem
*>(object
) ;
90 if ( mitem
->GetMenu() && !mitem
->GetMenu()->GetTitle().empty() )
92 // we don't stream out the first two items for menus with a title, they will be reconstructed
93 if ( mitem
->GetMenu()->FindItemByPosition(0) == mitem
|| mitem
->GetMenu()->FindItemByPosition(1) == mitem
)
99 wxBEGIN_ENUM( wxItemKind
)
100 wxENUM_MEMBER( wxITEM_SEPARATOR
)
101 wxENUM_MEMBER( wxITEM_NORMAL
)
102 wxENUM_MEMBER( wxITEM_CHECK
)
103 wxENUM_MEMBER( wxITEM_RADIO
)
104 wxEND_ENUM( wxItemKind
)
106 IMPLEMENT_DYNAMIC_CLASS_XTI_CALLBACK(wxMenuItem
, wxObject
,"wx/menuitem.h",wxMenuItemStreamingCallback
)
108 wxBEGIN_PROPERTIES_TABLE(wxMenuItem
)
109 wxPROPERTY( Parent
,wxMenu
*, SetMenu
, GetMenu
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
110 wxPROPERTY( Id
,int, SetId
, GetId
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
111 wxPROPERTY( Text
, wxString
, SetText
, GetText
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
112 wxPROPERTY( Help
, wxString
, SetHelp
, GetHelp
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
113 wxREADONLY_PROPERTY( Kind
, wxItemKind
, GetKind
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
114 wxPROPERTY( SubMenu
,wxMenu
*, SetSubMenu
, GetSubMenu
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
115 wxPROPERTY( Enabled
, bool , Enable
, IsEnabled
, wxxVariant((bool)true) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
116 wxPROPERTY( Checked
, bool , Check
, IsChecked
, wxxVariant((bool)false) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
117 wxPROPERTY( Checkable
, bool , SetCheckable
, IsCheckable
, wxxVariant((bool)false) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
118 wxEND_PROPERTIES_TABLE()
120 wxBEGIN_HANDLERS_TABLE(wxMenuItem
)
121 wxEND_HANDLERS_TABLE()
123 wxDIRECT_CONSTRUCTOR_6( wxMenuItem
, wxMenu
* , Parent
, int , Id
, wxString
, Text
, wxString
, Help
, wxItemKind
, Kind
, wxMenu
* , SubMenu
)
125 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem
, wxObject
)
128 // ----------------------------------------------------------------------------
130 // ----------------------------------------------------------------------------
132 #if wxUSE_OWNER_DRAWN
134 // these static variables are from the wxMenuItem object for cache
135 // system settings returned by the Win32 API's SystemParametersInfo() call
136 wxFont
wxMenuItem::ms_systemMenuFont
;
137 size_t wxMenuItem::ms_systemMenuHeight
= 0;
138 bool wxMenuItem::ms_alwaysShowCues
= false;
140 #endif // wxUSE_OWNER_DRAWN
146 wxMenuItem::wxMenuItem(wxMenu
*pParentMenu
,
148 const wxString
& text
,
149 const wxString
& strHelp
,
152 : wxMenuItemBase(pParentMenu
, id
, text
, strHelp
, kind
, pSubMenu
)
157 #if WXWIN_COMPATIBILITY_2_8
158 wxMenuItem::wxMenuItem(wxMenu
*parentMenu
,
160 const wxString
& text
,
161 const wxString
& help
,
164 : wxMenuItemBase(parentMenu
, id
, text
, help
,
165 isCheckable
? wxITEM_CHECK
: wxITEM_NORMAL
, subMenu
)
171 void wxMenuItem::Init()
173 m_radioGroup
.start
= -1;
174 m_isRadioGroupStart
= false;
176 #if wxUSE_OWNER_DRAWN
178 // init static varaibles
179 if ( !ms_systemMenuHeight
)
181 const NONCLIENTMETRICS
& metrics
= wxMSWImpl::GetNonClientMetrics();
183 ms_systemMenuFont
= wxFont(wxNativeFontInfo(metrics
.lfMenuFont
));
184 ms_systemMenuHeight
= metrics
.iMenuHeight
;
186 if ( ::SystemParametersInfo(SPI_GETKEYBOARDCUES
, 0,
187 &ms_alwaysShowCues
, 0) == 0 )
189 // if it's not supported, we must be on an old Windows version
190 // which always shows them
191 ms_alwaysShowCues
= true;
196 // when the color is not valid, wxOwnerDraw takes the default ones.
197 // If we set the colors here and they are changed by the user during
198 // the execution, then the colors are not updated until the application
199 // is restarted and our menus look bad
200 SetTextColour(wxNullColour
);
201 SetBackgroundColour(wxNullColour
);
203 // setting default colors switched ownerdraw on: switch it off again
204 SetOwnerDrawn(false);
206 // switch ownerdraw back on if using a non default margin
207 if ( !IsSeparator() )
208 SetMarginWidth(GetMarginWidth());
210 #endif // wxUSE_OWNER_DRAWN
213 wxMenuItem::~wxMenuItem()
220 // return the id for calling Win32 API functions
221 WXWPARAM
wxMenuItem::GetMSWId() const
223 // we must use ids in unsigned short range with Windows functions, if we
224 // pass ids > USHRT_MAX to them they get very confused (e.g. start
225 // generating WM_COMMAND messages with negative high word of wParam), so
226 // use the cast to ensure the id is in range
227 return m_subMenu
? wxPtrToUInt(m_subMenu
->GetHMenu())
228 : static_cast<unsigned short>(GetId());
234 bool wxMenuItem::IsChecked() const
236 // fix that RTTI is always getting the correct state (separators cannot be
237 // checked, but the Windows call below returns true
241 // the item might not be attached to a menu yet
243 // TODO: shouldn't we just always call the base class version? It seems
244 // like it ought to always be in sync
246 return wxMenuItemBase::IsChecked();
248 HMENU hmenu
= GetHMenuOf(m_parentMenu
);
249 int flag
= ::GetMenuState(hmenu
, GetMSWId(), MF_BYCOMMAND
);
251 return (flag
& MF_CHECKED
) != 0;
257 void wxMenuItem::SetAsRadioGroupStart()
259 m_isRadioGroupStart
= true;
262 void wxMenuItem::SetRadioGroupStart(int start
)
264 wxASSERT_MSG( !m_isRadioGroupStart
,
265 wxT("should only be called for the next radio items") );
267 m_radioGroup
.start
= start
;
270 void wxMenuItem::SetRadioGroupEnd(int end
)
272 wxASSERT_MSG( m_isRadioGroupStart
,
273 wxT("should only be called for the first radio item") );
275 m_radioGroup
.end
= end
;
281 void wxMenuItem::Enable(bool enable
)
283 if ( m_isEnabled
== enable
)
288 long rc
= EnableMenuItem(GetHMenuOf(m_parentMenu
),
291 (enable
? MF_ENABLED
: MF_GRAYED
));
295 wxLogLastError(wxT("EnableMenuItem"));
299 wxMenuItemBase::Enable(enable
);
302 void wxMenuItem::Check(bool check
)
304 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
306 if ( m_isChecked
== check
)
311 int flags
= check
? MF_CHECKED
: MF_UNCHECKED
;
312 HMENU hmenu
= GetHMenuOf(m_parentMenu
);
314 if ( GetKind() == wxITEM_RADIO
)
316 // it doesn't make sense to uncheck a radio item -- what would this
321 // get the index of this item in the menu
322 const wxMenuItemList
& items
= m_parentMenu
->GetMenuItems();
323 int pos
= items
.IndexOf(this);
324 wxCHECK_RET( pos
!= wxNOT_FOUND
,
325 wxT("menuitem not found in the menu items list?") );
327 // get the radio group range
331 if ( m_isRadioGroupStart
)
333 // we already have all information we need
335 end
= m_radioGroup
.end
;
337 else // next radio group item
339 // get the radio group end from the start item
340 start
= m_radioGroup
.start
;
341 end
= items
.Item(start
)->GetData()->m_radioGroup
.end
;
345 // calling CheckMenuRadioItem() with such parameters hangs my system
346 // (NT4 SP6) and I suspect this could happen to the others as well,
348 wxCHECK_RET( start
!= -1 && end
!= -1,
349 wxT("invalid ::CheckMenuRadioItem() parameter(s)") );
351 if ( !::CheckMenuRadioItem(hmenu
,
352 start
, // the first radio group item
354 pos
, // the one to check
357 wxLogLastError(wxT("CheckMenuRadioItem"));
361 // also uncheck all the other items in this radio group
362 wxMenuItemList::compatibility_iterator node
= items
.Item(start
);
363 for ( int n
= start
; n
<= end
&& node
; n
++ )
367 node
->GetData()->m_isChecked
= false;
370 node
= node
->GetNext();
375 if ( ::CheckMenuItem(hmenu
,
377 MF_BYCOMMAND
| flags
) == (DWORD
)-1 )
379 wxFAIL_MSG(wxT("CheckMenuItem() failed, item not in the menu?"));
384 wxMenuItemBase::Check(check
);
387 void wxMenuItem::SetItemLabel(const wxString
& txt
)
391 // don't do anything if label didn't change
395 // wxMenuItemBase will do stock ID checks
396 wxMenuItemBase::SetItemLabel(text
);
398 // the item can be not attached to any menu yet and SetItemLabel() is still
399 // valid to call in this case and should do nothing else
404 m_parentMenu
->UpdateAccel(this);
405 #endif // wxUSE_ACCEL
407 const UINT id
= GetMSWId();
408 HMENU hMenu
= GetHMenuOf(m_parentMenu
);
409 if ( !hMenu
|| ::GetMenuState(hMenu
, id
, MF_BYCOMMAND
) == (UINT
)-1 )
412 #if wxUSE_OWNER_DRAWN
413 if ( IsOwnerDrawn() )
415 // we don't need to do anything for owner drawn items, they will redraw
416 // themselves using the new text the next time they're displayed
419 #endif // owner drawn
421 // update the text of the native menu item
422 WinStruct
<MENUITEMINFO
> info
;
424 // surprisingly, calling SetMenuItemInfo() with just MIIM_STRING doesn't
425 // work as it resets the menu bitmap, so we need to first get the old item
426 // state and then modify it
427 const bool isLaterThanWin95
= wxGetWinVersion() > wxWinVersion_95
;
428 info
.fMask
= MIIM_STATE
|
433 if ( isLaterThanWin95
)
434 info
.fMask
|= MIIM_BITMAP
| MIIM_FTYPE
;
436 info
.fMask
|= MIIM_TYPE
;
437 if ( !::GetMenuItemInfo(hMenu
, id
, FALSE
, &info
) )
439 wxLogLastError(wxT("GetMenuItemInfo"));
443 if ( isLaterThanWin95
)
444 info
.fMask
|= MIIM_STRING
;
445 //else: MIIM_TYPE already specified
446 info
.dwTypeData
= (LPTSTR
)m_text
.wx_str();
447 info
.cch
= m_text
.length();
448 if ( !::SetMenuItemInfo(hMenu
, id
, FALSE
, &info
) )
450 wxLogLastError(wxT("SetMenuItemInfo"));
454 #if wxUSE_OWNER_DRAWN
456 wxString
wxMenuItem::GetName() const
458 return GetItemLabelText();
461 bool wxMenuItem::OnMeasureItem(size_t *width
, size_t *height
)
463 if ( IsOwnerDrawn() )
466 wxString str
= GetName();
468 // if we have a valid accel string, then pad out
469 // the menu string so that the menu and accel string are not
470 // placed on top of each other.
471 wxString accel
= GetItemLabel().AfterFirst(wxT('\t'));
472 if ( !accel
.empty() )
474 str
.Pad(str
.length()%8
);
484 dc
.GetTextExtent(str
, &w
, &h
);
488 else // don't draw the text, just the bitmap (if any)
494 // increase size to accommodate bigger bitmaps if necessary
495 if (m_bmpChecked
.Ok())
497 // Is BMP height larger than text height?
498 size_t adjustedHeight
= m_bmpChecked
.GetHeight();
499 if ( *height
< adjustedHeight
)
500 *height
= adjustedHeight
;
502 const int widthBmp
= m_bmpChecked
.GetWidth();
503 if ( IsOwnerDrawn() )
505 // widen the margin to fit the bitmap if necessary
506 if ( GetMarginWidth() < widthBmp
)
507 SetMarginWidth(widthBmp
);
510 else // we must allocate enough space for the bitmap
516 // add a 4-pixel separator, otherwise menus look cluttered
519 // notice that this adjustment must be done after (possibly) changing the
520 // margin width above
521 if ( IsOwnerDrawn() )
523 // add space at the end of the menu for the submenu expansion arrow
524 // this will also allow offsetting the accel string from the right edge
525 *width
+= GetMarginWidth() + 16;
528 // make sure that this item is at least as tall as the system menu height
529 if ( *height
< ms_systemMenuHeight
)
530 *height
= ms_systemMenuHeight
;
535 bool wxMenuItem::OnDrawItem(wxDC
& dc
, const wxRect
& rc
,
536 wxODAction
WXUNUSED(act
), wxODStatus stat
)
539 // this flag determines whether or not an edge will
540 // be drawn around the bitmap. In most "windows classic"
541 // applications, a 1-pixel highlight edge is drawn around
542 // the bitmap of an item when it is selected. However,
543 // with the new "luna" theme, no edge is drawn around
544 // the bitmap because the background is white (this applies
545 // only to "non-XP style" menus w/ bitmaps --
546 // see IE 6 menus for an example)
548 bool draw_bitmap_edge
= true;
552 wxColour colText1
, colBack1
;
553 GetColourToUse(stat
, colText1
, colBack1
);
555 DWORD colText
= wxColourToPalRGB(colText1
);
556 DWORD colBack
= wxColourToPalRGB(colBack1
);
558 if ( IsOwnerDrawn() )
560 // don't draw an edge around the bitmap, if background is white ...
561 DWORD menu_bg_color
= GetSysColor(COLOR_MENU
);
562 if ( GetRValue( menu_bg_color
) >= 0xf0 &&
563 GetGValue( menu_bg_color
) >= 0xf0 &&
564 GetBValue( menu_bg_color
) >= 0xf0 )
566 draw_bitmap_edge
= false;
569 else // edge doesn't look well with default Windows drawing
571 draw_bitmap_edge
= false;
575 wxMSWDCImpl
*impl
= (wxMSWDCImpl
*) dc
.GetImpl();
576 HDC hdc
= GetHdcOf(*impl
);
577 COLORREF colOldText
= ::SetTextColor(hdc
, colText
);
578 COLORREF colOldBack
= ::SetBkColor(hdc
, colBack
);
580 // *2, as in wxSYS_EDGE_Y
581 int margin
= GetMarginWidth() + 2 * wxSystemSettings::GetMetric(wxSYS_EDGE_X
);
583 // select the font and draw the text
584 // ---------------------------------
587 // determine where to draw and leave space for a check-mark.
588 // + 1 pixel to separate the edge from the highlight rectangle
589 int xText
= rc
.x
+ margin
+ 1;
592 // using native API because it recognizes '&'
593 if ( IsOwnerDrawn() )
595 int prevMode
= SetBkMode(hdc
, TRANSPARENT
);
596 AutoHBRUSH
hbr(colBack
);
597 SelectInHDC
selBrush(hdc
, hbr
);
600 wxCopyRectToRECT(rc
, rectFill
);
602 if ( (stat
& wxODSelected
) && m_bmpChecked
.Ok() && draw_bitmap_edge
)
604 // only draw the highlight under the text, not under
605 // the bitmap or checkmark
606 rectFill
.left
= xText
;
609 ::FillRect(hdc
, &rectFill
, hbr
);
611 // use default font if no font set
614 SelectInHDC
selFont(hdc
, GetHfontOf(font
));
616 // item text name with menemonic
617 wxString text
= GetItemLabel().BeforeFirst('\t');
619 xText
+= 3; // separate text from the highlight rectangle
622 ::GetTextExtentPoint32(hdc
, text
.c_str(), text
.length(), &textRect
);
624 int flags
= DST_PREFIXTEXT
;
625 if ( (stat
& wxODDisabled
) && !(stat
& wxODSelected
) )
626 flags
|= DSS_DISABLED
;
628 if ( (stat
& wxODHidePrefix
) && !ms_alwaysShowCues
)
629 flags
|= DSS_HIDEPREFIX
;
632 int y
= rc
.y
+ (rc
.GetHeight() - textRect
.cy
) / 2;
633 int cx
= rc
.GetWidth() - GetMarginWidth();
634 int cy
= textRect
.cy
;
636 ::DrawState(hdc
, NULL
, NULL
, (LPARAM
)text
.wx_str(),
637 text
.length(), x
, y
, cx
, cy
, flags
);
639 // ::SetTextAlign(hdc, TA_RIGHT) doesn't work with DSS_DISABLED or DSS_MONO
640 // as the last parameter in DrawState() (at least with Windows98). So we have
641 // to take care of right alignment ourselves.
642 wxString accel
= GetItemLabel().AfterFirst(wxT('\t'));
643 if ( !accel
.empty() )
646 ::GetTextExtentPoint32(hdc
, accel
.c_str(), accel
.length(), &accelRect
);
648 int flags
= DST_TEXT
;
649 if ( (stat
& wxODDisabled
) && !(stat
& wxODSelected
) )
650 flags
|= DSS_DISABLED
;
652 // right align accel string with right edge of menu
653 // (offset by the margin width)
655 int x
= rc
.GetWidth() - 16 - accelRect
.cx
;
656 int y
= rc
.y
+ (rc
.GetHeight() - accelRect
.cy
) / 2;
657 ::DrawState(hdc
, NULL
, NULL
, (LPARAM
)accel
.wx_str(),
658 accel
.length(), x
, y
, 0, 0, flags
);
661 ::SetBkMode(hdc
, prevMode
);
667 if ( IsCheckable() && !m_bmpChecked
.Ok() )
669 if ( stat
& wxODChecked
)
671 // what goes on: DrawFrameControl creates a b/w mask,
672 // then we copy it to screen to have right colors
674 // first create a monochrome bitmap in a memory DC
675 HDC hdcMem
= CreateCompatibleDC(hdc
);
676 HBITMAP hbmpCheck
= CreateBitmap(margin
, rc
.GetHeight(), 1, 1, 0);
677 SelectObject(hdcMem
, hbmpCheck
);
679 // then draw a check mark into it
680 RECT rect
= { 0, 0, margin
, rc
.GetHeight() };
681 if ( rc
.GetHeight() > 0 )
683 ::DrawFrameControl(hdcMem
, &rect
, DFC_MENU
, DFCS_MENUCHECK
);
686 // finally copy it to screen DC and clean up
687 BitBlt(hdc
, rc
.x
, rc
.y
, margin
, rc
.GetHeight(), hdcMem
, 0, 0, SRCCOPY
);
690 DeleteObject(hbmpCheck
);
697 if ( stat
& wxODDisabled
)
699 bmp
= GetDisabledBitmap();
704 // for not checkable bitmaps we should always use unchecked one
705 // because their checked bitmap is not set
706 bmp
= GetBitmap(!IsCheckable() || (stat
& wxODChecked
));
709 if ( bmp
.Ok() && stat
& wxODDisabled
)
711 // we need to grey out the bitmap as we don't have any specific
713 wxImage imgGrey
= bmp
.ConvertToImage().ConvertToGreyscale();
715 bmp
= wxBitmap(imgGrey
);
717 #endif // wxUSE_IMAGE
722 wxMemoryDC
dcMem(&dc
);
723 dcMem
.SelectObjectAsSource(bmp
);
726 int nBmpWidth
= bmp
.GetWidth(),
727 nBmpHeight
= bmp
.GetHeight();
729 // there should be enough space!
730 wxASSERT((nBmpWidth
<= rc
.GetWidth()) && (nBmpHeight
<= rc
.GetHeight()));
732 int heightDiff
= rc
.GetHeight() - nBmpHeight
;
733 dc
.Blit(rc
.x
+ (margin
- nBmpWidth
) / 2,
734 rc
.y
+ heightDiff
/ 2,
735 nBmpWidth
, nBmpHeight
,
736 &dcMem
, 0, 0, wxCOPY
, true /* use mask */);
738 if ( ( stat
& wxODSelected
) && !( stat
& wxODDisabled
) && draw_bitmap_edge
)
740 RECT rectBmp
= { rc
.GetLeft(), rc
.GetTop(),
741 rc
.GetLeft() + margin
,
742 rc
.GetTop() + rc
.GetHeight() };
743 SetBkColor(hdc
, colBack
);
745 DrawEdge(hdc
, &rectBmp
, BDR_RAISEDINNER
, BF_RECT
);
750 ::SetTextColor(hdc
, colOldText
);
751 ::SetBkColor(hdc
, colOldBack
);
757 void wxMenuItem::GetFontToUse(wxFont
& font
) const
761 font
= ms_systemMenuFont
;
764 #endif // wxUSE_OWNER_DRAWN
766 // ----------------------------------------------------------------------------
768 // ----------------------------------------------------------------------------
770 wxMenuItem
*wxMenuItemBase::New(wxMenu
*parentMenu
,
772 const wxString
& name
,
773 const wxString
& help
,
777 return new wxMenuItem(parentMenu
, id
, name
, help
, kind
, subMenu
);
780 #endif // wxUSE_MENUS