Files
wxWidgets/src/gtk/radiobox.cpp
Vadim Zeitlin 96f3832d52 Override DoEnable() instead of Enable() in wxGTK controls
This is slightly simpler, as it doesn't require checking whether the
control state really changes or not (it always does if DoEnable() is
called) and allows disabling the controls before creating them, e.g.
code like

    wxButton* const b = new wxButton();
    b->Disable();
    b->Create(this, wxID_OK);

works as expected now instead of spewing GTK+ errors.
2018-12-09 19:21:51 +01:00

712 lines
21 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/gtk/radiobox.cpp
// Purpose:
// Author: Robert Roebling
// Copyright: (c) 1998 Robert Roebling
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if wxUSE_RADIOBOX
#include "wx/radiobox.h"
#if wxUSE_TOOLTIPS
#include "wx/tooltip.h"
#endif
#include "wx/gtk/private.h"
//-----------------------------------------------------------------------------
// wxGTKRadioButtonInfo
//-----------------------------------------------------------------------------
// structure internally used by wxRadioBox to store its child buttons
class wxGTKRadioButtonInfo : public wxObject
{
public:
wxGTKRadioButtonInfo( GtkRadioButton * abutton, const wxRect & arect )
: button( abutton ), rect( arect ) {}
GtkRadioButton * button;
wxRect rect;
};
//-----------------------------------------------------------------------------
// data
//-----------------------------------------------------------------------------
#include "wx/listimpl.cpp"
WX_DEFINE_LIST( wxRadioBoxButtonsInfoList )
extern bool g_blockEventsOnDrag;
//-----------------------------------------------------------------------------
// "clicked"
//-----------------------------------------------------------------------------
extern "C" {
static void gtk_radiobutton_clicked_callback( GtkToggleButton *button, wxRadioBox *rb )
{
if (g_blockEventsOnDrag) return;
if (!gtk_toggle_button_get_active(button)) return;
wxCommandEvent event( wxEVT_RADIOBOX, rb->GetId() );
event.SetInt( rb->GetSelection() );
event.SetString( rb->GetStringSelection() );
event.SetEventObject( rb );
rb->HandleWindowEvent(event);
}
}
//-----------------------------------------------------------------------------
// "key_press_event"
//-----------------------------------------------------------------------------
extern "C" {
static gint gtk_radiobox_keypress_callback( GtkWidget *widget, GdkEventKey *gdk_event, wxRadioBox *rb )
{
if (g_blockEventsOnDrag) return FALSE;
if ( ((gdk_event->keyval == GDK_KEY_Tab) ||
(gdk_event->keyval == GDK_KEY_ISO_Left_Tab)) &&
rb->GetParent() && (rb->GetParent()->HasFlag( wxTAB_TRAVERSAL)) )
{
wxNavigationKeyEvent new_event;
new_event.SetEventObject( rb->GetParent() );
// GDK reports GDK_ISO_Left_Tab for SHIFT-TAB
new_event.SetDirection( (gdk_event->keyval == GDK_KEY_Tab) );
// CTRL-TAB changes the (parent) window, i.e. switch notebook page
new_event.SetWindowChange( (gdk_event->state & GDK_CONTROL_MASK) != 0 );
new_event.SetCurrentFocus( rb );
return rb->GetParent()->HandleWindowEvent(new_event);
}
if ((gdk_event->keyval != GDK_KEY_Up) &&
(gdk_event->keyval != GDK_KEY_Down) &&
(gdk_event->keyval != GDK_KEY_Left) &&
(gdk_event->keyval != GDK_KEY_Right))
{
return FALSE;
}
wxRadioBoxButtonsInfoList::compatibility_iterator node = rb->m_buttonsInfo.GetFirst();
while( node && GTK_WIDGET( node->GetData()->button ) != widget )
{
node = node->GetNext();
}
if (!node)
{
return FALSE;
}
if ((gdk_event->keyval == GDK_KEY_Up) ||
(gdk_event->keyval == GDK_KEY_Left))
{
if (node == rb->m_buttonsInfo.GetFirst())
node = rb->m_buttonsInfo.GetLast();
else
node = node->GetPrevious();
}
else
{
if (node == rb->m_buttonsInfo.GetLast())
node = rb->m_buttonsInfo.GetFirst();
else
node = node->GetNext();
}
GtkWidget *button = (GtkWidget*) node->GetData()->button;
gtk_widget_grab_focus( button );
return TRUE;
}
}
extern "C" {
static gint gtk_radiobutton_focus_out( GtkWidget * WXUNUSED(widget),
GdkEventFocus *WXUNUSED(event),
wxRadioBox *win )
{
// NB: This control is composed of several GtkRadioButton widgets and
// when focus changes from one of them to another in the same
// wxRadioBox, we get a focus-out event followed by focus-in for
// another GtkRadioButton owned by the same control. We don't want
// to generate two spurious wxEVT_SET_FOCUS events in this case,
// so we defer sending wx events until idle time.
win->GTKHandleFocusOut();
// never stop the signal emission, it seems to break the kbd handling
// inside the radiobox
return FALSE;
}
}
extern "C" {
static gint gtk_radiobutton_focus_in( GtkWidget * WXUNUSED(widget),
GdkEventFocus *WXUNUSED(event),
wxRadioBox *win )
{
win->GTKHandleFocusIn();
// never stop the signal emission, it seems to break the kbd handling
// inside the radiobox
return FALSE;
}
}
extern "C" {
static void gtk_radiobutton_size_allocate( GtkWidget *widget,
GtkAllocation * alloc,
wxRadioBox *win )
{
for ( wxRadioBoxButtonsInfoList::compatibility_iterator node = win->m_buttonsInfo.GetFirst();
node;
node = node->GetNext())
{
if (widget == GTK_WIDGET(node->GetData()->button))
{
const wxPoint origin = win->GetPosition();
wxRect rect = wxRect( alloc->x - origin.x, alloc->y - origin.y,
alloc->width, alloc->height );
node->GetData()->rect = rect;
break;
}
}
}
}
#ifndef __WXGTK3__
extern "C" {
static gboolean expose_event(GtkWidget* widget, GdkEventExpose*, wxWindow*)
{
const GtkAllocation& a = widget->allocation;
gtk_paint_flat_box(gtk_widget_get_style(widget), gtk_widget_get_window(widget),
GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, "", a.x, a.y, a.width, a.height);
return false;
}
}
#endif
//-----------------------------------------------------------------------------
// wxRadioBox
//-----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(wxRadioBox, wxControl);
bool wxRadioBox::Create( wxWindow *parent, wxWindowID id,
const wxString& title,
const wxPoint &pos, const wxSize &size,
const wxArrayString& choices, int majorDim,
long style, const wxValidator& validator,
const wxString &name )
{
wxCArrayString chs(choices);
return Create( parent, id, title, pos, size, chs.GetCount(),
chs.GetStrings(), majorDim, style, validator, name );
}
bool wxRadioBox::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& validator,
const wxString &name )
{
if (!PreCreation( parent, pos, size ) ||
!CreateBase( parent, id, pos, size, style, validator, name ))
{
wxFAIL_MSG( wxT("wxRadioBox creation failed") );
return false;
}
m_widget = GTKCreateFrame(title);
g_object_ref(m_widget);
wxControl::SetLabel(title);
if ( HasFlag(wxNO_BORDER) )
{
// If we don't do this here, the wxNO_BORDER style is ignored in Show()
gtk_frame_set_shadow_type(GTK_FRAME(m_widget), GTK_SHADOW_NONE);
}
// majorDim may be 0 if all trailing parameters were omitted, so don't
// assert here but just use the correct value for it
SetMajorDim(majorDim == 0 ? n : majorDim, style);
unsigned int num_of_cols = GetColumnCount();
unsigned int num_of_rows = GetRowCount();
GtkRadioButton *rbtn = NULL;
#ifdef __WXGTK3__
GtkWidget* grid = gtk_grid_new();
gtk_widget_show(grid);
gtk_container_add(GTK_CONTAINER(m_widget), grid);
#else
GtkWidget *table = gtk_table_new( num_of_rows, num_of_cols, FALSE );
gtk_table_set_col_spacings( GTK_TABLE(table), 1 );
gtk_table_set_row_spacings( GTK_TABLE(table), 1 );
gtk_widget_show( table );
gtk_container_add( GTK_CONTAINER(m_widget), table );
#endif
GSList *radio_button_group = NULL;
for (unsigned int i = 0; i < (unsigned int)n; i++)
{
if ( i != 0 )
radio_button_group = gtk_radio_button_get_group( GTK_RADIO_BUTTON(rbtn) );
// Process mnemonic in the label
wxString label;
bool hasMnemonic = false;
for ( wxString::const_iterator pc = choices[i].begin();
pc != choices[i].end(); ++pc )
{
if ( *pc == wxS('_') )
{
// If we have a literal underscore character in the label
// containing mnemonic, two underscores should be used.
if ( hasMnemonic )
label += wxS('_');
}
else if ( *pc == wxS('&') )
{
++pc; // skip it
if ( pc == choices[i].end() )
{
break;
}
else if ( *pc != wxS('&') )
{
if ( !hasMnemonic )
{
hasMnemonic = true;
// So far we assumed that label doesn't contain mnemonic
// and therefore single underscore characters were not
// replaced by two underscores. Now we have to double
// all existing underscore characters.
label.Replace(wxS("_"), wxS("__"));
label += wxS('_');
}
else
{
wxFAIL_MSG(wxT("duplicate mnemonic char in radio button label"));
}
}
}
label += *pc;
}
if ( hasMnemonic )
rbtn = GTK_RADIO_BUTTON( gtk_radio_button_new_with_mnemonic( radio_button_group, wxGTK_CONV( label ) ) );
else
rbtn = GTK_RADIO_BUTTON( gtk_radio_button_new_with_label( radio_button_group, wxGTK_CONV( label ) ) );
gtk_widget_show( GTK_WIDGET(rbtn) );
g_signal_connect (rbtn, "key_press_event",
G_CALLBACK (gtk_radiobox_keypress_callback), this);
m_buttonsInfo.Append( new wxGTKRadioButtonInfo( rbtn, wxRect() ) );
#ifdef __WXGTK3__
int left, top;
if (HasFlag(wxRA_SPECIFY_COLS))
{
left = i % num_of_cols;
top = i / num_of_cols;
}
else
{
left = i / num_of_rows;
top = i % num_of_rows;
}
gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(rbtn), left, top, 1, 1);
#else
if (HasFlag(wxRA_SPECIFY_COLS))
{
int left = i%num_of_cols;
int right = (i%num_of_cols) + 1;
int top = i/num_of_cols;
int bottom = (i/num_of_cols)+1;
gtk_table_attach( GTK_TABLE(table), GTK_WIDGET(rbtn), left, right, top, bottom,
GTK_FILL, GTK_FILL, 1, 1 );
}
else
{
int left = i/num_of_rows;
int right = (i/num_of_rows) + 1;
int top = i%num_of_rows;
int bottom = (i%num_of_rows)+1;
gtk_table_attach( GTK_TABLE(table), GTK_WIDGET(rbtn), left, right, top, bottom,
GTK_FILL, GTK_FILL, 1, 1 );
}
#endif
ConnectWidget( GTK_WIDGET(rbtn) );
if (!i)
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(rbtn), TRUE );
g_signal_connect (rbtn, "clicked",
G_CALLBACK (gtk_radiobutton_clicked_callback), this);
g_signal_connect (rbtn, "focus_in_event",
G_CALLBACK (gtk_radiobutton_focus_in), this);
g_signal_connect (rbtn, "focus_out_event",
G_CALLBACK (gtk_radiobutton_focus_out), this);
g_signal_connect (rbtn, "size_allocate",
G_CALLBACK (gtk_radiobutton_size_allocate), this);
}
m_parent->DoAddChild( this );
PostCreation(size);
return true;
}
wxRadioBox::~wxRadioBox()
{
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkWidget *button = GTK_WIDGET( node->GetData()->button );
GTKDisconnect(button);
gtk_widget_destroy( button );
node = node->GetNext();
}
WX_CLEAR_LIST( wxRadioBoxButtonsInfoList, m_buttonsInfo );
}
bool wxRadioBox::Show( bool show )
{
wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
if (!wxControl::Show(show))
{
// nothing to do
return false;
}
if ( HasFlag(wxNO_BORDER) )
gtk_widget_hide( m_widget );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkWidget *button = GTK_WIDGET( node->GetData()->button );
if (show)
gtk_widget_show( button );
else
gtk_widget_hide( button );
node = node->GetNext();
}
return true;
}
void wxRadioBox::SetSelection( int n )
{
wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( n );
wxCHECK_RET( node, wxT("radiobox wrong index") );
GtkToggleButton *button = GTK_TOGGLE_BUTTON( node->GetData()->button );
GtkDisableEvents();
gtk_toggle_button_set_active( button, 1 );
GtkEnableEvents();
}
int wxRadioBox::GetSelection(void) const
{
wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid radiobox") );
int count = 0;
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkToggleButton *button = GTK_TOGGLE_BUTTON( node->GetData()->button );
if (gtk_toggle_button_get_active(button)) return count;
count++;
node = node->GetNext();
}
wxFAIL_MSG( wxT("wxRadioBox none selected") );
return wxNOT_FOUND;
}
wxString wxRadioBox::GetString(unsigned int n) const
{
wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( n );
wxCHECK_MSG( node, wxEmptyString, wxT("radiobox wrong index") );
GtkLabel* label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(node->GetData()->button)));
wxString str( wxGTK_CONV_BACK( gtk_label_get_text(label) ) );
return str;
}
void wxRadioBox::SetLabel( const wxString& label )
{
wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
GTKSetLabelForFrame(GTK_FRAME(m_widget), label);
}
void wxRadioBox::SetString(unsigned int item, const wxString& label)
{
wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
wxCHECK_RET( node, wxT("radiobox wrong index") );
GtkLabel* g_label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(node->GetData()->button)));
gtk_label_set_text( g_label, wxGTK_CONV( label ) );
}
bool wxRadioBox::Enable( bool enable )
{
// Explicitly forward to the base class just because we need to override
// this function to prevent it from being hidden by Enable(int, bool)
// overload.
return wxControl::Enable(enable);
}
void wxRadioBox::DoEnable(bool enable)
{
if ( !m_widget )
return;
wxControl::DoEnable(enable);
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkButton *button = GTK_BUTTON( node->GetData()->button );
GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(button)));
gtk_widget_set_sensitive( GTK_WIDGET(button), enable );
gtk_widget_set_sensitive( GTK_WIDGET(label), enable );
node = node->GetNext();
}
if (enable)
GTKFixSensitivity();
}
bool wxRadioBox::Enable(unsigned int item, bool enable)
{
wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
GtkButton *button = GTK_BUTTON( node->GetData()->button );
GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(button)));
gtk_widget_set_sensitive( GTK_WIDGET(button), enable );
gtk_widget_set_sensitive( GTK_WIDGET(label), enable );
return true;
}
bool wxRadioBox::IsItemEnabled(unsigned int item) const
{
wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
GtkButton *button = GTK_BUTTON( node->GetData()->button );
// don't use GTK_WIDGET_IS_SENSITIVE() here, we want to return true even if
// the parent radiobox is disabled
return gtk_widget_get_sensitive(GTK_WIDGET(button)) != 0;
}
bool wxRadioBox::Show(unsigned int item, bool show)
{
wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
GtkWidget *button = GTK_WIDGET( node->GetData()->button );
if (show)
gtk_widget_show( button );
else
gtk_widget_hide( button );
return true;
}
bool wxRadioBox::IsItemShown(unsigned int item) const
{
wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
GtkButton *button = GTK_BUTTON( node->GetData()->button );
return gtk_widget_get_visible(GTK_WIDGET(button)) != 0;
}
unsigned int wxRadioBox::GetCount() const
{
return m_buttonsInfo.GetCount();
}
void wxRadioBox::GtkDisableEvents()
{
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
g_signal_handlers_block_by_func(node->GetData()->button,
(gpointer)gtk_radiobutton_clicked_callback, this);
node = node->GetNext();
}
}
void wxRadioBox::GtkEnableEvents()
{
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
g_signal_handlers_unblock_by_func(node->GetData()->button,
(gpointer)gtk_radiobutton_clicked_callback, this);
node = node->GetNext();
}
}
void wxRadioBox::DoApplyWidgetStyle(GtkRcStyle *style)
{
GTKFrameApplyWidgetStyle(GTK_FRAME(m_widget), style);
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkWidget *widget = GTK_WIDGET( node->GetData()->button );
GTKApplyStyle(widget, style);
GTKApplyStyle(gtk_bin_get_child(GTK_BIN(widget)), style);
node = node->GetNext();
}
#ifndef __WXGTK3__
g_signal_handlers_disconnect_by_func(m_widget, (void*)expose_event, this);
if (m_backgroundColour.IsOk())
g_signal_connect(m_widget, "expose-event", G_CALLBACK(expose_event), this);
#endif
}
bool wxRadioBox::GTKWidgetNeedsMnemonic() const
{
return true;
}
void wxRadioBox::GTKWidgetDoSetMnemonic(GtkWidget* w)
{
GTKFrameSetMnemonicWidget(GTK_FRAME(m_widget), w);
}
#if wxUSE_TOOLTIPS
void wxRadioBox::GTKApplyToolTip(const char* tip)
{
// set this tooltip for all radiobuttons which don't have their own tips
unsigned n = 0;
for ( wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
node;
node = node->GetNext(), n++ )
{
if ( !GetItemToolTip(n) )
{
wxToolTip::GTKApply(GTK_WIDGET(node->GetData()->button), tip);
}
}
}
void wxRadioBox::DoSetItemToolTip(unsigned int n, wxToolTip *tooltip)
{
wxCharBuffer buf;
if ( !tooltip )
tooltip = GetToolTip();
if ( tooltip )
buf = wxGTK_CONV(tooltip->GetTip());
wxToolTip::GTKApply(GTK_WIDGET(m_buttonsInfo[n]->button), buf);
}
#endif // wxUSE_TOOLTIPS
GdkWindow *wxRadioBox::GTKGetWindow(wxArrayGdkWindows& windows) const
{
windows.push_back(gtk_widget_get_window(m_widget));
wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
while (node)
{
GtkWidget *button = GTK_WIDGET( node->GetData()->button );
// don't put NULL pointers in the 'windows' array!
if (gtk_widget_get_window(button))
windows.push_back(gtk_widget_get_window(button));
node = node->GetNext();
}
return NULL;
}
// static
wxVisualAttributes
wxRadioBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
{
return GetDefaultAttributesFromGTKWidget(gtk_radio_button_new_with_label(NULL, ""));
}
int wxRadioBox::GetItemFromPoint(const wxPoint& point) const
{
const wxPoint pt = ScreenToClient(point);
unsigned n = 0;
for ( wxRadioBoxButtonsInfoList::compatibility_iterator
node = m_buttonsInfo.GetFirst(); node; node = node->GetNext(), n++ )
{
if ( m_buttonsInfo[n]->rect.Contains(pt) )
return n;
}
return wxNOT_FOUND;
}
#endif // wxUSE_RADIOBOX