allow calling SetItemLabel() for menu items with NULL menu parent too
[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 #if WXWIN_COMPATIBILITY_2_8
141 wxMenuItem::wxMenuItem(wxMenu *parentMenu,
142 int id,
143 const wxString& text,
144 const wxString& help,
145 bool isCheckable,
146 wxMenu *subMenu)
147 : wxMenuItemBase(parentMenu, id, text, help,
148 isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
149 #if wxUSE_OWNER_DRAWN
150 , wxOwnerDrawn(text, isCheckable, true)
151 #endif // owner drawn
152 {
153 Init();
154 }
155 #endif
156
157 void wxMenuItem::Init()
158 {
159 m_radioGroup.start = -1;
160 m_isRadioGroupStart = false;
161
162 #if wxUSE_OWNER_DRAWN
163
164 // when the color is not valid, wxOwnerDraw takes the default ones.
165 // If we set the colors here and they are changed by the user during
166 // the execution, then the colors are not updated until the application
167 // is restarted and our menus look bad
168 SetTextColour(wxNullColour);
169 SetBackgroundColour(wxNullColour);
170
171 // setting default colors switched ownerdraw on: switch it off again
172 ResetOwnerDrawn();
173
174 // switch ownerdraw back on if using a non default margin
175 if ( !IsSeparator() )
176 SetMarginWidth(GetMarginWidth());
177
178 // tell the owner drawing code to show the accel string as well
179 SetAccelString(m_text.AfterFirst(_T('\t')));
180 #endif // wxUSE_OWNER_DRAWN
181 }
182
183 wxMenuItem::~wxMenuItem()
184 {
185 }
186
187 // misc
188 // ----
189
190 // return the id for calling Win32 API functions
191 WXWPARAM wxMenuItem::GetMSWId() const
192 {
193 // we must use ids in unsigned short range with Windows functions, if we
194 // pass ids > USHRT_MAX to them they get very confused (e.g. start
195 // generating WM_COMMAND messages with negative high word of wParam), so
196 // use the cast to ensure the id is in range
197 return m_subMenu ? wxPtrToUInt(m_subMenu->GetHMenu())
198 : static_cast<unsigned short>(GetId());
199 }
200
201 // get item state
202 // --------------
203
204 bool wxMenuItem::IsChecked() const
205 {
206 // fix that RTTI is always getting the correct state (separators cannot be checked, but the call below
207 // returns true
208 if ( IsSeparator() )
209 return false ;
210
211 int flag = ::GetMenuState(GetHMenuOf(m_parentMenu), GetMSWId(), MF_BYCOMMAND);
212
213 return (flag & MF_CHECKED) != 0;
214 }
215
216 // radio group stuff
217 // -----------------
218
219 void wxMenuItem::SetAsRadioGroupStart()
220 {
221 m_isRadioGroupStart = true;
222 }
223
224 void wxMenuItem::SetRadioGroupStart(int start)
225 {
226 wxASSERT_MSG( !m_isRadioGroupStart,
227 _T("should only be called for the next radio items") );
228
229 m_radioGroup.start = start;
230 }
231
232 void wxMenuItem::SetRadioGroupEnd(int end)
233 {
234 wxASSERT_MSG( m_isRadioGroupStart,
235 _T("should only be called for the first radio item") );
236
237 m_radioGroup.end = end;
238 }
239
240 // change item state
241 // -----------------
242
243 void wxMenuItem::Enable(bool enable)
244 {
245 if ( m_isEnabled == enable )
246 return;
247
248 long rc = EnableMenuItem(GetHMenuOf(m_parentMenu),
249 GetMSWId(),
250 MF_BYCOMMAND |
251 (enable ? MF_ENABLED : MF_GRAYED));
252
253 if ( rc == -1 ) {
254 wxLogLastError(wxT("EnableMenuItem"));
255 }
256
257 wxMenuItemBase::Enable(enable);
258 }
259
260 void wxMenuItem::Check(bool check)
261 {
262 wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
263
264 if ( m_isChecked == check )
265 return;
266
267 int flags = check ? MF_CHECKED : MF_UNCHECKED;
268 HMENU hmenu = GetHMenuOf(m_parentMenu);
269
270 if ( GetKind() == wxITEM_RADIO )
271 {
272 // it doesn't make sense to uncheck a radio item - what would this do?
273 if ( !check )
274 return;
275
276 // get the index of this item in the menu
277 const wxMenuItemList& items = m_parentMenu->GetMenuItems();
278 int pos = items.IndexOf(this);
279 wxCHECK_RET( pos != wxNOT_FOUND,
280 _T("menuitem not found in the menu items list?") );
281
282 // get the radio group range
283 int start,
284 end;
285
286 if ( m_isRadioGroupStart )
287 {
288 // we already have all information we need
289 start = pos;
290 end = m_radioGroup.end;
291 }
292 else // next radio group item
293 {
294 // get the radio group end from the start item
295 start = m_radioGroup.start;
296 end = items.Item(start)->GetData()->m_radioGroup.end;
297 }
298
299 #ifdef __WIN32__
300 // calling CheckMenuRadioItem() with such parameters hangs my system
301 // (NT4 SP6) and I suspect this could happen to the others as well - so
302 // don't do it!
303 wxCHECK_RET( start != -1 && end != -1,
304 _T("invalid ::CheckMenuRadioItem() parameter(s)") );
305
306 if ( !::CheckMenuRadioItem(hmenu,
307 start, // the first radio group item
308 end, // the last one
309 pos, // the one to check
310 MF_BYPOSITION) )
311 {
312 wxLogLastError(_T("CheckMenuRadioItem"));
313 }
314 #endif // __WIN32__
315
316 // also uncheck all the other items in this radio group
317 wxMenuItemList::compatibility_iterator node = items.Item(start);
318 for ( int n = start; n <= end && node; n++ )
319 {
320 if ( n != pos )
321 {
322 node->GetData()->m_isChecked = false;
323 }
324
325 node = node->GetNext();
326 }
327 }
328 else // check item
329 {
330 if ( ::CheckMenuItem(hmenu,
331 GetMSWId(),
332 MF_BYCOMMAND | flags) == (DWORD)-1 )
333 {
334 wxFAIL_MSG( _T("CheckMenuItem() failed, item not in the menu?") );
335 }
336 }
337
338 wxMenuItemBase::Check(check);
339 }
340
341 void wxMenuItem::SetItemLabel(const wxString& txt)
342 {
343 wxString text = txt;
344
345 // don't do anything if label didn't change
346 if ( m_text == txt )
347 return;
348
349 // wxMenuItemBase will do stock ID checks
350 wxMenuItemBase::SetItemLabel(text);
351
352 // m_text could now be different from 'text' if we are a stock menu item,
353 // so use only m_text below
354
355 OWNER_DRAWN_ONLY( wxOwnerDrawn::SetName(m_text) );
356 #if wxUSE_OWNER_DRAWN
357 // tell the owner drawing code to to show the accel string as well
358 SetAccelString(m_text.AfterFirst(_T('\t')));
359 #endif
360
361 #if wxUSE_ACCEL
362 if ( m_parentMenu )
363 m_parentMenu->UpdateAccel(this);
364 #endif // wxUSE_ACCEL
365
366 // the item can be not attached to any menu yet and SetItemLabel() is still
367 // valid to call in this case and should do nothing else
368 if ( !m_parentMenu )
369 return;
370
371 const UINT id = GetMSWId();
372 HMENU hMenu = GetHMenuOf(m_parentMenu);
373 if ( !hMenu || ::GetMenuState(hMenu, id, MF_BYCOMMAND) == (UINT)-1 )
374 return;
375
376 #if wxUSE_OWNER_DRAWN
377 if ( IsOwnerDrawn() )
378 {
379 // we don't need to do anything for owner drawn items, they will redraw
380 // themselves using the new text the next time they're displayed
381 return;
382 }
383 #endif // owner drawn
384
385 // update the text of the native menu item
386 WinStruct<MENUITEMINFO> info;
387
388 // surprisingly, calling SetMenuItemInfo() with just MIIM_STRING doesn't
389 // work as it resets the menu bitmap, so we need to first get the old item
390 // state and then modify it
391 const bool isLaterThanWin95 = wxGetWinVersion() > wxWinVersion_95;
392 info.fMask = MIIM_STATE |
393 MIIM_ID |
394 MIIM_SUBMENU |
395 MIIM_CHECKMARKS |
396 MIIM_DATA;
397 if ( isLaterThanWin95 )
398 info.fMask |= MIIM_BITMAP | MIIM_FTYPE;
399 else
400 info.fMask |= MIIM_TYPE;
401 if ( !::GetMenuItemInfo(hMenu, id, FALSE, &info) )
402 {
403 wxLogLastError(wxT("GetMenuItemInfo"));
404 return;
405 }
406
407 if ( isLaterThanWin95 )
408 info.fMask |= MIIM_STRING;
409 //else: MIIM_TYPE already specified
410 info.dwTypeData = (LPTSTR)m_text.wx_str();
411 info.cch = m_text.length();
412 if ( !::SetMenuItemInfo(hMenu, id, FALSE, &info) )
413 {
414 wxLogLastError(wxT("SetMenuItemInfo"));
415 }
416 }
417
418 void wxMenuItem::SetCheckable(bool checkable)
419 {
420 wxMenuItemBase::SetCheckable(checkable);
421 OWNER_DRAWN_ONLY( wxOwnerDrawn::SetCheckable(checkable) );
422 }
423
424 // ----------------------------------------------------------------------------
425 // wxMenuItemBase
426 // ----------------------------------------------------------------------------
427
428 wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
429 int id,
430 const wxString& name,
431 const wxString& help,
432 wxItemKind kind,
433 wxMenu *subMenu)
434 {
435 return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
436 }
437
438 #endif // wxUSE_MENUS