change not only the focus but also last remembered TLW focus in SetValue() to avoid...
[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 ::SendMessage(GetHwnd(), BM_SETCHECK,
161 value ? BST_CHECKED : BST_UNCHECKED, 0);
162
163 m_isChecked = value;
164
165 if ( !value )
166 return;
167
168 // if we set the value of one radio button we also must clear all the other
169 // buttons in the same group: Windows doesn't do it automatically
170 //
171 // moreover, if another radiobutton in the group currently has the focus,
172 // we have to set it to this radiobutton, else the old readiobutton will be
173 // reselected automatically, if a parent window loses the focus and regains
174 // it.
175 wxWindow * const focus = FindFocus();
176 wxTopLevelWindow * const
177 tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
178 wxCHECK_RET( tlw, _T("radio button outside of TLW?") );
179 wxWindow * const focusInTLW = tlw->GetLastFocus();
180
181 const wxWindowList& siblings = GetParent()->GetChildren();
182 wxWindowList::compatibility_iterator nodeThis = siblings.Find(this);
183 wxCHECK_RET( nodeThis, _T("radio button not a child of its parent?") );
184
185 // this will be set to true in the code below if the focus is in our TLW
186 // and belongs to one of the other buttons in the same group
187 bool shouldSetFocus = false;
188
189 // this will be set to true if the focus is outside of our TLW currently
190 // but the remembered focus of this TLW is one of the other buttons in the
191 // same group
192 bool shouldSetTLWFocus = false;
193
194 // if it's not the first item of the group ...
195 if ( !HasFlag(wxRB_GROUP) )
196 {
197 // ... turn off all radio buttons before it
198 for ( wxWindowList::compatibility_iterator nodeBefore = nodeThis->GetPrevious();
199 nodeBefore;
200 nodeBefore = nodeBefore->GetPrevious() )
201 {
202 wxRadioButton *btn = wxDynamicCast(nodeBefore->GetData(),
203 wxRadioButton);
204 if ( !btn )
205 {
206 // don't stop on non radio buttons, we could have intermixed
207 // buttons and e.g. static labels
208 continue;
209 }
210
211 if ( btn->HasFlag(wxRB_SINGLE) )
212 {
213 // A wxRB_SINGLE button isn't part of this group
214 break;
215 }
216
217 if ( btn == focus )
218 shouldSetFocus = true;
219 else if ( btn == focusInTLW )
220 shouldSetTLWFocus = true;
221
222 btn->SetValue(false);
223
224 if ( btn->HasFlag(wxRB_GROUP) )
225 {
226 // even if there are other radio buttons before this one,
227 // they're not in the same group with us
228 break;
229 }
230 }
231 }
232
233 // ... and also turn off all buttons after this one
234 for ( wxWindowList::compatibility_iterator nodeAfter = nodeThis->GetNext();
235 nodeAfter;
236 nodeAfter = nodeAfter->GetNext() )
237 {
238 wxRadioButton *btn = wxDynamicCast(nodeAfter->GetData(),
239 wxRadioButton);
240
241 if ( !btn )
242 continue;
243
244 if ( btn->HasFlag(wxRB_GROUP | wxRB_SINGLE) )
245 {
246 // no more buttons or the first button of the next group
247 break;
248 }
249
250 if ( btn == focus )
251 shouldSetFocus = true;
252 else if ( btn == focusInTLW )
253 shouldSetTLWFocus = true;
254
255 btn->SetValue(false);
256 }
257
258 if ( shouldSetFocus )
259 SetFocus();
260 else if ( shouldSetTLWFocus )
261 tlw->SetLastFocus(this);
262 }
263
264 bool wxRadioButton::GetValue() const
265 {
266 wxASSERT_MSG( m_isChecked ==
267 (::SendMessage(GetHwnd(), BM_GETCHECK, 0, 0L) != 0),
268 _T("wxRadioButton::m_isChecked is out of sync?") );
269
270 return m_isChecked;
271 }
272
273 // ----------------------------------------------------------------------------
274 // wxRadioButton event processing
275 // ----------------------------------------------------------------------------
276
277 void wxRadioButton::Command (wxCommandEvent& event)
278 {
279 SetValue(event.GetInt() != 0);
280 ProcessCommand(event);
281 }
282
283 bool wxRadioButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
284 {
285 if ( param != BN_CLICKED )
286 return false;
287
288 if ( !m_isChecked )
289 {
290 // we have to do this for BS_RADIOBUTTON anyhow and, strangely enough,
291 // sometimes this is needed even for BS_AUTORADIOBUTTON (when we
292 // receive focus the button gets BN_CLICKED but stays unchecked!)
293 SetValue(true);
294
295 wxCommandEvent event(wxEVT_COMMAND_RADIOBUTTON_SELECTED, GetId());
296 event.SetEventObject( this );
297 event.SetInt(true); // always checked
298
299 ProcessCommand(event);
300 }
301
302 return true;
303 }
304
305 // ----------------------------------------------------------------------------
306 // wxRadioButton geometry
307 // ----------------------------------------------------------------------------
308
309 wxSize wxRadioButton::DoGetBestSize() const
310 {
311 static int s_radioSize = 0;
312
313 if ( !s_radioSize )
314 {
315 wxScreenDC dc;
316 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
317
318 s_radioSize = dc.GetCharHeight();
319
320 // radio button bitmap size under CE is bigger than the font height,
321 // adding just one pixel seems to work fine for the default font but it
322 // would be nice to find some better way to find the correct height
323 #ifdef __WXWINCE__
324 s_radioSize++;
325 #endif // __WXWINCE__
326 }
327
328 wxString str = GetLabel();
329
330 int wRadio, hRadio;
331 if ( !str.empty() )
332 {
333 GetTextExtent(GetLabelText(str), &wRadio, &hRadio);
334 wRadio += s_radioSize + GetCharWidth();
335
336 if ( hRadio < s_radioSize )
337 hRadio = s_radioSize;
338 }
339 else
340 {
341 wRadio = s_radioSize;
342 hRadio = s_radioSize;
343 }
344
345 wxSize best(wRadio, hRadio);
346 CacheBestSize(best);
347 return best;
348 }
349
350 WXDWORD wxRadioButton::MSWGetStyle(long style, WXDWORD *exstyle) const
351 {
352 WXDWORD styleMSW = wxControl::MSWGetStyle(style, exstyle);
353
354 if ( style & wxRB_GROUP )
355 styleMSW |= WS_GROUP;
356
357 return styleMSW;
358 }
359
360 #endif // wxUSE_RADIOBTN