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