allow wxRB_GROUP to be changed after creation (patch 1544686)
[wxWidgets.git] / src / msw / radiobut.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/radiobut.cpp
3 // Purpose: wxRadioButton
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
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_RADIOBTN
28
29 #include "wx/radiobut.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/settings.h"
33 #include "wx/dcscreen.h"
34 #endif
35
36 #include "wx/msw/private.h"
37
38 // ============================================================================
39 // wxRadioButton implementation
40 // ============================================================================
41
42 // ----------------------------------------------------------------------------
43 // wxRadioButton creation
44 // ----------------------------------------------------------------------------
45
46
47 #if wxUSE_EXTENDED_RTTI
48 WX_DEFINE_FLAGS( wxRadioButtonStyle )
49
50 wxBEGIN_FLAGS( wxRadioButtonStyle )
51 // new style border flags, we put them first to
52 // use them for streaming out
53 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
54 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
55 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
56 wxFLAGS_MEMBER(wxBORDER_RAISED)
57 wxFLAGS_MEMBER(wxBORDER_STATIC)
58 wxFLAGS_MEMBER(wxBORDER_NONE)
59
60 // old style border flags
61 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
62 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
63 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
64 wxFLAGS_MEMBER(wxRAISED_BORDER)
65 wxFLAGS_MEMBER(wxSTATIC_BORDER)
66 wxFLAGS_MEMBER(wxBORDER)
67
68 // standard window styles
69 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
70 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
71 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
72 wxFLAGS_MEMBER(wxWANTS_CHARS)
73 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
74 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
75 wxFLAGS_MEMBER(wxVSCROLL)
76 wxFLAGS_MEMBER(wxHSCROLL)
77
78 wxFLAGS_MEMBER(wxRB_GROUP)
79
80 wxEND_FLAGS( wxRadioButtonStyle )
81
82 IMPLEMENT_DYNAMIC_CLASS_XTI(wxRadioButton, wxControl,"wx/radiobut.h")
83
84 wxBEGIN_PROPERTIES_TABLE(wxRadioButton)
85 wxEVENT_PROPERTY( Click , wxEVT_COMMAND_RADIOBUTTON_SELECTED , wxCommandEvent )
86 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
87 wxPROPERTY( Label,wxString, SetLabel, GetLabel, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
88 wxPROPERTY( Value ,bool, SetValue, GetValue, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
89 wxPROPERTY_FLAGS( WindowStyle , wxRadioButtonStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
90 wxEND_PROPERTIES_TABLE()
91
92 wxBEGIN_HANDLERS_TABLE(wxRadioButton)
93 wxEND_HANDLERS_TABLE()
94
95 wxCONSTRUCTOR_6( wxRadioButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
96
97 #else
98 IMPLEMENT_DYNAMIC_CLASS(wxRadioButton, wxControl)
99 #endif
100
101
102 void wxRadioButton::Init()
103 {
104 m_isChecked = false;
105 }
106
107 bool wxRadioButton::Create(wxWindow *parent,
108 wxWindowID id,
109 const wxString& label,
110 const wxPoint& pos,
111 const wxSize& size,
112 long style,
113 const wxValidator& validator,
114 const wxString& name)
115 {
116 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
117 return false;
118
119 long msStyle = WS_TABSTOP;
120 if ( HasFlag(wxRB_GROUP) )
121 msStyle |= WS_GROUP;
122
123 /*
124 wxRB_SINGLE is a temporary workaround for the following problem: if you
125 have 2 radiobuttons in the same group but which are not consecutive in
126 the dialog, Windows can enter an infinite loop! The simplest way to
127 reproduce it is to create radio button, then a panel and then another
128 radio button: then checking the last button hangs the app.
129
130 Ideally, we'd detect (and avoid) such situation automatically but for
131 now, as I don't know how to do it, just allow the user to create
132 BS_RADIOBUTTON buttons for such situations.
133 */
134 msStyle |= HasFlag(wxRB_SINGLE) ? BS_RADIOBUTTON : BS_AUTORADIOBUTTON;
135
136 if ( HasFlag(wxCLIP_SIBLINGS) )
137 msStyle |= WS_CLIPSIBLINGS;
138 if ( HasFlag(wxALIGN_RIGHT) )
139 msStyle |= BS_LEFTTEXT | BS_RIGHT;
140
141 if ( !MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, 0) )
142 return false;
143
144 // for compatibility with wxGTK, the first radio button in a group is
145 // always checked (this makes sense anyhow as you need to ensure that at
146 // least one button in the group is checked and this is the simlpest way to
147 // do it)
148 if ( HasFlag(wxRB_GROUP) )
149 SetValue(true);
150
151 return true;
152 }
153
154 // ----------------------------------------------------------------------------
155 // wxRadioButton functions
156 // ----------------------------------------------------------------------------
157
158 void wxRadioButton::SetValue(bool value)
159 {
160 (void)::SendMessage(GetHwnd(), BM_SETCHECK, (value?BST_CHECKED:BST_UNCHECKED), 0L);
161
162 m_isChecked = value;
163
164 // if we set the value of one radio button we also must clear all the other
165 // buttons in the same group: Windows doesn't do it automatically
166 if ( m_isChecked )
167 {
168 // If another radiobutton in the group currently has the focus, we have to
169 // set it to this radiobutton, else the old readiobutton will be reselected
170 // automatically, if a parent window loses the focus and regains it.
171 bool shouldSetFocus = false;
172 wxWindow* pFocusWnd = FindFocus();
173
174 const wxWindowList& siblings = GetParent()->GetChildren();
175 wxWindowList::compatibility_iterator nodeThis = siblings.Find(this);
176 wxCHECK_RET( nodeThis, _T("radio button not a child of its parent?") );
177
178 // if it's not the first item of the group ...
179 if ( !HasFlag(wxRB_GROUP) )
180 {
181 // ... turn off all radio buttons before it
182 for ( wxWindowList::compatibility_iterator nodeBefore = nodeThis->GetPrevious();
183 nodeBefore;
184 nodeBefore = nodeBefore->GetPrevious() )
185 {
186 wxRadioButton *btn = wxDynamicCast(nodeBefore->GetData(),
187 wxRadioButton);
188 if ( btn && btn->HasFlag(wxRB_SINGLE) )
189 {
190 // A wxRB_SINGLE button isn't part of this group
191 break;
192 }
193
194 if (btn)
195 {
196 if (btn == pFocusWnd)
197 shouldSetFocus = true;
198
199 btn->SetValue(false);
200
201 if ( btn->HasFlag(wxRB_GROUP) )
202 {
203 // even if there are other radio buttons before this one,
204 // they're not in the same group with us
205 break;
206 }
207 }
208 }
209 }
210
211 // ... and also turn off all buttons after this one
212 for ( wxWindowList::compatibility_iterator nodeAfter = nodeThis->GetNext();
213 nodeAfter;
214 nodeAfter = nodeAfter->GetNext() )
215 {
216 wxRadioButton *btn = wxDynamicCast(nodeAfter->GetData(),
217 wxRadioButton);
218
219 if ( btn && (btn->HasFlag(wxRB_GROUP) || btn->HasFlag(wxRB_SINGLE) ) )
220 {
221 // no more buttons or the first button of the next group
222 break;
223 }
224
225 if (btn)
226 {
227 if (btn == pFocusWnd)
228 shouldSetFocus = true;
229
230 btn->SetValue(false);
231 }
232 }
233 if (shouldSetFocus)
234 SetFocus();
235 }
236 }
237
238 bool wxRadioButton::GetValue() const
239 {
240 wxASSERT_MSG( m_isChecked ==
241 (::SendMessage(GetHwnd(), BM_GETCHECK, 0, 0L) != 0),
242 _T("wxRadioButton::m_isChecked is out of sync?") );
243
244 return m_isChecked;
245 }
246
247 // ----------------------------------------------------------------------------
248 // wxRadioButton event processing
249 // ----------------------------------------------------------------------------
250
251 void wxRadioButton::Command (wxCommandEvent& event)
252 {
253 SetValue(event.GetInt() != 0);
254 ProcessCommand(event);
255 }
256
257 bool wxRadioButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
258 {
259 if ( param != BN_CLICKED )
260 return false;
261
262 if ( !m_isChecked )
263 {
264 // we have to do this for BS_RADIOBUTTON anyhow and, strangely enough,
265 // sometimes this is needed even for BS_AUTORADIOBUTTON (when we
266 // receive focus the button gets BN_CLICKED but stays unchecked!)
267 SetValue(true);
268
269 wxCommandEvent event(wxEVT_COMMAND_RADIOBUTTON_SELECTED, GetId());
270 event.SetEventObject( this );
271 event.SetInt(true); // always checked
272
273 ProcessCommand(event);
274 }
275
276 return true;
277 }
278
279 // ----------------------------------------------------------------------------
280 // wxRadioButton geometry
281 // ----------------------------------------------------------------------------
282
283 wxSize wxRadioButton::DoGetBestSize() const
284 {
285 static int s_radioSize = 0;
286
287 if ( !s_radioSize )
288 {
289 wxScreenDC dc;
290 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
291
292 s_radioSize = dc.GetCharHeight();
293 }
294
295 wxString str = GetLabel();
296
297 int wRadio, hRadio;
298 if ( !str.empty() )
299 {
300 GetTextExtent(GetLabelText(str), &wRadio, &hRadio);
301 wRadio += s_radioSize + GetCharWidth();
302
303 if ( hRadio < s_radioSize )
304 hRadio = s_radioSize;
305 }
306 else
307 {
308 wRadio = s_radioSize;
309 hRadio = s_radioSize;
310 }
311
312 wxSize best(wRadio, hRadio);
313 CacheBestSize(best);
314 return best;
315 }
316
317 WXDWORD wxRadioButton::MSWGetStyle(long style, WXDWORD *exstyle) const
318 {
319 WXDWORD styleMSW = wxControl::MSWGetStyle(style, exstyle);
320
321 if ( style & wxRB_GROUP )
322 styleMSW |= WS_GROUP;
323
324 return styleMSW;
325 }
326
327 #endif // wxUSE_RADIOBTN