Files
wxWidgets/src/osx/radiobox_osx.cpp
Vadim Zeitlin 8648f839e7 Don't accept focus for wxRadioBox itself in wxOSX
The radio box is just a static box and so can't have focus, only its
child radio buttons should have it.

This resolves the problem with getting stuck on wxRadioBox when full
keyboard access is off, as wxControlContainer code tried to give focus
to wxRadioBox because its AcceptsFocusFromKeyboard() returned true, but
none of its radio buttons could be focused without full keyboard access.

However this introduces a new problem with wxRadioBox being skipped when
full keyboard access is on, which will be fixed in the following commit.

See #18089.
2019-10-26 02:46:39 +02:00

558 lines
13 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/osx/radiobox_osx.cpp
// Purpose: wxRadioBox
// Author: Stefan Csomor
// Modified by: JS Lair (99/11/15) first implementation
// Created: 1998-01-01
// Copyright: (c) Stefan Csomor
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h"
#if wxUSE_RADIOBOX
#include "wx/radiobox.h"
#ifndef WX_PRECOMP
#include "wx/radiobut.h"
#include "wx/arrstr.h"
#endif
#include "wx/osx/private.h"
// regarding layout: note that there are differences in inter-control
// spacing between InterfaceBuild and the Human Interface Guidelines, we stick
// to the latter, as those are also used eg in the System Preferences Dialogs
wxIMPLEMENT_DYNAMIC_CLASS(wxRadioBox, wxControl);
wxBEGIN_EVENT_TABLE(wxRadioBox, wxControl)
EVT_RADIOBUTTON( wxID_ANY , wxRadioBox::OnRadioButton )
wxEND_EVENT_TABLE()
void wxRadioBox::OnRadioButton( wxCommandEvent &outer )
{
if ( outer.IsChecked() )
{
wxCommandEvent event( wxEVT_RADIOBOX, m_windowId );
int i = GetSelection() ;
event.SetInt(i);
event.SetString(GetString(i));
event.SetEventObject( this );
ProcessCommand(event);
}
}
wxRadioBox::wxRadioBox()
{
m_noItems = 0;
m_noRowsOrCols = 0;
m_radioButtonCycle = NULL;
}
wxRadioBox::~wxRadioBox()
{
SendDestroyEvent();
wxRadioButton *next, *current;
current = m_radioButtonCycle->NextInCycle();
if (current != NULL)
{
while (current != m_radioButtonCycle)
{
next = current->NextInCycle();
delete current;
current = next;
}
delete current;
}
}
// Create the radiobox for two-step construction
bool wxRadioBox::Create( wxWindow *parent,
wxWindowID id, const wxString& label,
const wxPoint& pos, const wxSize& size,
const wxArrayString& choices,
int majorDim, long style,
const wxValidator& val, const wxString& name )
{
wxCArrayString chs(choices);
return Create(
parent, id, label, pos, size, chs.GetCount(),
chs.GetStrings(), majorDim, style, val, name);
}
bool wxRadioBox::Create( wxWindow *parent,
wxWindowID id, const wxString& label,
const wxPoint& pos, const wxSize& size,
int n, const wxString choices[],
int majorDim, long style,
const wxValidator& val, const wxString& name )
{
DontCreatePeer();
if ( !wxControl::Create( parent, id, pos, size, style, val, name ) )
return false;
// The radio box itself never accepts focus, only its child buttons do.
m_container.DisableSelfFocus();
// during construction we must keep this at 0, otherwise GetBestSize fails
m_noItems = 0;
m_noRowsOrCols = majorDim;
m_radioButtonCycle = NULL;
SetMajorDim( majorDim == 0 ? n : majorDim, style );
m_labelOrig = m_label = label;
SetPeer(wxWidgetImpl::CreateGroupBox( this, parent, id, label, pos, size, style, GetExtraStyle() ));
for (int i = 0; i < n; i++)
{
wxRadioButton *radBtn = new wxRadioButton(
this,
wxID_ANY,
GetLabelText(choices[i]),
wxPoint( 5, 20 * i + 10 ),
wxDefaultSize,
i == 0 ? wxRB_GROUP : 0 );
if ( i == 0 )
m_radioButtonCycle = radBtn;
// m_radioButtonCycle = radBtn->AddInCycle( m_radioButtonCycle );
}
// as all radiobuttons have been set-up, set the correct dimensions
m_noItems = (unsigned int)n;
SetMajorDim( majorDim == 0 ? n : majorDim, style );
SetSelection( 0 );
InvalidateBestSize();
SetInitialSize( size );
MacPostControlCreate( pos, size );
return true;
}
// Enables or disables the entire radiobox
//
bool wxRadioBox::Enable(bool enable)
{
wxRadioButton *current;
if (!wxControl::Enable( enable ))
return false;
current = m_radioButtonCycle;
for (unsigned int i = 0; i < m_noItems; i++)
{
current->Enable( enable );
current = current->NextInCycle();
}
return true;
}
// Enables or disables an given button
//
bool wxRadioBox::Enable(unsigned int item, bool enable)
{
if (!IsValid( item ))
return false;
unsigned int i = 0;
wxRadioButton *current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->Enable( enable );
}
bool wxRadioBox::IsItemEnabled(unsigned int item) const
{
if (!IsValid( item ))
return false;
unsigned int i = 0;
wxRadioButton *current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->IsEnabled();
}
// Returns the label for the given button
//
wxString wxRadioBox::GetString(unsigned int item) const
{
wxRadioButton *current;
if (!IsValid( item ))
return wxEmptyString;
unsigned int i = 0;
current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->GetLabel();
}
// Returns the zero-based position of the selected button
//
int wxRadioBox::GetSelection() const
{
int i;
wxRadioButton *current;
i = 0;
current = m_radioButtonCycle;
while (!current->GetValue())
{
i++;
current = current->NextInCycle();
}
return i;
}
// Sets the label of a given button
//
void wxRadioBox::SetString(unsigned int item,const wxString& label)
{
if (!IsValid( item ))
return;
unsigned int i = 0;
wxRadioButton *current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->SetLabel( label );
}
// Sets a button by passing the desired position. This does not cause
// wxEVT_RADIOBOX event to get emitted
//
void wxRadioBox::SetSelection(int item)
{
int i;
wxRadioButton *current;
if (!IsValid( item ))
return;
i = 0;
current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
current->SetValue( true );
}
// Shows or hides the entire radiobox
//
bool wxRadioBox::Show(bool show)
{
wxRadioButton *current;
current = m_radioButtonCycle;
for (unsigned int i=0; i<m_noItems; i++)
{
current->Show( show );
current = current->NextInCycle();
}
wxControl::Show( show );
return true;
}
// Shows or hides the given button
//
bool wxRadioBox::Show(unsigned int item, bool show)
{
if (!IsValid( item ))
return false;
unsigned int i = 0;
wxRadioButton *current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->Show( show );
}
bool wxRadioBox::IsItemShown(unsigned int item) const
{
if (!IsValid( item ))
return false;
unsigned int i = 0;
wxRadioButton *current = m_radioButtonCycle;
while (i != item)
{
i++;
current = current->NextInCycle();
}
return current->IsShown();
}
// Simulates the effect of the user issuing a command to the item
//
void wxRadioBox::Command( wxCommandEvent& event )
{
SetSelection( event.GetInt() );
ProcessCommand( event );
}
// Sets the selected button to receive keyboard input
//
void wxRadioBox::SetFocus()
{
wxRadioButton *current;
current = m_radioButtonCycle;
while (!current->GetValue())
{
current = current->NextInCycle();
}
current->SetFocus();
}
// Simulates the effect of the user issuing a command to the item
//
// Cocoa has an additional border are of about 3 pixels
#define RADIO_SIZE 23
void wxRadioBox::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxRadioButton *current;
// define the position
int x_current, y_current;
int x_offset, y_offset;
int widthOld, heightOld;
GetSize( &widthOld, &heightOld );
GetPosition( &x_current, &y_current );
x_offset = x;
y_offset = y;
if (!(sizeFlags & wxSIZE_ALLOW_MINUS_ONE))
{
if (x == wxDefaultCoord)
x_offset = x_current;
if (y == wxDefaultCoord)
y_offset = y_current;
}
// define size
int charWidth, charHeight;
int maxWidth, maxHeight;
int eachWidth[128], eachHeight[128];
int totWidth, totHeight;
GetTextExtent(
wxT("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
&charWidth, &charHeight );
charWidth /= 52;
maxWidth = -1;
maxHeight = -1;
wxSize bestSizeRadio ;
if ( m_radioButtonCycle )
bestSizeRadio = m_radioButtonCycle->GetBestSize();
for (unsigned int i = 0 ; i < m_noItems; i++)
{
GetTextExtent(GetString(i), &eachWidth[i], &eachHeight[i] );
eachWidth[i] = eachWidth[i] + RADIO_SIZE;
eachHeight[i] = wxMax( eachHeight[i], bestSizeRadio.y );
if (maxWidth < eachWidth[i])
maxWidth = eachWidth[i];
if (maxHeight < eachHeight[i])
maxHeight = eachHeight[i];
}
// according to HIG (official space - 3 Pixels Diff between Frame and Layout size)
int space = 3;
if ( GetWindowVariant() == wxWINDOW_VARIANT_MINI )
space = 2;
totHeight = GetRowCount() * maxHeight + (GetRowCount() - 1) * space;
totWidth = GetColumnCount() * (maxWidth + charWidth);
// Determine the full size in case we need to use it as fallback.
wxSize sz;
if ( (width == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_WIDTH)) ||
(height == wxDefaultCoord && (sizeFlags & wxSIZE_AUTO_HEIGHT)) )
{
sz = DoGetSizeFromClientSize( wxSize( totWidth, totHeight ) ) ;
}
// change the width / height only when specified
if ( width == wxDefaultCoord )
{
if ( sizeFlags & wxSIZE_AUTO_WIDTH )
width = sz.x;
else
width = widthOld;
}
if ( height == wxDefaultCoord )
{
if ( sizeFlags & wxSIZE_AUTO_HEIGHT )
height = sz.y;
else
height = heightOld;
}
wxControl::DoSetSize( x_offset, y_offset, width, height, wxSIZE_AUTO );
// But now recompute the full size again because it could have changed.
// This notably happens if the previous full size was too small to fully
// fit the box margins.
sz = DoGetSizeFromClientSize( wxSize( totWidth, totHeight ) ) ;
// arrange radio buttons
int x_start, y_start;
x_start = ( width - sz.x ) / 2;
y_start = ( height - sz.y ) / 2;
x_offset = x_start;
y_offset = y_start;
current = m_radioButtonCycle;
int i;
for (i = 0 ; i < (int)m_noItems; i++)
{
// not to do for the zero button!
if ((i > 0) && ((i % GetMajorDim()) == 0))
{
if (m_windowStyle & wxRA_SPECIFY_ROWS)
{
x_offset += maxWidth + charWidth;
y_offset = y_start;
}
else
{
x_offset = x_start;
y_offset += maxHeight + space;
}
}
current->SetSize( x_offset, y_offset, eachWidth[i], eachHeight[i] );
current = current->NextInCycle();
if (m_windowStyle & wxRA_SPECIFY_ROWS)
y_offset += maxHeight + space;
else
x_offset += maxWidth + charWidth;
}
}
wxSize wxRadioBox::DoGetBestSize() const
{
int charWidth, charHeight;
int maxWidth, maxHeight;
int eachWidth, eachHeight;
int totWidth, totHeight;
wxFont font = GetFont(); // GetParent()->GetFont()
GetTextExtent(
wxT("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
&charWidth, &charHeight, NULL, NULL, &font );
charWidth /= 52;
maxWidth = -1;
maxHeight = -1;
wxSize bestSizeRadio ;
if ( m_radioButtonCycle )
bestSizeRadio = m_radioButtonCycle->GetBestSize();
for (unsigned int i = 0 ; i < m_noItems; i++)
{
GetTextExtent(GetString(i), &eachWidth, &eachHeight, NULL, NULL, &font );
eachWidth = (eachWidth + RADIO_SIZE);
eachHeight = wxMax(eachHeight, bestSizeRadio.y );
if (maxWidth < eachWidth)
maxWidth = eachWidth;
if (maxHeight < eachHeight)
maxHeight = eachHeight;
}
// according to HIG (official space - 3 Pixels Diff between Frame and Layout size)
int space = 3;
if ( GetWindowVariant() == wxWINDOW_VARIANT_MINI )
space = 2;
totHeight = GetRowCount() * maxHeight + (GetRowCount() - 1) * space;
totWidth = GetColumnCount() * (maxWidth + charWidth);
wxSize sz = DoGetSizeFromClientSize( wxSize( totWidth, totHeight ) );
totWidth = sz.x;
totHeight = sz.y;
// optimum size is an additional 5 pt border to all sides
totWidth += 10;
totHeight += 10;
// handle radio box title as well
GetTextExtent( GetLabel(), &eachWidth, NULL );
eachWidth = (int)(eachWidth + RADIO_SIZE) + 3 * charWidth;
if (totWidth < eachWidth)
totWidth = eachWidth;
return wxSize( totWidth, totHeight );
}
bool wxRadioBox::SetFont(const wxFont& font)
{
bool retval = wxWindowBase::SetFont( font );
// dont' update the native control, it has its own small font
// should we iterate over the children ?
return retval;
}
#endif // wxUSE_RADIOBOX