1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/menuitem.cpp
3 // Purpose:     wxMenuItem implementation
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     11.11.97
7 // RCS-ID:      $Id: menuitem.cpp 41021 2006-09-05 21:00:55Z VZ $
8 // Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 // ===========================================================================
13 // declarations
14 // ===========================================================================
15 
16 // ---------------------------------------------------------------------------
17 // headers
18 // ---------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 #ifdef __BORLANDC__
24     #pragma hdrstop
25 #endif
26 
27 #if wxUSE_MENUS
28 
29 #include "wx/menuitem.h"
30 #include "wx/stockitem.h"
31 
32 #ifndef WX_PRECOMP
33     #include "wx/font.h"
34     #include "wx/bitmap.h"
35     #include "wx/settings.h"
36     #include "wx/window.h"
37     #include "wx/accel.h"
38     #include "wx/string.h"
39     #include "wx/log.h"
40     #include "wx/menu.h"
41 #endif
42 
43 #if wxUSE_ACCEL
44     #include "wx/accel.h"
45 #endif // wxUSE_ACCEL
46 
47 #include "wx/msw/private.h"
48 
49 #ifdef __WXWINCE__
50 // Implemented in menu.cpp
51 UINT GetMenuState(HMENU hMenu, UINT id, UINT flags) ;
52 #endif
53 
54 // ---------------------------------------------------------------------------
55 // macro
56 // ---------------------------------------------------------------------------
57 
58 // hide the ugly cast
59 #define GetHMenuOf(menu)    ((HMENU)menu->GetHMenu())
60 
61 // conditional compilation
62 #if wxUSE_OWNER_DRAWN
63     #define OWNER_DRAWN_ONLY( code ) if ( IsOwnerDrawn() ) code
64 #else // !wxUSE_OWNER_DRAWN
65     #define OWNER_DRAWN_ONLY( code )
66 #endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
67 
68 // ============================================================================
69 // implementation
70 // ============================================================================
71 
72 // ----------------------------------------------------------------------------
73 // dynamic classes implementation
74 // ----------------------------------------------------------------------------
75 
76 #if wxUSE_EXTENDED_RTTI
77 
wxMenuItemStreamingCallback(const wxObject * object,wxWriter *,wxPersister *,wxxVariantArray &)78 bool wxMenuItemStreamingCallback( const wxObject *object, wxWriter * , wxPersister * , wxxVariantArray & )
79 {
80     const wxMenuItem * mitem = dynamic_cast<const wxMenuItem*>(object) ;
81     if ( mitem->GetMenu() && !mitem->GetMenu()->GetTitle().empty() )
82     {
83         // we don't stream out the first two items for menus with a title, they will be reconstructed
84         if ( mitem->GetMenu()->FindItemByPosition(0) == mitem || mitem->GetMenu()->FindItemByPosition(1) == mitem )
85             return false ;
86     }
87     return true ;
88 }
89 
90 wxBEGIN_ENUM( wxItemKind )
wxENUM_MEMBER(wxITEM_SEPARATOR)91     wxENUM_MEMBER( wxITEM_SEPARATOR )
92     wxENUM_MEMBER( wxITEM_NORMAL )
93     wxENUM_MEMBER( wxITEM_CHECK )
94     wxENUM_MEMBER( wxITEM_RADIO )
95 wxEND_ENUM( wxItemKind )
96 
97 IMPLEMENT_DYNAMIC_CLASS_XTI_CALLBACK(wxMenuItem, wxObject,"wx/menuitem.h",wxMenuItemStreamingCallback)
98 
99 wxBEGIN_PROPERTIES_TABLE(wxMenuItem)
100     wxPROPERTY( Parent,wxMenu*, SetMenu, GetMenu, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
101     wxPROPERTY( Id,int, SetId, GetId, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
102     wxPROPERTY( Text, wxString , SetText, GetText, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
103     wxPROPERTY( Help, wxString , SetHelp, GetHelp, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
104     wxREADONLY_PROPERTY( Kind, wxItemKind , GetKind , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
105     wxPROPERTY( SubMenu,wxMenu*, SetSubMenu, GetSubMenu, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
106     wxPROPERTY( Enabled , bool , Enable , IsEnabled , wxxVariant((bool)true) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
107     wxPROPERTY( Checked , bool , Check , IsChecked , wxxVariant((bool)false) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
108     wxPROPERTY( Checkable , bool , SetCheckable , IsCheckable , wxxVariant((bool)false) , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
109 wxEND_PROPERTIES_TABLE()
110 
111 wxBEGIN_HANDLERS_TABLE(wxMenuItem)
112 wxEND_HANDLERS_TABLE()
113 
114 wxDIRECT_CONSTRUCTOR_6( wxMenuItem , wxMenu* , Parent , int , Id , wxString , Text , wxString , Help , wxItemKind , Kind , wxMenu* , SubMenu  )
115 #else
116 IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
117 #endif
118 
119 // ----------------------------------------------------------------------------
120 // wxMenuItem
121 // ----------------------------------------------------------------------------
122 
123 // ctor & dtor
124 // -----------
125 
126 wxMenuItem::wxMenuItem(wxMenu *pParentMenu,
127                        int id,
128                        const wxString& text,
129                        const wxString& strHelp,
130                        wxItemKind kind,
131                        wxMenu *pSubMenu)
132           : wxMenuItemBase(pParentMenu, id, text, strHelp, kind, pSubMenu)
133 #if wxUSE_OWNER_DRAWN
134             , wxOwnerDrawn(text, kind == wxITEM_CHECK, true)
135 #endif // owner drawn
136 {
137     Init();
138 }
139 
wxMenuItem(wxMenu * parentMenu,int id,const wxString & text,const wxString & help,bool isCheckable,wxMenu * subMenu)140 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
141                        int id,
142                        const wxString& text,
143                        const wxString& help,
144                        bool isCheckable,
145                        wxMenu *subMenu)
146           : wxMenuItemBase(parentMenu, id, text, help,
147                            isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
148 #if wxUSE_OWNER_DRAWN
149            , wxOwnerDrawn(text, isCheckable, true)
150 #endif // owner drawn
151 {
152     Init();
153 }
154 
Init()155 void wxMenuItem::Init()
156 {
157     m_radioGroup.start = -1;
158     m_isRadioGroupStart = false;
159 
160 #if  wxUSE_OWNER_DRAWN
161 
162     // when the color is not valid, wxOwnerDraw takes the default ones.
163     // If we set the colors here and they are changed by the user during
164     // the execution, then the colors are not updated until the application
165     // is restarted and our menus look bad
166     SetTextColour(wxNullColour);
167     SetBackgroundColour(wxNullColour);
168 
169     // setting default colors switched ownerdraw on: switch it off again
170     ResetOwnerDrawn();
171 
172     //  switch ownerdraw back on if using a non default margin
173     if ( GetId() != wxID_SEPARATOR )
174         SetMarginWidth(GetMarginWidth());
175 
176     // tell the owner drawing code to show the accel string as well
177     SetAccelString(m_text.AfterFirst(_T('\t')));
178 #endif // wxUSE_OWNER_DRAWN
179 }
180 
~wxMenuItem()181 wxMenuItem::~wxMenuItem()
182 {
183 }
184 
185 // misc
186 // ----
187 
188 // return the id for calling Win32 API functions
GetRealId() const189 int wxMenuItem::GetRealId() const
190 {
191     return m_subMenu ? (int)m_subMenu->GetHMenu() : GetId();
192 }
193 
194 // get item state
195 // --------------
196 
IsChecked() const197 bool wxMenuItem::IsChecked() const
198 {
199     // fix that RTTI is always getting the correct state (separators cannot be checked, but the call below
200     // returns true
201     if ( GetId() == wxID_SEPARATOR )
202         return false ;
203 
204     int flag = ::GetMenuState(GetHMenuOf(m_parentMenu), GetId(), MF_BYCOMMAND);
205 
206     return (flag & MF_CHECKED) != 0;
207 }
208 
209 /* static */
GetLabelFromText(const wxString & text)210 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
211 {
212     return wxStripMenuCodes(text);
213 }
214 
215 // radio group stuff
216 // -----------------
217 
SetAsRadioGroupStart()218 void wxMenuItem::SetAsRadioGroupStart()
219 {
220     m_isRadioGroupStart = true;
221 }
222 
SetRadioGroupStart(int start)223 void wxMenuItem::SetRadioGroupStart(int start)
224 {
225     wxASSERT_MSG( !m_isRadioGroupStart,
226                   _T("should only be called for the next radio items") );
227 
228     m_radioGroup.start = start;
229 }
230 
SetRadioGroupEnd(int end)231 void wxMenuItem::SetRadioGroupEnd(int end)
232 {
233     wxASSERT_MSG( m_isRadioGroupStart,
234                   _T("should only be called for the first radio item") );
235 
236     m_radioGroup.end = end;
237 }
238 
239 // change item state
240 // -----------------
241 
Enable(bool enable)242 void wxMenuItem::Enable(bool enable)
243 {
244     if ( m_isEnabled == enable )
245         return;
246 
247     long rc = EnableMenuItem(GetHMenuOf(m_parentMenu),
248                              GetRealId(),
249                              MF_BYCOMMAND |
250                              (enable ? MF_ENABLED : MF_GRAYED));
251 
252     if ( rc == -1 ) {
253         wxLogLastError(wxT("EnableMenuItem"));
254     }
255 
256     wxMenuItemBase::Enable(enable);
257 }
258 
Check(bool check)259 void wxMenuItem::Check(bool check)
260 {
261     wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
262 
263     if ( m_isChecked == check )
264         return;
265 
266     int flags = check ? MF_CHECKED : MF_UNCHECKED;
267     HMENU hmenu = GetHMenuOf(m_parentMenu);
268 
269     if ( GetKind() == wxITEM_RADIO )
270     {
271         // it doesn't make sense to uncheck a radio item - what would this do?
272         if ( !check )
273             return;
274 
275         // get the index of this item in the menu
276         const wxMenuItemList& items = m_parentMenu->GetMenuItems();
277         int pos = items.IndexOf(this);
278         wxCHECK_RET( pos != wxNOT_FOUND,
279                      _T("menuitem not found in the menu items list?") );
280 
281         // get the radio group range
282         int start,
283             end;
284 
285         if ( m_isRadioGroupStart )
286         {
287             // we already have all information we need
288             start = pos;
289             end = m_radioGroup.end;
290         }
291         else // next radio group item
292         {
293             // get the radio group end from the start item
294             start = m_radioGroup.start;
295             end = items.Item(start)->GetData()->m_radioGroup.end;
296         }
297 
298 #ifdef __WIN32__
299         // calling CheckMenuRadioItem() with such parameters hangs my system
300         // (NT4 SP6) and I suspect this could happen to the others as well - so
301         // don't do it!
302         wxCHECK_RET( start != -1 && end != -1,
303                      _T("invalid ::CheckMenuRadioItem() parameter(s)") );
304 
305         if ( !::CheckMenuRadioItem(hmenu,
306                                    start,   // the first radio group item
307                                    end,     // the last one
308                                    pos,     // the one to check
309                                    MF_BYPOSITION) )
310         {
311             wxLogLastError(_T("CheckMenuRadioItem"));
312         }
313 #endif // __WIN32__
314 
315         // also uncheck all the other items in this radio group
316         wxMenuItemList::compatibility_iterator node = items.Item(start);
317         for ( int n = start; n <= end && node; n++ )
318         {
319             if ( n != pos )
320             {
321                 node->GetData()->m_isChecked = false;
322             }
323 
324             node = node->GetNext();
325         }
326     }
327     else // check item
328     {
329         if ( ::CheckMenuItem(hmenu,
330                              GetRealId(),
331                              MF_BYCOMMAND | flags) == (DWORD)-1 )
332         {
333             wxFAIL_MSG( _T("CheckMenuItem() failed, item not in the menu?") );
334         }
335     }
336 
337     wxMenuItemBase::Check(check);
338 }
339 
SetText(const wxString & txt)340 void wxMenuItem::SetText(const wxString& txt)
341 {
342     wxString text = txt;
343 
344     // don't do anything if label didn't change
345     if ( m_text == txt )
346         return;
347 
348     // wxMenuItemBase will do stock ID checks
349     wxMenuItemBase::SetText(text);
350 
351     // m_text could now be different from 'text' if we are a stock menu item,
352     // so use only m_text below
353 
354     OWNER_DRAWN_ONLY( wxOwnerDrawn::SetName(m_text) );
355 #if wxUSE_OWNER_DRAWN
356     // tell the owner drawing code to to show the accel string as well
357     SetAccelString(m_text.AfterFirst(_T('\t')));
358 #endif
359 
360     HMENU hMenu = GetHMenuOf(m_parentMenu);
361     wxCHECK_RET( hMenu, wxT("menuitem without menu") );
362 
363 #if wxUSE_ACCEL
364     m_parentMenu->UpdateAccel(this);
365 #endif // wxUSE_ACCEL
366 
367     UINT id = GetRealId();
368     UINT flagsOld = ::GetMenuState(hMenu, id, MF_BYCOMMAND);
369     if ( flagsOld == 0xFFFFFFFF )
370     {
371         // It's not an error, it means that the menu item doesn't exist
372         //wxLogLastError(wxT("GetMenuState"));
373     }
374     else
375     {
376         if ( IsSubMenu() )
377         {
378             // high byte contains the number of items in a submenu for submenus
379             flagsOld &= 0xFF;
380             flagsOld |= MF_POPUP;
381         }
382 
383         LPCTSTR data;
384 
385 #if wxUSE_OWNER_DRAWN
386         if ( IsOwnerDrawn() )
387         {
388             flagsOld |= MF_OWNERDRAW;
389             data = (LPCTSTR)this;
390         }
391         else
392 #endif  //owner drawn
393         {
394             flagsOld |= MF_STRING;
395             data = (wxChar*) m_text.c_str();
396         }
397 
398 #ifdef __WXWINCE__
399         // FIXME: complete this, applying the old
400         // flags.
401         // However, the WinCE doc for SetMenuItemInfo
402         // says that you can't use it to set the menu
403         // item state; only data, id and type.
404         MENUITEMINFO info;
405         wxZeroMemory(info);
406         info.cbSize = sizeof(info);
407         info.fMask = MIIM_TYPE;
408         info.fType = MFT_STRING;
409         info.cch = m_text.length();
410         info.dwTypeData = (LPTSTR) data ;
411         if ( !::SetMenuItemInfo(hMenu, id, FALSE, & info) )
412         {
413             wxLogLastError(wxT("SetMenuItemInfo"));
414         }
415 #else
416         if ( ::ModifyMenu(hMenu, id,
417                           MF_BYCOMMAND | flagsOld,
418                           id, data) == (int)0xFFFFFFFF )
419         {
420             wxLogLastError(wxT("ModifyMenu"));
421         }
422 #endif
423     }
424 }
425 
SetCheckable(bool checkable)426 void wxMenuItem::SetCheckable(bool checkable)
427 {
428     wxMenuItemBase::SetCheckable(checkable);
429     OWNER_DRAWN_ONLY( wxOwnerDrawn::SetCheckable(checkable) );
430 }
431 
432 // ----------------------------------------------------------------------------
433 // wxMenuItemBase
434 // ----------------------------------------------------------------------------
435 
New(wxMenu * parentMenu,int id,const wxString & name,const wxString & help,wxItemKind kind,wxMenu * subMenu)436 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
437                                 int id,
438                                 const wxString& name,
439                                 const wxString& help,
440                                 wxItemKind kind,
441                                 wxMenu *subMenu)
442 {
443     return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
444 }
445 
446 #endif // wxUSE_MENUS
447