update position for widgets in native containers, fixes #15231
[wxWidgets.git] / src / gtk / radiobox.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/radiobox.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #if wxUSE_RADIOBOX
14
15 #include "wx/radiobox.h"
16
17 #if wxUSE_TOOLTIPS
18 #include "wx/tooltip.h"
19 #endif
20
21 #include <gtk/gtk.h>
22 #include "wx/gtk/private.h"
23 #include "wx/gtk/private/gtk2-compat.h"
24
25 #include <gdk/gdkkeysyms.h>
26 #if GTK_CHECK_VERSION(3,0,0)
27 #include <gdk/gdkkeysyms-compat.h>
28 #endif
29
30 //-----------------------------------------------------------------------------
31 // wxGTKRadioButtonInfo
32 //-----------------------------------------------------------------------------
33 // structure internally used by wxRadioBox to store its child buttons
34
35 class wxGTKRadioButtonInfo : public wxObject
36 {
37 public:
38 wxGTKRadioButtonInfo( GtkRadioButton * abutton, const wxRect & arect )
39 : button( abutton ), rect( arect ) {}
40
41 GtkRadioButton * button;
42 wxRect rect;
43 };
44
45 //-----------------------------------------------------------------------------
46 // data
47 //-----------------------------------------------------------------------------
48
49 #include "wx/listimpl.cpp"
50 WX_DEFINE_LIST( wxRadioBoxButtonsInfoList )
51
52 extern bool g_blockEventsOnDrag;
53
54 //-----------------------------------------------------------------------------
55 // "clicked"
56 //-----------------------------------------------------------------------------
57
58 extern "C" {
59 static void gtk_radiobutton_clicked_callback( GtkToggleButton *button, wxRadioBox *rb )
60 {
61 if (g_blockEventsOnDrag) return;
62
63 if (!gtk_toggle_button_get_active(button)) return;
64
65 wxCommandEvent event( wxEVT_RADIOBOX, rb->GetId() );
66 event.SetInt( rb->GetSelection() );
67 event.SetString( rb->GetStringSelection() );
68 event.SetEventObject( rb );
69 rb->HandleWindowEvent(event);
70 }
71 }
72
73 //-----------------------------------------------------------------------------
74 // "key_press_event"
75 //-----------------------------------------------------------------------------
76
77 extern "C" {
78 static gint gtk_radiobox_keypress_callback( GtkWidget *widget, GdkEventKey *gdk_event, wxRadioBox *rb )
79 {
80 if (g_blockEventsOnDrag) return FALSE;
81
82 if ( ((gdk_event->keyval == GDK_Tab) ||
83 (gdk_event->keyval == GDK_ISO_Left_Tab)) &&
84 rb->GetParent() && (rb->GetParent()->HasFlag( wxTAB_TRAVERSAL)) )
85 {
86 wxNavigationKeyEvent new_event;
87 new_event.SetEventObject( rb->GetParent() );
88 // GDK reports GDK_ISO_Left_Tab for SHIFT-TAB
89 new_event.SetDirection( (gdk_event->keyval == GDK_Tab) );
90 // CTRL-TAB changes the (parent) window, i.e. switch notebook page
91 new_event.SetWindowChange( (gdk_event->state & GDK_CONTROL_MASK) != 0 );
92 new_event.SetCurrentFocus( rb );
93 return rb->GetParent()->HandleWindowEvent(new_event);
94 }
95
96 if ((gdk_event->keyval != GDK_Up) &&
97 (gdk_event->keyval != GDK_Down) &&
98 (gdk_event->keyval != GDK_Left) &&
99 (gdk_event->keyval != GDK_Right))
100 {
101 return FALSE;
102 }
103
104 wxRadioBoxButtonsInfoList::compatibility_iterator node = rb->m_buttonsInfo.GetFirst();
105 while( node && GTK_WIDGET( node->GetData()->button ) != widget )
106 {
107 node = node->GetNext();
108 }
109 if (!node)
110 {
111 return FALSE;
112 }
113
114 if ((gdk_event->keyval == GDK_Up) ||
115 (gdk_event->keyval == GDK_Left))
116 {
117 if (node == rb->m_buttonsInfo.GetFirst())
118 node = rb->m_buttonsInfo.GetLast();
119 else
120 node = node->GetPrevious();
121 }
122 else
123 {
124 if (node == rb->m_buttonsInfo.GetLast())
125 node = rb->m_buttonsInfo.GetFirst();
126 else
127 node = node->GetNext();
128 }
129
130 GtkWidget *button = (GtkWidget*) node->GetData()->button;
131
132 gtk_widget_grab_focus( button );
133
134 return TRUE;
135 }
136 }
137
138 extern "C" {
139 static gint gtk_radiobutton_focus_out( GtkWidget * WXUNUSED(widget),
140 GdkEventFocus *WXUNUSED(event),
141 wxRadioBox *win )
142 {
143 // NB: This control is composed of several GtkRadioButton widgets and
144 // when focus changes from one of them to another in the same
145 // wxRadioBox, we get a focus-out event followed by focus-in for
146 // another GtkRadioButton owned by the same control. We don't want
147 // to generate two spurious wxEVT_SET_FOCUS events in this case,
148 // so we defer sending wx events until idle time.
149 win->GTKHandleFocusOut();
150
151 // never stop the signal emission, it seems to break the kbd handling
152 // inside the radiobox
153 return FALSE;
154 }
155 }
156
157 extern "C" {
158 static gint gtk_radiobutton_focus_in( GtkWidget * WXUNUSED(widget),
159 GdkEventFocus *WXUNUSED(event),
160 wxRadioBox *win )
161 {
162 win->GTKHandleFocusIn();
163
164 // never stop the signal emission, it seems to break the kbd handling
165 // inside the radiobox
166 return FALSE;
167 }
168 }
169
170 extern "C" {
171 static void gtk_radiobutton_size_allocate( GtkWidget *widget,
172 GtkAllocation * alloc,
173 wxRadioBox *win )
174 {
175 for ( wxRadioBoxButtonsInfoList::compatibility_iterator node = win->m_buttonsInfo.GetFirst();
176 node;
177 node = node->GetNext())
178 {
179 if (widget == GTK_WIDGET(node->GetData()->button))
180 {
181 const wxPoint origin = win->GetPosition();
182 wxRect rect = wxRect( alloc->x - origin.x, alloc->y - origin.y,
183 alloc->width, alloc->height );
184 node->GetData()->rect = rect;
185 break;
186 }
187 }
188 }
189 }
190
191
192 //-----------------------------------------------------------------------------
193 // wxRadioBox
194 //-----------------------------------------------------------------------------
195
196 IMPLEMENT_DYNAMIC_CLASS(wxRadioBox,wxControl)
197
198 bool wxRadioBox::Create( wxWindow *parent, wxWindowID id,
199 const wxString& title,
200 const wxPoint &pos, const wxSize &size,
201 const wxArrayString& choices, int majorDim,
202 long style, const wxValidator& validator,
203 const wxString &name )
204 {
205 wxCArrayString chs(choices);
206
207 return Create( parent, id, title, pos, size, chs.GetCount(),
208 chs.GetStrings(), majorDim, style, validator, name );
209 }
210
211 bool wxRadioBox::Create( wxWindow *parent, wxWindowID id, const wxString& title,
212 const wxPoint &pos, const wxSize &size,
213 int n, const wxString choices[], int majorDim,
214 long style, const wxValidator& validator,
215 const wxString &name )
216 {
217 if (!PreCreation( parent, pos, size ) ||
218 !CreateBase( parent, id, pos, size, style, validator, name ))
219 {
220 wxFAIL_MSG( wxT("wxRadioBox creation failed") );
221 return false;
222 }
223
224 m_widget = GTKCreateFrame(title);
225 g_object_ref(m_widget);
226 wxControl::SetLabel(title);
227 if ( HasFlag(wxNO_BORDER) )
228 {
229 // If we don't do this here, the wxNO_BORDER style is ignored in Show()
230 gtk_frame_set_shadow_type(GTK_FRAME(m_widget), GTK_SHADOW_NONE);
231 }
232
233
234 // majorDim may be 0 if all trailing parameters were omitted, so don't
235 // assert here but just use the correct value for it
236 SetMajorDim(majorDim == 0 ? n : majorDim, style);
237
238
239 unsigned int num_of_cols = GetColumnCount();
240 unsigned int num_of_rows = GetRowCount();
241
242 GtkRadioButton *rbtn = NULL;
243
244 GtkWidget *table = gtk_table_new( num_of_rows, num_of_cols, FALSE );
245 gtk_table_set_col_spacings( GTK_TABLE(table), 1 );
246 gtk_table_set_row_spacings( GTK_TABLE(table), 1 );
247 gtk_widget_show( table );
248 gtk_container_add( GTK_CONTAINER(m_widget), table );
249
250 wxString label;
251 GSList *radio_button_group = NULL;
252 for (unsigned int i = 0; i < (unsigned int)n; i++)
253 {
254 if ( i != 0 )
255 radio_button_group = gtk_radio_button_get_group( GTK_RADIO_BUTTON(rbtn) );
256
257 label.Empty();
258 for ( wxString::const_iterator pc = choices[i].begin();
259 pc != choices[i].end(); ++pc )
260 {
261 if ( *pc != wxT('&') )
262 label += *pc;
263 }
264
265 rbtn = GTK_RADIO_BUTTON( gtk_radio_button_new_with_label( radio_button_group, wxGTK_CONV( label ) ) );
266 gtk_widget_show( GTK_WIDGET(rbtn) );
267
268 g_signal_connect (rbtn, "key_press_event",
269 G_CALLBACK (gtk_radiobox_keypress_callback), this);
270
271 m_buttonsInfo.Append( new wxGTKRadioButtonInfo( rbtn, wxRect() ) );
272
273 if (HasFlag(wxRA_SPECIFY_COLS))
274 {
275 int left = i%num_of_cols;
276 int right = (i%num_of_cols) + 1;
277 int top = i/num_of_cols;
278 int bottom = (i/num_of_cols)+1;
279 gtk_table_attach( GTK_TABLE(table), GTK_WIDGET(rbtn), left, right, top, bottom,
280 GTK_FILL, GTK_FILL, 1, 1 );
281 }
282 else
283 {
284 int left = i/num_of_rows;
285 int right = (i/num_of_rows) + 1;
286 int top = i%num_of_rows;
287 int bottom = (i%num_of_rows)+1;
288 gtk_table_attach( GTK_TABLE(table), GTK_WIDGET(rbtn), left, right, top, bottom,
289 GTK_FILL, GTK_FILL, 1, 1 );
290 }
291
292 ConnectWidget( GTK_WIDGET(rbtn) );
293
294 if (!i)
295 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(rbtn), TRUE );
296
297 g_signal_connect (rbtn, "clicked",
298 G_CALLBACK (gtk_radiobutton_clicked_callback), this);
299 g_signal_connect (rbtn, "focus_in_event",
300 G_CALLBACK (gtk_radiobutton_focus_in), this);
301 g_signal_connect (rbtn, "focus_out_event",
302 G_CALLBACK (gtk_radiobutton_focus_out), this);
303 g_signal_connect (rbtn, "size_allocate",
304 G_CALLBACK (gtk_radiobutton_size_allocate), this);
305 }
306
307 m_parent->DoAddChild( this );
308
309 PostCreation(size);
310
311 return true;
312 }
313
314 wxRadioBox::~wxRadioBox()
315 {
316 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
317 while (node)
318 {
319 GtkWidget *button = GTK_WIDGET( node->GetData()->button );
320 GTKDisconnect(button);
321 gtk_widget_destroy( button );
322 node = node->GetNext();
323 }
324 WX_CLEAR_LIST( wxRadioBoxButtonsInfoList, m_buttonsInfo );
325 }
326
327 bool wxRadioBox::Show( bool show )
328 {
329 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
330
331 if (!wxControl::Show(show))
332 {
333 // nothing to do
334 return false;
335 }
336
337 if ( HasFlag(wxNO_BORDER) )
338 gtk_widget_hide( m_widget );
339
340 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
341 while (node)
342 {
343 GtkWidget *button = GTK_WIDGET( node->GetData()->button );
344
345 if (show)
346 gtk_widget_show( button );
347 else
348 gtk_widget_hide( button );
349
350 node = node->GetNext();
351 }
352
353 return true;
354 }
355
356 void wxRadioBox::SetSelection( int n )
357 {
358 wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
359
360 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( n );
361
362 wxCHECK_RET( node, wxT("radiobox wrong index") );
363
364 GtkToggleButton *button = GTK_TOGGLE_BUTTON( node->GetData()->button );
365
366 GtkDisableEvents();
367
368 gtk_toggle_button_set_active( button, 1 );
369
370 GtkEnableEvents();
371 }
372
373 int wxRadioBox::GetSelection(void) const
374 {
375 wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid radiobox") );
376
377 int count = 0;
378
379 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
380 while (node)
381 {
382 GtkToggleButton *button = GTK_TOGGLE_BUTTON( node->GetData()->button );
383 if (gtk_toggle_button_get_active(button)) return count;
384 count++;
385 node = node->GetNext();
386 }
387
388 wxFAIL_MSG( wxT("wxRadioBox none selected") );
389
390 return wxNOT_FOUND;
391 }
392
393 wxString wxRadioBox::GetString(unsigned int n) const
394 {
395 wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid radiobox") );
396
397 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( n );
398
399 wxCHECK_MSG( node, wxEmptyString, wxT("radiobox wrong index") );
400
401 GtkLabel* label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(node->GetData()->button)));
402
403 wxString str( wxGTK_CONV_BACK( gtk_label_get_text(label) ) );
404
405 return str;
406 }
407
408 void wxRadioBox::SetLabel( const wxString& label )
409 {
410 wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
411
412 GTKSetLabelForFrame(GTK_FRAME(m_widget), label);
413 }
414
415 void wxRadioBox::SetString(unsigned int item, const wxString& label)
416 {
417 wxCHECK_RET( m_widget != NULL, wxT("invalid radiobox") );
418
419 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
420
421 wxCHECK_RET( node, wxT("radiobox wrong index") );
422
423 GtkLabel* g_label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(node->GetData()->button)));
424
425 gtk_label_set_text( g_label, wxGTK_CONV( label ) );
426 }
427
428 bool wxRadioBox::Enable( bool enable )
429 {
430 if ( !wxControl::Enable( enable ) )
431 return false;
432
433 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
434 while (node)
435 {
436 GtkButton *button = GTK_BUTTON( node->GetData()->button );
437 GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(button)));
438
439 gtk_widget_set_sensitive( GTK_WIDGET(button), enable );
440 gtk_widget_set_sensitive( GTK_WIDGET(label), enable );
441 node = node->GetNext();
442 }
443
444 if (enable)
445 GTKFixSensitivity();
446
447 return true;
448 }
449
450 bool wxRadioBox::Enable(unsigned int item, bool enable)
451 {
452 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
453
454 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
455
456 wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
457
458 GtkButton *button = GTK_BUTTON( node->GetData()->button );
459 GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(button)));
460
461 gtk_widget_set_sensitive( GTK_WIDGET(button), enable );
462 gtk_widget_set_sensitive( GTK_WIDGET(label), enable );
463
464 return true;
465 }
466
467 bool wxRadioBox::IsItemEnabled(unsigned int item) const
468 {
469 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
470
471 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
472
473 wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
474
475 GtkButton *button = GTK_BUTTON( node->GetData()->button );
476
477 // don't use GTK_WIDGET_IS_SENSITIVE() here, we want to return true even if
478 // the parent radiobox is disabled
479 return gtk_widget_get_sensitive(GTK_WIDGET(button)) != 0;
480 }
481
482 bool wxRadioBox::Show(unsigned int item, bool show)
483 {
484 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
485
486 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
487
488 wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
489
490 GtkWidget *button = GTK_WIDGET( node->GetData()->button );
491
492 if (show)
493 gtk_widget_show( button );
494 else
495 gtk_widget_hide( button );
496
497 return true;
498 }
499
500 bool wxRadioBox::IsItemShown(unsigned int item) const
501 {
502 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid radiobox") );
503
504 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.Item( item );
505
506 wxCHECK_MSG( node, false, wxT("radiobox wrong index") );
507
508 GtkButton *button = GTK_BUTTON( node->GetData()->button );
509
510 return gtk_widget_get_visible(GTK_WIDGET(button)) != 0;
511 }
512
513 unsigned int wxRadioBox::GetCount() const
514 {
515 return m_buttonsInfo.GetCount();
516 }
517
518 void wxRadioBox::GtkDisableEvents()
519 {
520 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
521 while (node)
522 {
523 g_signal_handlers_block_by_func(node->GetData()->button,
524 (gpointer)gtk_radiobutton_clicked_callback, this);
525
526 node = node->GetNext();
527 }
528 }
529
530 void wxRadioBox::GtkEnableEvents()
531 {
532 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
533 while (node)
534 {
535 g_signal_handlers_unblock_by_func(node->GetData()->button,
536 (gpointer)gtk_radiobutton_clicked_callback, this);
537
538 node = node->GetNext();
539 }
540 }
541
542 void wxRadioBox::DoApplyWidgetStyle(GtkRcStyle *style)
543 {
544 GTKFrameApplyWidgetStyle(GTK_FRAME(m_widget), style);
545
546 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
547 while (node)
548 {
549 GtkWidget *widget = GTK_WIDGET( node->GetData()->button );
550
551 GTKApplyStyle(widget, style);
552 GTKApplyStyle(gtk_bin_get_child(GTK_BIN(widget)), style);
553
554 node = node->GetNext();
555 }
556 }
557
558 bool wxRadioBox::GTKWidgetNeedsMnemonic() const
559 {
560 return true;
561 }
562
563 void wxRadioBox::GTKWidgetDoSetMnemonic(GtkWidget* w)
564 {
565 GTKFrameSetMnemonicWidget(GTK_FRAME(m_widget), w);
566 }
567
568 #if wxUSE_TOOLTIPS
569 void wxRadioBox::GTKApplyToolTip(const char* tip)
570 {
571 // set this tooltip for all radiobuttons which don't have their own tips
572 unsigned n = 0;
573 for ( wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
574 node;
575 node = node->GetNext(), n++ )
576 {
577 if ( !GetItemToolTip(n) )
578 {
579 wxToolTip::GTKApply(GTK_WIDGET(node->GetData()->button), tip);
580 }
581 }
582 }
583
584 void wxRadioBox::DoSetItemToolTip(unsigned int n, wxToolTip *tooltip)
585 {
586 wxCharBuffer buf;
587 if ( !tooltip )
588 tooltip = GetToolTip();
589 if ( tooltip )
590 buf = wxGTK_CONV(tooltip->GetTip());
591
592 wxToolTip::GTKApply(GTK_WIDGET(m_buttonsInfo[n]->button), buf);
593 }
594
595 #endif // wxUSE_TOOLTIPS
596
597 GdkWindow *wxRadioBox::GTKGetWindow(wxArrayGdkWindows& windows) const
598 {
599 windows.push_back(gtk_widget_get_window(m_widget));
600
601 wxRadioBoxButtonsInfoList::compatibility_iterator node = m_buttonsInfo.GetFirst();
602 while (node)
603 {
604 GtkWidget *button = GTK_WIDGET( node->GetData()->button );
605
606 // don't put NULL pointers in the 'windows' array!
607 if (gtk_widget_get_window(button))
608 windows.push_back(gtk_widget_get_window(button));
609
610 node = node->GetNext();
611 }
612
613 return NULL;
614 }
615
616 // static
617 wxVisualAttributes
618 wxRadioBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
619 {
620 return GetDefaultAttributesFromGTKWidget(gtk_radio_button_new_with_label(NULL, ""));
621 }
622
623 int wxRadioBox::GetItemFromPoint(const wxPoint& point) const
624 {
625 const wxPoint pt = ScreenToClient(point);
626 unsigned n = 0;
627 for ( wxRadioBoxButtonsInfoList::compatibility_iterator
628 node = m_buttonsInfo.GetFirst(); node; node = node->GetNext(), n++ )
629 {
630 if ( m_buttonsInfo[n]->rect.Contains(pt) )
631 return n;
632 }
633
634 return wxNOT_FOUND;
635 }
636
637 #endif // wxUSE_RADIOBOX