1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/radiobox.cpp
3 // Purpose:     wxRadioBox implementation
4 // Author:      Julian Smart
5 // Modified by:
6 // Created:     04/01/98
7 // Copyright:   (c) Julian Smart
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // ===========================================================================
12 // declarations
13 // ===========================================================================
14 
15 // ---------------------------------------------------------------------------
16 // headers
17 // ---------------------------------------------------------------------------
18 
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21 
22 #ifdef __BORLANDC__
23     #pragma hdrstop
24 #endif
25 
26 #if wxUSE_RADIOBOX
27 
28 #include "wx/radiobox.h"
29 
30 #ifndef WX_PRECOMP
31     #include "wx/hashmap.h"
32     #include "wx/bitmap.h"
33     #include "wx/brush.h"
34     #include "wx/settings.h"
35     #include "wx/log.h"
36 #endif
37 
38 #include "wx/msw/subwin.h"
39 
40 #if wxUSE_TOOLTIPS
41     #include "wx/tooltip.h"
42 #endif // wxUSE_TOOLTIPS
43 
44 // TODO: wxCONSTRUCTOR
45 #if 0 // wxUSE_EXTENDED_RTTI
46 WX_DEFINE_FLAGS( wxRadioBoxStyle )
47 
48 wxBEGIN_FLAGS( wxRadioBoxStyle )
49     // new style border flags, we put them first to
50     // use them for streaming out
51     wxFLAGS_MEMBER(wxBORDER_SIMPLE)
52     wxFLAGS_MEMBER(wxBORDER_SUNKEN)
53     wxFLAGS_MEMBER(wxBORDER_DOUBLE)
54     wxFLAGS_MEMBER(wxBORDER_RAISED)
55     wxFLAGS_MEMBER(wxBORDER_STATIC)
56     wxFLAGS_MEMBER(wxBORDER_NONE)
57 
58     // old style border flags
59     wxFLAGS_MEMBER(wxSIMPLE_BORDER)
60     wxFLAGS_MEMBER(wxSUNKEN_BORDER)
61     wxFLAGS_MEMBER(wxDOUBLE_BORDER)
62     wxFLAGS_MEMBER(wxRAISED_BORDER)
63     wxFLAGS_MEMBER(wxSTATIC_BORDER)
64     wxFLAGS_MEMBER(wxBORDER)
65 
66     // standard window styles
67     wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
68     wxFLAGS_MEMBER(wxCLIP_CHILDREN)
69     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
70     wxFLAGS_MEMBER(wxWANTS_CHARS)
71     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
72     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
73     wxFLAGS_MEMBER(wxVSCROLL)
74     wxFLAGS_MEMBER(wxHSCROLL)
75 
76     wxFLAGS_MEMBER(wxRA_SPECIFY_COLS)
77     wxFLAGS_MEMBER(wxRA_SPECIFY_ROWS)
78 wxEND_FLAGS( wxRadioBoxStyle )
79 
80 IMPLEMENT_DYNAMIC_CLASS_XTI(wxRadioBox, wxControl,"wx/radiobox.h")
81 
82 wxBEGIN_PROPERTIES_TABLE(wxRadioBox)
83     wxEVENT_PROPERTY( Select , wxEVT_RADIOBOX , wxCommandEvent )
84     wxPROPERTY_FLAGS( WindowStyle , wxRadioBoxStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
85 wxEND_PROPERTIES_TABLE()
86 
87 #else
88 IMPLEMENT_DYNAMIC_CLASS(wxRadioBox, wxControl)
89 #endif
90 
91 /*
92     selection
93     content
94         label
95         dimension
96         item
97 */
98 
99 // ---------------------------------------------------------------------------
100 // private functions
101 // ---------------------------------------------------------------------------
102 
103 // wnd proc for radio buttons
104 LRESULT APIENTRY _EXPORT wxRadioBtnWndProc(HWND hWnd,
105                                            UINT message,
106                                            WPARAM wParam,
107                                            LPARAM lParam);
108 
109 // ---------------------------------------------------------------------------
110 // global vars
111 // ---------------------------------------------------------------------------
112 
113 namespace
114 {
115 
116 // the pointer to standard radio button wnd proc
117 WXFARPROC s_wndprocRadioBtn = (WXFARPROC)NULL;
118 
119 // Hash allowing to find wxRadioBox containing the given radio button by its
120 // HWND. This is used by (subclassed) radio button window proc to find the
121 // radio box it belongs to.
122 WX_DECLARE_HASH_MAP(HWND, wxRadioBox *,
123                     wxPointerHash, wxPointerEqual,
124                     RadioBoxFromButton);
125 
126 RadioBoxFromButton gs_boxFromButton;
127 
128 } // anonymous namespace
129 
130 // ===========================================================================
131 // implementation
132 // ===========================================================================
133 
134 /* static */
GetFromRadioButtonHWND(WXHWND hwnd)135 wxRadioBox* wxRadioBox::GetFromRadioButtonHWND(WXHWND hwnd)
136 {
137     const RadioBoxFromButton::const_iterator it = gs_boxFromButton.find(hwnd);
138     return it == gs_boxFromButton.end() ? NULL : it->second;
139 }
140 
141 // ---------------------------------------------------------------------------
142 // wxRadioBox creation
143 // ---------------------------------------------------------------------------
144 
145 // Radio box item
Init()146 void wxRadioBox::Init()
147 {
148     m_selectedButton = wxNOT_FOUND;
149     m_radioButtons = NULL;
150     m_dummyHwnd = NULL;
151     m_radioWidth = NULL;
152     m_radioHeight = NULL;
153 }
154 
Create(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],int majorDim,long style,const wxValidator & val,const wxString & name)155 bool wxRadioBox::Create(wxWindow *parent,
156                         wxWindowID id,
157                         const wxString& title,
158                         const wxPoint& pos,
159                         const wxSize& size,
160                         int n,
161                         const wxString choices[],
162                         int majorDim,
163                         long style,
164                         const wxValidator& val,
165                         const wxString& name)
166 {
167     // common initialization
168     if ( !wxStaticBox::Create(parent, id, title, pos, size, style, name) )
169         return false;
170 
171     // the code elsewhere in this file supposes that either wxRA_SPECIFY_COLS
172     // or wxRA_SPECIFY_ROWS is set, ensure that this is indeed the case
173     if ( !(style & (wxRA_SPECIFY_ROWS | wxRA_SPECIFY_COLS)) )
174         style |= wxRA_SPECIFY_COLS;
175 
176 #if wxUSE_VALIDATORS
177     SetValidator(val);
178 #else
179     wxUnusedVar(val);
180 #endif // wxUSE_VALIDATORS/!wxUSE_VALIDATORS
181 
182     // We need an extra one to keep track of the 'dummy' item we
183     // create to end the radio group, so it will be destroyed and
184     // it's id will be released.  But we want it separate from the
185     // other buttons since the wxSubwindows will operate on it as
186     // well and we just want to ignore it until destroying it.
187     // For instance, we don't want the bounding box of the radio
188     // buttons to include the dummy button
189     m_radioButtons = new wxSubwindows(n);
190 
191     m_radioWidth = new int[n];
192     m_radioHeight = new int[n];
193 
194     for ( int i = 0; i < n; i++ )
195     {
196         m_radioWidth[i] =
197         m_radioHeight[i] = wxDefaultCoord;
198         long styleBtn = BS_AUTORADIOBUTTON | WS_TABSTOP | WS_CHILD | WS_VISIBLE;
199         if ( i == 0 )
200             styleBtn |= WS_GROUP;
201 
202         wxWindowIDRef subid = NewControlId();
203 
204         HWND hwndBtn = ::CreateWindow(wxT("BUTTON"),
205                                       choices[i].t_str(),
206                                       styleBtn,
207                                       0, 0, 0, 0,   // will be set in SetSize()
208                                       GetHwndOf(parent),
209                                       (HMENU)wxUIntToPtr(subid.GetValue()),
210                                       wxGetInstance(),
211                                       NULL);
212 
213         if ( !hwndBtn )
214         {
215             wxLogLastError(wxT("CreateWindow(radio btn)"));
216 
217             return false;
218         }
219 
220         // Keep track of the subwindow
221         m_radioButtons->Set(i, hwndBtn, subid);
222 
223         SubclassRadioButton((WXHWND)hwndBtn);
224 
225         // Also, make it a subcontrol of this control
226         m_subControls.Add(subid);
227     }
228 
229     // Create a dummy radio control to end the group.
230     m_dummyId = NewControlId();
231 
232     m_dummyHwnd = (WXHWND)::CreateWindow(wxT("BUTTON"),
233                          wxEmptyString,
234                          WS_GROUP | BS_AUTORADIOBUTTON | WS_CHILD,
235                          0, 0, 0, 0, GetHwndOf(parent),
236                          (HMENU)wxUIntToPtr(m_dummyId.GetValue()),
237                          wxGetInstance(), NULL);
238 
239 
240     m_radioButtons->SetFont(GetFont());
241 
242 #ifdef __WXWINCE__
243     // Set the z-order correctly
244     SetWindowPos(GetHwnd(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
245 #endif
246 
247     SetMajorDim(majorDim == 0 ? n : majorDim, style);
248     // Select the first radio button if we have any buttons at all.
249     if ( n > 0 )
250         SetSelection(0);
251     SetSize(pos.x, pos.y, size.x, size.y);
252 
253     // Now that we have items determine what is the best size and set it.
254     SetInitialSize(size);
255 
256     // And update all the buttons positions to match it.
257     const wxSize actualSize = GetSize();
258     PositionAllButtons(pos.x, pos.y, actualSize.x, actualSize.y);
259 
260     // The base wxStaticBox class never accepts focus, but we do because giving
261     // focus to a wxRadioBox actually gives it to one of its buttons, which are
262     // not visible at wx level and hence are not taken into account by the
263     // logic in wxControlContainer code.
264     m_container.EnableSelfFocus();
265 
266     return true;
267 }
268 
Create(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,int majorDim,long style,const wxValidator & val,const wxString & name)269 bool wxRadioBox::Create(wxWindow *parent,
270                         wxWindowID id,
271                         const wxString& title,
272                         const wxPoint& pos,
273                         const wxSize& size,
274                         const wxArrayString& choices,
275                         int majorDim,
276                         long style,
277                         const wxValidator& val,
278                         const wxString& name)
279 {
280     wxCArrayString chs(choices);
281     return Create(parent, id, title, pos, size, chs.GetCount(),
282                   chs.GetStrings(), majorDim, style, val, name);
283 }
284 
~wxRadioBox()285 wxRadioBox::~wxRadioBox()
286 {
287     SendDestroyEvent();
288 
289     // Unsubclass all the radio buttons and remove their soon-to-be-invalid
290     // HWNDs from the global map. Notice that we need to unsubclass because
291     // otherwise we'd need the entries in gs_boxFromButton for the buttons
292     // being deleted to handle the messages generated during their destruction.
293     for ( size_t item = 0; item < m_radioButtons->GetCount(); item++ )
294     {
295         HWND hwnd = m_radioButtons->Get(item);
296 
297         wxSetWindowProc(hwnd, reinterpret_cast<WNDPROC>(s_wndprocRadioBtn));
298         gs_boxFromButton.erase(hwnd);
299     }
300 
301     delete m_radioButtons;
302 
303     if ( m_dummyHwnd )
304         DestroyWindow((HWND)m_dummyHwnd);
305 
306     delete[] m_radioWidth;
307     delete[] m_radioHeight;
308 }
309 
310 // NB: if this code is changed, wxGetWindowForHWND() which relies on having the
311 //     radiobox pointer in GWL_USERDATA for radio buttons must be updated too!
SubclassRadioButton(WXHWND hWndBtn)312 void wxRadioBox::SubclassRadioButton(WXHWND hWndBtn)
313 {
314     HWND hwndBtn = (HWND)hWndBtn;
315 
316     if ( !s_wndprocRadioBtn )
317         s_wndprocRadioBtn = (WXFARPROC)wxGetWindowProc(hwndBtn);
318 
319     wxSetWindowProc(hwndBtn, wxRadioBtnWndProc);
320 
321     gs_boxFromButton[hwndBtn] = this;
322 }
323 
324 // ----------------------------------------------------------------------------
325 // events generation
326 // ----------------------------------------------------------------------------
327 
MSWCommand(WXUINT cmd,WXWORD id_)328 bool wxRadioBox::MSWCommand(WXUINT cmd, WXWORD id_)
329 {
330     const int id = (signed short)id_;
331 
332     if ( cmd == BN_CLICKED )
333     {
334         if (id == GetId())
335             return true;
336 
337         int selectedButton = wxNOT_FOUND;
338 
339         const unsigned int count = GetCount();
340         for ( unsigned int i = 0; i < count; i++ )
341         {
342             const HWND hwndBtn = (*m_radioButtons)[i];
343             if ( id == wxGetWindowId(hwndBtn) )
344             {
345                 // we can get BN_CLICKED for a button which just became focused
346                 // but it may not be checked, in which case we shouldn't
347                 // generate a radiobox selection changed event for it
348                 if ( ::SendMessage(hwndBtn, BM_GETCHECK, 0, 0) == BST_CHECKED )
349                     selectedButton = i;
350 
351                 break;
352             }
353         }
354 
355         if ( selectedButton == wxNOT_FOUND )
356         {
357             // just ignore it - due to a hack with WM_NCHITTEST handling in our
358             // wnd proc, we can receive dummy click messages when we click near
359             // the radiobox edge (this is ugly but Julian wouldn't let me get
360             // rid of this...)
361             return false;
362         }
363 
364         if ( selectedButton != m_selectedButton )
365         {
366             m_selectedButton = selectedButton;
367 
368             SendNotificationEvent();
369         }
370         //else: don't generate events when the selection doesn't change
371 
372         return true;
373     }
374     else
375         return false;
376 }
377 
Command(wxCommandEvent & event)378 void wxRadioBox::Command(wxCommandEvent & event)
379 {
380     SetSelection (event.GetInt());
381     SetFocus();
382     ProcessCommand(event);
383 }
384 
SendNotificationEvent()385 void wxRadioBox::SendNotificationEvent()
386 {
387     wxCommandEvent event(wxEVT_RADIOBOX, m_windowId);
388     event.SetInt( m_selectedButton );
389     event.SetString(GetString(m_selectedButton));
390     event.SetEventObject( this );
391     ProcessCommand(event);
392 }
393 
394 // ----------------------------------------------------------------------------
395 // simple accessors
396 // ----------------------------------------------------------------------------
397 
GetCount() const398 unsigned int wxRadioBox::GetCount() const
399 {
400     return m_radioButtons ? m_radioButtons->GetCount() : 0u;
401 }
402 
SetString(unsigned int item,const wxString & label)403 void wxRadioBox::SetString(unsigned int item, const wxString& label)
404 {
405     wxCHECK_RET( IsValid(item), wxT("invalid radiobox index") );
406 
407     m_radioWidth[item] =
408     m_radioHeight[item] = wxDefaultCoord;
409 
410     ::SetWindowText((*m_radioButtons)[item], label.c_str());
411 
412     InvalidateBestSize();
413 }
414 
SetSelection(int N)415 void wxRadioBox::SetSelection(int N)
416 {
417     wxCHECK_RET( IsValid(N), wxT("invalid radiobox index") );
418 
419     // unselect the old button
420     if ( m_selectedButton != wxNOT_FOUND )
421         ::SendMessage((*m_radioButtons)[m_selectedButton], BM_SETCHECK, 0, 0L);
422 
423     // and select the new one
424     ::SendMessage((*m_radioButtons)[N], BM_SETCHECK, 1, 0L);
425 
426     m_selectedButton = N;
427 }
428 
429 // Find string for position
GetString(unsigned int item) const430 wxString wxRadioBox::GetString(unsigned int item) const
431 {
432     wxCHECK_MSG( IsValid(item), wxEmptyString,
433                  wxT("invalid radiobox index") );
434 
435     return wxGetWindowText((*m_radioButtons)[item]);
436 }
437 
SetFocus()438 void wxRadioBox::SetFocus()
439 {
440     if ( GetCount() > 0 )
441     {
442         ::SetFocus((*m_radioButtons)[m_selectedButton == wxNOT_FOUND
443                                         ? 0
444                                         : m_selectedButton]);
445     }
446 }
447 
CanBeFocused() const448 bool wxRadioBox::CanBeFocused() const
449 {
450     // If the control itself is hidden or disabled, no need to check anything
451     // else.
452     if ( !wxStaticBox::CanBeFocused() )
453         return false;
454 
455     // Otherwise, check if we have any buttons that can be focused.
456     for ( size_t item = 0; item < m_radioButtons->GetCount(); item++ )
457     {
458         if ( IsItemEnabled(item) && IsItemShown(item) )
459             return true;
460     }
461 
462     // We didn't find any items that can accept focus, so neither can we as a
463     // whole accept it.
464     return false;
465 }
466 
467 // Enable a specific button
Enable(unsigned int item,bool enable)468 bool wxRadioBox::Enable(unsigned int item, bool enable)
469 {
470     wxCHECK_MSG( IsValid(item), false,
471                  wxT("invalid item in wxRadioBox::Enable()") );
472 
473     BOOL ret = MSWEnableHWND((*m_radioButtons)[item], enable);
474 
475     return (ret == 0) != enable;
476 }
477 
IsItemEnabled(unsigned int item) const478 bool wxRadioBox::IsItemEnabled(unsigned int item) const
479 {
480     wxCHECK_MSG( IsValid(item), false,
481                  wxT("invalid item in wxRadioBox::IsItemEnabled()") );
482 
483     return ::IsWindowEnabled((*m_radioButtons)[item]) != 0;
484 }
485 
486 // Show a specific button
Show(unsigned int item,bool show)487 bool wxRadioBox::Show(unsigned int item, bool show)
488 {
489     wxCHECK_MSG( IsValid(item), false,
490                  wxT("invalid item in wxRadioBox::Show()") );
491 
492     BOOL ret = ::ShowWindow((*m_radioButtons)[item], show ? SW_SHOW : SW_HIDE);
493 
494     bool changed = (ret != 0) != show;
495     if ( changed )
496     {
497         InvalidateBestSize();
498     }
499 
500     return changed;
501 }
502 
IsItemShown(unsigned int item) const503 bool wxRadioBox::IsItemShown(unsigned int item) const
504 {
505     wxCHECK_MSG( IsValid(item), false,
506                  wxT("invalid item in wxRadioBox::IsItemShown()") );
507 
508     // don't use IsWindowVisible() here because it would return false if the
509     // radiobox itself is hidden while we want to only return false if this
510     // button specifically is hidden
511     return (::GetWindowLong((*m_radioButtons)[item],
512                             GWL_STYLE) & WS_VISIBLE) != 0;
513 }
514 
515 #if wxUSE_TOOLTIPS
516 
HasToolTips() const517 bool wxRadioBox::HasToolTips() const
518 {
519     return wxStaticBox::HasToolTips() || wxRadioBoxBase::HasItemToolTips();
520 }
521 
DoSetItemToolTip(unsigned int item,wxToolTip * tooltip)522 void wxRadioBox::DoSetItemToolTip(unsigned int item, wxToolTip *tooltip)
523 {
524     // we have already checked for the item to be valid in wxRadioBoxBase
525     const HWND hwndRbtn = (*m_radioButtons)[item];
526     if ( tooltip != NULL )
527         tooltip->AddOtherWindow(hwndRbtn);
528     else // unset the tooltip
529         wxToolTip::Remove(hwndRbtn, 0, wxRect(0,0,0,0));
530         // the second parameter can be zero since it's ignored by Remove()
531         // as we pass a rect for which wxRect::IsEmpty()==true...
532 }
533 
534 #endif // wxUSE_TOOLTIPS
535 
Reparent(wxWindowBase * newParent)536 bool wxRadioBox::Reparent(wxWindowBase *newParent)
537 {
538     if ( !wxStaticBox::Reparent(newParent) )
539     {
540         return false;
541     }
542 
543     HWND hwndParent = GetHwndOf(GetParent());
544     for ( size_t item = 0; item < m_radioButtons->GetCount(); item++ )
545     {
546         ::SetParent((*m_radioButtons)[item], hwndParent);
547     }
548 #ifdef __WXWINCE__
549     // put static box under the buttons in the Z-order
550     SetWindowPos(GetHwnd(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
551 #endif
552     return true;
553 }
554 
WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxRadioBox,wxStaticBox,m_radioButtons)555 WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxRadioBox, wxStaticBox, m_radioButtons)
556 
557 // ----------------------------------------------------------------------------
558 // size calculations
559 // ----------------------------------------------------------------------------
560 
561 wxSize wxRadioBox::GetMaxButtonSize() const
562 {
563     // calculate the max button size
564     int widthMax = 0,
565         heightMax = 0;
566     const unsigned int count = GetCount();
567     for ( unsigned int i = 0 ; i < count; i++ )
568     {
569         int width, height;
570         if ( m_radioWidth[i] < 0 )
571         {
572             GetTextExtent(wxGetWindowText((*m_radioButtons)[i]), &width, &height);
573 
574             // adjust the size to take into account the radio box itself
575             // FIXME this is totally bogus!
576             width += RADIO_SIZE;
577             height *= 3;
578             height /= 2;
579         }
580         else
581         {
582             width = m_radioWidth[i];
583             height = m_radioHeight[i];
584         }
585 
586         if ( widthMax < width )
587             widthMax = width;
588         if ( heightMax < height )
589             heightMax = height;
590     }
591 
592     return wxSize(widthMax, heightMax);
593 }
594 
GetTotalButtonSize(const wxSize & sizeBtn) const595 wxSize wxRadioBox::GetTotalButtonSize(const wxSize& sizeBtn) const
596 {
597     // the radiobox should be big enough for its buttons
598     int cx1, cy1;
599     wxGetCharSize(m_hWnd, &cx1, &cy1, GetFont());
600 
601     int extraHeight = cy1;
602 
603     int height = GetRowCount() * sizeBtn.y + cy1/2 + extraHeight;
604     int width  = GetColumnCount() * (sizeBtn.x + cx1) + cx1;
605 
606     // Add extra space under the label, if it exists.
607     if (!wxControl::GetLabel().empty())
608         height += cy1/2;
609 
610     // and also wide enough for its label
611     int widthLabel;
612     GetTextExtent(GetLabelText(), &widthLabel, NULL);
613     widthLabel += RADIO_SIZE; // FIXME this is bogus too
614     if ( widthLabel > width )
615         width = widthLabel;
616 
617     return wxSize(width, height);
618 }
619 
DoGetBestSize() const620 wxSize wxRadioBox::DoGetBestSize() const
621 {
622     if ( !m_radioButtons )
623     {
624         // if we're not fully initialized yet, we can't meaningfully compute
625         // our best size, we'll do it later
626         return wxSize(1, 1);
627     }
628 
629     wxSize best = GetTotalButtonSize(GetMaxButtonSize());
630     CacheBestSize(best);
631     return best;
632 }
633 
DoSetSize(int x,int y,int width,int height,int sizeFlags)634 void wxRadioBox::DoSetSize(int x, int y, int width, int height, int sizeFlags)
635 {
636     if ( (width == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_WIDTH)) ||
637             (height == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_HEIGHT)) )
638     {
639         // Attempt to have a look coherent with other platforms: We compute the
640         // biggest toggle dim, then we align all items according this value.
641         const wxSize totSize = GetTotalButtonSize(GetMaxButtonSize());
642 
643         // only change our width/height if asked for
644         if ( width == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_WIDTH) )
645             width = totSize.x;
646 
647         if ( height == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_HEIGHT) )
648             height = totSize.y;
649     }
650 
651     wxStaticBox::DoSetSize(x, y, width, height);
652 }
653 
DoMoveWindow(int x,int y,int width,int height)654 void wxRadioBox::DoMoveWindow(int x, int y, int width, int height)
655 {
656     wxStaticBox::DoMoveWindow(x, y, width, height);
657 
658     PositionAllButtons(x, y, width, height);
659 }
660 
661 void
PositionAllButtons(int x,int y,int width,int WXUNUSED (height))662 wxRadioBox::PositionAllButtons(int x, int y, int width, int WXUNUSED(height))
663 {
664     wxSize maxSize = GetMaxButtonSize();
665     int maxWidth = maxSize.x,
666         maxHeight = maxSize.y;
667 
668     // Now position all the buttons: the current button will be put at
669     // wxPoint(x_offset, y_offset) and the new row/column will start at
670     // startX/startY. The size of all buttons will be the same wxSize(maxWidth,
671     // maxHeight) except for the buttons in the last column which should extend
672     // to the right border of radiobox and thus can be wider than this.
673 
674     // Also, remember that wxRA_SPECIFY_COLS means that we arrange buttons in
675     // left to right order and GetMajorDim() is the number of columns while
676     // wxRA_SPECIFY_ROWS means that the buttons are arranged top to bottom and
677     // GetMajorDim() is the number of rows.
678 
679     int cx1, cy1;
680     wxGetCharSize(m_hWnd, &cx1, &cy1, GetFont());
681 
682     int x_offset = x + cx1;
683     int y_offset = y + cy1;
684 
685     // Add extra space under the label, if it exists.
686     if (!wxControl::GetLabel().empty())
687         y_offset += cy1/2;
688 
689     int startX = x_offset;
690     int startY = y_offset;
691 
692     const unsigned int count = GetCount();
693     for (unsigned int i = 0; i < count; i++)
694     {
695         // the last button in the row may be wider than the other ones as the
696         // radiobox may be wider than the sum of the button widths (as it
697         // happens, for example, when the radiobox label is very long)
698         bool isLastInTheRow;
699         if ( m_windowStyle & wxRA_SPECIFY_COLS )
700         {
701             // item is the last in its row if it is a multiple of the number of
702             // columns or if it is just the last item
703             unsigned int n = i + 1;
704             isLastInTheRow = ((n % GetMajorDim()) == 0) || (n == count);
705         }
706         else // wxRA_SPECIFY_ROWS
707         {
708             // item is the last in the row if it is in the last columns
709             isLastInTheRow = i >= (count/GetMajorDim())*GetMajorDim();
710         }
711 
712         // is this the start of new row/column?
713         if ( i && (i % GetMajorDim() == 0) )
714         {
715             if ( m_windowStyle & wxRA_SPECIFY_ROWS )
716             {
717                 // start of new column
718                 y_offset = startY;
719                 x_offset += maxWidth + cx1;
720             }
721             else // start of new row
722             {
723                 x_offset = startX;
724                 y_offset += maxHeight;
725                 if (m_radioWidth[0]>0)
726                     y_offset += cy1/2;
727             }
728         }
729 
730         int widthBtn;
731         if ( isLastInTheRow )
732         {
733             // make the button go to the end of radio box
734             widthBtn = startX + width - x_offset - 2*cx1;
735             if ( widthBtn < maxWidth )
736                 widthBtn = maxWidth;
737         }
738         else
739         {
740             // normal button, always of the same size
741             widthBtn = maxWidth;
742         }
743 
744         // make all buttons of the same, maximal size - like this they cover
745         // the radiobox entirely and the radiobox tooltips are always shown
746         // (otherwise they are not when the mouse pointer is in the radiobox
747         // part not belonging to any radiobutton)
748         DoMoveSibling((*m_radioButtons)[i], x_offset, y_offset, widthBtn, maxHeight);
749 
750         // where do we put the next button?
751         if ( m_windowStyle & wxRA_SPECIFY_ROWS )
752         {
753             // below this one
754             y_offset += maxHeight;
755             if (m_radioWidth[0]>0)
756                 y_offset += cy1/2;
757         }
758         else
759         {
760             // to the right of this one
761             x_offset += widthBtn + cx1;
762         }
763     }
764 }
765 
GetItemFromPoint(const wxPoint & pt) const766 int wxRadioBox::GetItemFromPoint(const wxPoint& pt) const
767 {
768     const unsigned int count = GetCount();
769     for ( unsigned int i = 0; i < count; i++ )
770     {
771         RECT rect = wxGetWindowRect((*m_radioButtons)[i]);
772 
773         if ( rect.left <= pt.x && pt.x < rect.right &&
774                 rect.top  <= pt.y && pt.y < rect.bottom )
775         {
776             return i;
777         }
778     }
779 
780     return wxNOT_FOUND;
781 }
782 
783 // ----------------------------------------------------------------------------
784 // radio box drawing
785 // ----------------------------------------------------------------------------
786 
787 #ifndef __WXWINCE__
788 
MSWGetRegionWithoutChildren()789 WXHRGN wxRadioBox::MSWGetRegionWithoutChildren()
790 {
791     RECT rc;
792     ::GetWindowRect(GetHwnd(), &rc);
793     HRGN hrgn = ::CreateRectRgn(rc.left, rc.top, rc.right + 1, rc.bottom + 1);
794 
795     const unsigned int count = GetCount();
796     for ( unsigned int i = 0; i < count; ++i )
797     {
798         // don't clip out hidden children
799         if ( !IsItemShown(i) )
800             continue;
801 
802         ::GetWindowRect((*m_radioButtons)[i], &rc);
803         AutoHRGN hrgnchild(::CreateRectRgnIndirect(&rc));
804         ::CombineRgn(hrgn, hrgn, hrgnchild, RGN_DIFF);
805     }
806 
807     return (WXHRGN)hrgn;
808 }
809 
810 #endif // __WXWINCE__
811 
812 // ---------------------------------------------------------------------------
813 // window proc for radio buttons
814 // ---------------------------------------------------------------------------
815 
wxRadioBtnWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)816 LRESULT APIENTRY _EXPORT wxRadioBtnWndProc(HWND hwnd,
817                                            UINT message,
818                                            WPARAM wParam,
819                                            LPARAM lParam)
820 {
821 
822     wxRadioBox * const radiobox = wxRadioBox::GetFromRadioButtonHWND(hwnd);
823     wxCHECK_MSG( radiobox, 0, wxT("Should have the associated radio box") );
824 
825     switch ( message )
826     {
827         case WM_GETDLGCODE:
828             // we must tell IsDialogMessage()/our kbd processing code that we
829             // want to process arrows ourselves because neither of them is
830             // smart enough to handle arrows properly for us
831             {
832                 long lDlgCode = ::CallWindowProc(CASTWNDPROC s_wndprocRadioBtn, hwnd,
833                                                  message, wParam, lParam);
834 
835                 return lDlgCode | DLGC_WANTARROWS;
836             }
837 
838         case WM_KEYDOWN:
839             {
840                 bool processed = true;
841 
842                 wxDirection dir;
843                 switch ( wParam )
844                 {
845                     case VK_UP:
846                         dir = wxUP;
847                         break;
848 
849                     case VK_LEFT:
850                         dir = wxLEFT;
851                         break;
852 
853                     case VK_DOWN:
854                         dir = wxDOWN;
855                         break;
856 
857                     case VK_RIGHT:
858                         dir = wxRIGHT;
859                         break;
860 
861                     default:
862                         processed = false;
863 
864                         // just to suppress the compiler warning
865                         dir = wxALL;
866                 }
867 
868                 if ( processed )
869                 {
870                     int selOld = radiobox->GetSelection();
871                     int selNew = radiobox->GetNextItem
872                                  (
873                                   selOld,
874                                   dir,
875                                   radiobox->GetWindowStyle()
876                                  );
877 
878                     if ( selNew != selOld )
879                     {
880                         radiobox->SetSelection(selNew);
881                         radiobox->SetFocus();
882 
883                         // emulate the button click
884                         radiobox->SendNotificationEvent();
885 
886                         return 0;
887                     }
888                 }
889             }
890             break;
891 
892         case WM_SETFOCUS:
893         case WM_KILLFOCUS:
894             {
895                 // if we don't do this, no focus events are generated for the
896                 // radiobox and, besides, we need to notify the parent about
897                 // the focus change, otherwise the focus handling logic in
898                 // wxControlContainer doesn't work
899                 if ( message == WM_SETFOCUS )
900                     radiobox->HandleSetFocus((WXHWND)wParam);
901                 else
902                     radiobox->HandleKillFocus((WXHWND)wParam);
903             }
904             break;
905 
906 #ifndef __WXWINCE__
907         case WM_HELP:
908             {
909                 bool processed = false;
910 
911                 wxEvtHandler * const handler = radiobox->GetEventHandler();
912 
913                 HELPINFO* info = (HELPINFO*) lParam;
914                 if ( info->iContextType == HELPINFO_WINDOW )
915                 {
916                     for ( wxWindow* subjectOfHelp = radiobox;
917                           subjectOfHelp;
918                           subjectOfHelp = subjectOfHelp->GetParent() )
919                     {
920                         wxHelpEvent helpEvent(wxEVT_HELP,
921                                               subjectOfHelp->GetId(),
922                                               wxPoint(info->MousePos.x,
923                                                       info->MousePos.y));
924                         helpEvent.SetEventObject(radiobox);
925                         if ( handler->ProcessEvent(helpEvent) )
926                         {
927                             processed = true;
928                             break;
929                         }
930                     }
931                 }
932                 else if (info->iContextType == HELPINFO_MENUITEM)
933                 {
934                     wxHelpEvent helpEvent(wxEVT_HELP, info->iCtrlId);
935                     helpEvent.SetEventObject(radiobox);
936                     processed = handler->ProcessEvent(helpEvent);
937                 }
938 
939                 if ( processed )
940                     return 0;
941             }
942             break;
943 #endif // !__WXWINCE__
944     }
945 
946     return ::CallWindowProc(CASTWNDPROC s_wndprocRadioBtn, hwnd, message, wParam, lParam);
947 }
948 
949 #endif // wxUSE_RADIOBOX
950