Commited FRM's stockitem patch (empty stock items).
[wxWidgets.git] / src / msw / menuitem.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/menuitem.cpp
3 // Purpose: wxMenuItem implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.11.97
7 // RCS-ID: $Id$
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
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 )
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
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
155 void wxMenuItem::Init()
156 {
157 if (m_text.IsEmpty())
158 {
159 wxASSERT_MSG(wxIsStockId(GetId()), wxT("A non-stock menu item with an empty label?"));
160 m_text = wxGetStockLabel(GetId(), wxSTOCK_WITH_ACCELERATOR|wxSTOCK_WITH_MNEMONIC);
161 }
162
163 m_radioGroup.start = -1;
164 m_isRadioGroupStart = false;
165
166 #if wxUSE_OWNER_DRAWN
167
168 // when the color is not valid, wxOwnerDraw takes the default ones.
169 // If we set the colors here and they are changed by the user during
170 // the execution, then the colors are not updated until the application
171 // is restarted and our menus look bad
172 SetTextColour(wxNullColour);
173 SetBackgroundColour(wxNullColour);
174
175 // setting default colors switched ownerdraw on: switch it off again
176 ResetOwnerDrawn();
177
178 // switch ownerdraw back on if using a non default margin
179 if ( GetId() != wxID_SEPARATOR )
180 SetMarginWidth(GetMarginWidth());
181
182 // tell the owner drawing code to show the accel string as well
183 SetAccelString(m_text.AfterFirst(_T('\t')));
184 #endif // wxUSE_OWNER_DRAWN
185 }
186
187 wxMenuItem::~wxMenuItem()
188 {
189 }
190
191 // misc
192 // ----
193
194 // return the id for calling Win32 API functions
195 int wxMenuItem::GetRealId() const
196 {
197 return m_subMenu ? (int)m_subMenu->GetHMenu() : GetId();
198 }
199
200 // get item state
201 // --------------
202
203 bool wxMenuItem::IsChecked() const
204 {
205 // fix that RTTI is always getting the correct state (separators cannot be checked, but the call below
206 // returns true
207 if ( GetId() == wxID_SEPARATOR )
208 return false ;
209
210 int flag = ::GetMenuState(GetHMenuOf(m_parentMenu), GetId(), MF_BYCOMMAND);
211
212 return (flag & MF_CHECKED) != 0;
213 }
214
215 /* static */
216 wxString wxMenuItemBase::GetLabelFromText(const wxString& text)
217 {
218 return wxStripMenuCodes(text);
219 }
220
221 // radio group stuff
222 // -----------------
223
224 void wxMenuItem::SetAsRadioGroupStart()
225 {
226 m_isRadioGroupStart = true;
227 }
228
229 void wxMenuItem::SetRadioGroupStart(int start)
230 {
231 wxASSERT_MSG( !m_isRadioGroupStart,
232 _T("should only be called for the next radio items") );
233
234 m_radioGroup.start = start;
235 }
236
237 void wxMenuItem::SetRadioGroupEnd(int end)
238 {
239 wxASSERT_MSG( m_isRadioGroupStart,
240 _T("should only be called for the first radio item") );
241
242 m_radioGroup.end = end;
243 }
244
245 // change item state
246 // -----------------
247
248 void wxMenuItem::Enable(bool enable)
249 {
250 if ( m_isEnabled == enable )
251 return;
252
253 long rc = EnableMenuItem(GetHMenuOf(m_parentMenu),
254 GetRealId(),
255 MF_BYCOMMAND |
256 (enable ? MF_ENABLED : MF_GRAYED));
257
258 if ( rc == -1 ) {
259 wxLogLastError(wxT("EnableMenuItem"));
260 }
261
262 wxMenuItemBase::Enable(enable);
263 }
264
265 void wxMenuItem::Check(bool check)
266 {
267 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
268
269 if ( m_isChecked == check )
270 return;
271
272 int flags = check ? MF_CHECKED : MF_UNCHECKED;
273 HMENU hmenu = GetHMenuOf(m_parentMenu);
274
275 if ( GetKind() == wxITEM_RADIO )
276 {
277 // it doesn't make sense to uncheck a radio item - what would this do?
278 if ( !check )
279 return;
280
281 // get the index of this item in the menu
282 const wxMenuItemList& items = m_parentMenu->GetMenuItems();
283 int pos = items.IndexOf(this);
284 wxCHECK_RET( pos != wxNOT_FOUND,
285 _T("menuitem not found in the menu items list?") );
286
287 // get the radio group range
288 int start,
289 end;
290
291 if ( m_isRadioGroupStart )
292 {
293 // we already have all information we need
294 start = pos;
295 end = m_radioGroup.end;
296 }
297 else // next radio group item
298 {
299 // get the radio group end from the start item
300 start = m_radioGroup.start;
301 end = items.Item(start)->GetData()->m_radioGroup.end;
302 }
303
304 #ifdef __WIN32__
305 // calling CheckMenuRadioItem() with such parameters hangs my system
306 // (NT4 SP6) and I suspect this could happen to the others as well - so
307 // don't do it!
308 wxCHECK_RET( start != -1 && end != -1,
309 _T("invalid ::CheckMenuRadioItem() parameter(s)") );
310
311 if ( !::CheckMenuRadioItem(hmenu,
312 start, // the first radio group item
313 end, // the last one
314 pos, // the one to check
315 MF_BYPOSITION) )
316 {
317 wxLogLastError(_T("CheckMenuRadioItem"));
318 }
319 #endif // __WIN32__
320
321 // also uncheck all the other items in this radio group
322 wxMenuItemList::compatibility_iterator node = items.Item(start);
323 for ( int n = start; n <= end && node; n++ )
324 {
325 if ( n != pos )
326 {
327 node->GetData()->m_isChecked = false;
328 }
329
330 node = node->GetNext();
331 }
332 }
333 else // check item
334 {
335 if ( ::CheckMenuItem(hmenu,
336 GetRealId(),
337 MF_BYCOMMAND | flags) == (DWORD)-1 )
338 {
339 wxFAIL_MSG( _T("CheckMenuItem() failed, item not in the menu?") );
340 }
341 }
342
343 wxMenuItemBase::Check(check);
344 }
345
346 void wxMenuItem::SetText(const wxString& txt)
347 {
348 wxString text = txt;
349
350 // don't do anything if label didn't change
351 if ( m_text == txt )
352 return;
353
354 if (text.IsEmpty())
355 {
356 wxASSERT_MSG(wxIsStockId(GetId()), wxT("A non-stock menu item with an empty label?"));
357 text = wxGetStockLabel(GetId(), wxSTOCK_WITH_ACCELERATOR|wxSTOCK_WITH_MNEMONIC);
358 }
359
360 wxMenuItemBase::SetText(text);
361 OWNER_DRAWN_ONLY( wxOwnerDrawn::SetName(text) );
362 #if wxUSE_OWNER_DRAWN
363 // tell the owner drawing code to to show the accel string as well
364 SetAccelString(text.AfterFirst(_T('\t')));
365 #endif
366
367 HMENU hMenu = GetHMenuOf(m_parentMenu);
368 wxCHECK_RET( hMenu, wxT("menuitem without menu") );
369
370 #if wxUSE_ACCEL
371 m_parentMenu->UpdateAccel(this);
372 #endif // wxUSE_ACCEL
373
374 UINT id = GetRealId();
375 UINT flagsOld = ::GetMenuState(hMenu, id, MF_BYCOMMAND);
376 if ( flagsOld == 0xFFFFFFFF )
377 {
378 // It's not an error, it means that the menu item doesn't exist
379 //wxLogLastError(wxT("GetMenuState"));
380 }
381 else
382 {
383 if ( IsSubMenu() )
384 {
385 // high byte contains the number of items in a submenu for submenus
386 flagsOld &= 0xFF;
387 flagsOld |= MF_POPUP;
388 }
389
390 LPCTSTR data;
391
392 #if wxUSE_OWNER_DRAWN
393 if ( IsOwnerDrawn() )
394 {
395 flagsOld |= MF_OWNERDRAW;
396 data = (LPCTSTR)this;
397 }
398 else
399 #endif //owner drawn
400 {
401 flagsOld |= MF_STRING;
402 data = (wxChar*) text.c_str();
403 }
404
405 #ifdef __WXWINCE__
406 // FIXME: complete this, applying the old
407 // flags.
408 // However, the WinCE doc for SetMenuItemInfo
409 // says that you can't use it to set the menu
410 // item state; only data, id and type.
411 MENUITEMINFO info;
412 wxZeroMemory(info);
413 info.cbSize = sizeof(info);
414 info.fMask = MIIM_TYPE;
415 info.fType = MFT_STRING;
416 info.cch = text.length();
417 info.dwTypeData = (LPTSTR) data ;
418 if ( !::SetMenuItemInfo(hMenu, id, FALSE, & info) )
419 {
420 wxLogLastError(wxT("SetMenuItemInfo"));
421 }
422 #else
423 if ( ::ModifyMenu(hMenu, id,
424 MF_BYCOMMAND | flagsOld,
425 id, data) == (int)0xFFFFFFFF )
426 {
427 wxLogLastError(wxT("ModifyMenu"));
428 }
429 #endif
430 }
431 }
432
433 void wxMenuItem::SetCheckable(bool checkable)
434 {
435 wxMenuItemBase::SetCheckable(checkable);
436 OWNER_DRAWN_ONLY( wxOwnerDrawn::SetCheckable(checkable) );
437 }
438
439 // ----------------------------------------------------------------------------
440 // wxMenuItemBase
441 // ----------------------------------------------------------------------------
442
443 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
444 int id,
445 const wxString& name,
446 const wxString& help,
447 wxItemKind kind,
448 wxMenu *subMenu)
449 {
450 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
451 }
452
453 #endif // wxUSE_MENUS