gtkradiobutton.c 22.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17 18

/*
19
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
20 21 22 23 24
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

25
#include "config.h"
26 27 28

#include "gtkradiobutton.h"

29
#include "gtkcontainerprivate.h"
30
#include "gtkbuttonprivate.h"
31
#include "gtktogglebuttonprivate.h"
32
#include "gtkcheckbuttonprivate.h"
Elliot Lee's avatar
Elliot Lee committed
33
#include "gtklabel.h"
34
#include "gtkmarshalers.h"
35
#include "gtkprivate.h"
Manish Singh's avatar
Manish Singh committed
36
#include "gtkintl.h"
37
#include "a11y/gtkradiobuttonaccessible.h"
38
#include "gtkstylecontextprivate.h"
Elliot Lee's avatar
Elliot Lee committed
39

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
/**
 * SECTION:gtkradiobutton
 * @Short_description: A choice from multiple check buttons
 * @Title: GtkRadioButton
 * @See_also: #GtkComboBox
 *
 * A single radio button performs the same basic function as a #GtkCheckButton,
 * as its position in the object hierarchy reflects. It is only when multiple
 * radio buttons are grouped together that they become a different user
 * interface component in their own right.
 *
 * Every radio button is a member of some group of radio buttons. When one is
 * selected, all other radio buttons in the same group are deselected. A
 * #GtkRadioButton is one way of giving the user a choice from many options.
 *
 * Radio button widgets are created with gtk_radio_button_new(), passing %NULL
 * as the argument if this is the first radio button in a group. In subsequent
 * calls, the group you wish to add this button to should be passed as an
 * argument. Optionally, gtk_radio_button_new_with_label() can be used if you
 * want a text label on the radio button.
 *
 * Alternatively, when adding widgets to an existing group of radio buttons,
 * use gtk_radio_button_new_from_widget() with a #GtkRadioButton that already
 * has a group assigned to it. The convenience function
 * gtk_radio_button_new_with_label_from_widget() is also provided.
 *
 * To retrieve the group a #GtkRadioButton is assigned to, use
 * gtk_radio_button_get_group().
 *
 * To remove a #GtkRadioButton from one group and make it part of a new one,
 * use gtk_radio_button_set_group().
 *
 * The group list does not need to be freed, as each #GtkRadioButton will remove
 * itself and its list item when it is destroyed.
 *
75 76
 * # CSS nodes
 *
77 78
 * |[<!-- language="plain" -->
 * radiobutton
79 80
 * ├── radio
 * ╰── <child>
81 82
 * ]|
 *
83 84 85
 * A GtkRadioButton with indicator (see gtk_toggle_button_set_mode()) has a
 * main CSS node with name radiobutton and a subnode with name radio.
 *
86 87
 * |[<!-- language="plain" -->
 * button.radio
88 89
 * ├── radio
 * ╰── <child>
90 91
 * ]|
 *
92 93 94 95
 * A GtkRadioButton without indicator changes the name of its main node
 * to button and adds a .radio style class to it. The subnode is invisible
 * in this case.
 *
96 97
 * ## How to create a group of two radio buttons.
 *
98
 * |[<!-- language="C" -->
99 100 101 102
 * void create_radio_buttons (void) {
 *
 *    GtkWidget *window, *radio1, *radio2, *box, *entry;
 *    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
103 104
 *    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
 *    gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
105
 *
106
 *    // Create a radio button with a GtkEntry widget
107
 *    radio1 = gtk_radio_button_new (NULL);
108
 *    entry = gtk_entry_new ();
109 110 111
 *    gtk_container_add (GTK_CONTAINER (radio1), entry);
 *
 *
112
 *    // Create a radio button with a label
113
 *    radio2 = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (radio1),
114
 *                                                          "I’m the second radio button.");
115
 *
116
 *    // Pack them into a box, then show all the widgets
117 118
 *    gtk_box_pack_start (GTK_BOX (box), radio1);
 *    gtk_box_pack_start (GTK_BOX (box), radio2);
119 120 121 122
 *    gtk_container_add (GTK_CONTAINER (window), box);
 *    gtk_widget_show_all (window);
 *    return;
 * }
123
 * ]|
124 125 126 127 128 129 130 131 132
 *
 * When an unselected button in the group is clicked the clicked button
 * receives the #GtkToggleButton::toggled signal, as does the previously
 * selected button.
 * Inside the #GtkToggleButton::toggled handler, gtk_toggle_button_get_active()
 * can be used to determine if the button has been selected or deselected.
 */


133
struct _GtkRadioButtonPrivate
134 135 136 137
{
  GSList *group;
};

138
enum {
Manish Singh's avatar
Manish Singh committed
139
  PROP_0,
140 141
  PROP_GROUP,
  LAST_PROP
142 143
};

144
static GParamSpec *radio_button_props[LAST_PROP] = { NULL, };
Elliot Lee's avatar
Elliot Lee committed
145

146
static void     gtk_radio_button_destroy        (GtkWidget           *widget);
147 148 149
static gboolean gtk_radio_button_focus          (GtkWidget           *widget,
						 GtkDirectionType     direction);
static void     gtk_radio_button_clicked        (GtkButton           *button);
Manish Singh's avatar
Manish Singh committed
150 151 152 153 154 155 156 157
static void     gtk_radio_button_set_property   (GObject             *object,
						 guint                prop_id,
						 const GValue        *value,
						 GParamSpec          *pspec);
static void     gtk_radio_button_get_property   (GObject             *object,
						 guint                prop_id,
						 GValue              *value,
						 GParamSpec          *pspec);
Elliot Lee's avatar
Elliot Lee committed
158

159
G_DEFINE_TYPE_WITH_PRIVATE (GtkRadioButton, gtk_radio_button, GTK_TYPE_CHECK_BUTTON)
Elliot Lee's avatar
Elliot Lee committed
160

161
static guint group_changed_signal = 0;
Elliot Lee's avatar
Elliot Lee committed
162 163 164 165

static void
gtk_radio_button_class_init (GtkRadioButtonClass *class)
{
Manish Singh's avatar
Manish Singh committed
166
  GObjectClass *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
167
  GtkButtonClass *button_class;
168
  GtkWidgetClass *widget_class;
Elliot Lee's avatar
Elliot Lee committed
169

Manish Singh's avatar
Manish Singh committed
170
  gobject_class = G_OBJECT_CLASS (class);
171
  widget_class = (GtkWidgetClass*) class;
Elliot Lee's avatar
Elliot Lee committed
172 173
  button_class = (GtkButtonClass*) class;

Manish Singh's avatar
Manish Singh committed
174 175
  gobject_class->set_property = gtk_radio_button_set_property;
  gobject_class->get_property = gtk_radio_button_get_property;
176

177 178 179 180 181
  /**
   * GtkRadioButton:group:
   *
   * Sets a new group for a radio button.
   */
182 183 184 185 186 187 188 189 190
  radio_button_props[PROP_GROUP] =
      g_param_spec_object ("group",
                           P_("Group"),
                           P_("The radio button whose group this widget belongs to."),
                           GTK_TYPE_RADIO_BUTTON,
                           GTK_PARAM_WRITABLE);

  g_object_class_install_properties (gobject_class, LAST_PROP, radio_button_props);

191
  widget_class->destroy = gtk_radio_button_destroy;
192 193
  widget_class->focus = gtk_radio_button_focus;

Elliot Lee's avatar
Elliot Lee committed
194 195
  button_class->clicked = gtk_radio_button_clicked;

196 197 198
  class->group_changed = NULL;

  /**
199
   * GtkRadioButton::group-changed:
200
   * @button: the object which received the signal
201 202 203 204
   *
   * Emitted when the group of radio buttons that a radio button belongs
   * to changes. This is emitted when a radio button switches from
   * being alone to being part of a group of 2 or more buttons, or
Matthias Clasen's avatar
Matthias Clasen committed
205
   * vice-versa, and when a button is moved from one group of 2 or
206 207
   * more buttons to a different one, but not when the composition
   * of the group that a button belongs to changes.
208 209
   *
   * Since: 2.4
210
   */
211
  group_changed_signal = g_signal_new (I_("group-changed"),
212
				       G_OBJECT_CLASS_TYPE (gobject_class),
213 214 215
				       G_SIGNAL_RUN_FIRST,
				       G_STRUCT_OFFSET (GtkRadioButtonClass, group_changed),
				       NULL, NULL,
216
				       NULL,
217
				       G_TYPE_NONE, 0);
218

219
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_RADIO_BUTTON_ACCESSIBLE);
220
  gtk_widget_class_set_css_name (widget_class, "radiobutton");
Elliot Lee's avatar
Elliot Lee committed
221 222 223 224 225
}

static void
gtk_radio_button_init (GtkRadioButton *radio_button)
{
226
  GtkRadioButtonPrivate *priv;
227
  GtkCssNode *css_node;
228

229
  radio_button->priv = gtk_radio_button_get_instance_private (radio_button);
230 231
  priv = radio_button->priv;

232
  gtk_widget_set_receives_default (GTK_WIDGET (radio_button), FALSE);
233

234
  _gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), TRUE);
235

236
  priv->group = g_slist_prepend (NULL, radio_button);
237 238 239

  css_node = gtk_check_button_get_indicator_node (GTK_CHECK_BUTTON (radio_button));
  gtk_css_node_set_name (css_node, I_("radio"));
Elliot Lee's avatar
Elliot Lee committed
240 241
}

242
static void
Manish Singh's avatar
Manish Singh committed
243 244 245 246
gtk_radio_button_set_property (GObject      *object,
			       guint         prop_id,
			       const GValue *value,
			       GParamSpec   *pspec)
247
{
248 249 250 251
  GtkRadioButton *radio_button;

  radio_button = GTK_RADIO_BUTTON (object);

Manish Singh's avatar
Manish Singh committed
252
  switch (prop_id)
253 254
    {
      GSList *slist;
255
      GtkRadioButton *button;
256

Manish Singh's avatar
Manish Singh committed
257
    case PROP_GROUP:
258
        button = g_value_get_object (value);
259 260 261

      if (button)
	slist = gtk_radio_button_get_group (button);
262 263 264 265 266
      else
	slist = NULL;
      gtk_radio_button_set_group (radio_button, slist);
      break;
    default:
Manish Singh's avatar
Manish Singh committed
267
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
268 269 270 271 272
      break;
    }
}

static void
Manish Singh's avatar
Manish Singh committed
273 274 275 276
gtk_radio_button_get_property (GObject    *object,
			       guint       prop_id,
			       GValue     *value,
			       GParamSpec *pspec)
277
{
Manish Singh's avatar
Manish Singh committed
278
  switch (prop_id)
279 280
    {
    default:
Manish Singh's avatar
Manish Singh committed
281
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
282 283 284 285
      break;
    }
}

286 287 288
/**
 * gtk_radio_button_set_group:
 * @radio_button: a #GtkRadioButton.
289 290
 * @group: (element-type GtkRadioButton) (allow-none): an existing radio
 *     button group, such as one returned from gtk_radio_button_get_group(), or %NULL.
291
 *
292
 * Sets a #GtkRadioButton’s group. It should be noted that this does not change
293 294 295 296
 * the layout of your interface in any way, so if you are changing the group,
 * it is likely you will need to re-arrange the user interface to reflect these
 * changes.
 */
297 298
void
gtk_radio_button_set_group (GtkRadioButton *radio_button,
299
			    GSList         *group)
Elliot Lee's avatar
Elliot Lee committed
300
{
301
  GtkRadioButtonPrivate *priv;
302 303
  GtkWidget *old_group_singleton = NULL;
  GtkWidget *new_group_singleton = NULL;
304

305
  g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));
306 307 308

  if (g_slist_find (group, radio_button))
    return;
309

310 311 312
  priv = radio_button->priv;

  if (priv->group)
313
    {
314
      GSList *slist;
315

316 317 318 319 320 321
      priv->group = g_slist_remove (priv->group, radio_button);

      if (priv->group && !priv->group->next)
	old_group_singleton = g_object_ref (priv->group->data);

      for (slist = priv->group; slist; slist = slist->next)
322
	{
323 324 325
	  GtkRadioButton *tmp_button;
	  
	  tmp_button = slist->data;
326 327

	  tmp_button->priv->group = priv->group;
328 329
	}
    }
330
  
331 332
  if (group && !group->next)
    new_group_singleton = g_object_ref (group->data);
333 334 335

  priv->group = g_slist_prepend (group, radio_button);

336
  if (group)
Elliot Lee's avatar
Elliot Lee committed
337
    {
338 339 340
      GSList *slist;
      
      for (slist = group; slist; slist = slist->next)
Elliot Lee's avatar
Elliot Lee committed
341
	{
342 343 344
	  GtkRadioButton *tmp_button;
	  
	  tmp_button = slist->data;
345 346

	  tmp_button->priv->group = priv->group;
Elliot Lee's avatar
Elliot Lee committed
347 348
	}
    }
349

350 351
  g_object_ref (radio_button);
  
352
  g_object_notify_by_pspec (G_OBJECT (radio_button), radio_button_props[PROP_GROUP]);
353 354 355 356 357 358 359 360 361 362 363 364
  g_signal_emit (radio_button, group_changed_signal, 0);
  if (old_group_singleton)
    {
      g_signal_emit (old_group_singleton, group_changed_signal, 0);
      g_object_unref (old_group_singleton);
    }
  if (new_group_singleton)
    {
      g_signal_emit (new_group_singleton, group_changed_signal, 0);
      g_object_unref (new_group_singleton);
    }

365
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), group == NULL);
366 367

  g_object_unref (radio_button);
368 369
}

370 371 372 373 374 375 376 377 378 379 380 381
/**
 * gtk_radio_button_join_group:
 * @radio_button: the #GtkRadioButton object
 * @group_source: (allow-none): a radio button object whos group we are 
 *   joining, or %NULL to remove the radio button from its group
 *
 * Joins a #GtkRadioButton object to the group of another #GtkRadioButton object
 *
 * Use this in language bindings instead of the gtk_radio_button_get_group() 
 * and gtk_radio_button_set_group() methods
 *
 * A common way to set up a group of radio buttons is the following:
382
 * |[<!-- language="C" -->
383 384 385
 *   GtkRadioButton *radio_button;
 *   GtkRadioButton *last_button;
 *
386
 *   while (some_condition)
387
 *     {
388
 *        radio_button = gtk_radio_button_new (NULL);
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
 *
 *        gtk_radio_button_join_group (radio_button, last_button);
 *        last_button = radio_button;
 *     }
 * ]|
 *
 * Since: 3.0
 */
void
gtk_radio_button_join_group (GtkRadioButton *radio_button, 
			     GtkRadioButton *group_source)
{
  g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));
  g_return_if_fail (group_source == NULL || GTK_IS_RADIO_BUTTON (group_source));

  if (group_source)
    {
      GSList *group;
      group = gtk_radio_button_get_group (group_source);

      if (!group)
        {
          /* if we are not already part of a group we need to set up a new one
             and then get the newly created group */
          gtk_radio_button_set_group (group_source, NULL);
          group = gtk_radio_button_get_group (group_source);
        }

      gtk_radio_button_set_group (radio_button, group);
    }
  else
    {
      gtk_radio_button_set_group (radio_button, NULL);
    }
}

425 426
/**
 * gtk_radio_button_new:
427 428
 * @group: (element-type GtkRadioButton) (allow-none): an existing
 *         radio button group, or %NULL if you are creating a new group.
429 430 431 432
 *
 * Creates a new #GtkRadioButton. To be of any practical value, a widget should
 * then be packed into the radio button.
 *
433
 * Returns: a new radio button
434
 */
435 436 437 438 439
GtkWidget*
gtk_radio_button_new (GSList *group)
{
  GtkRadioButton *radio_button;

Manish Singh's avatar
Manish Singh committed
440
  radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, NULL);
441

442 443
  if (group)
    gtk_radio_button_set_group (radio_button, group);
Elliot Lee's avatar
Elliot Lee committed
444 445 446 447

  return GTK_WIDGET (radio_button);
}

448 449
/**
 * gtk_radio_button_new_with_label:
450 451
 * @group: (element-type GtkRadioButton) (allow-none): an existing
 *         radio button group, or %NULL if you are creating a new group.
452 453 454 455
 * @label: the text label to display next to the radio button.
 *
 * Creates a new #GtkRadioButton with a text label.
 *
456
 * Returns: a new radio button.
457
 */
Elliot Lee's avatar
Elliot Lee committed
458 459 460 461 462 463
GtkWidget*
gtk_radio_button_new_with_label (GSList      *group,
				 const gchar *label)
{
  GtkWidget *radio_button;

464
  radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, "label", label, NULL) ;
Elliot Lee's avatar
Elliot Lee committed
465

466
  if (group)
Havoc Pennington's avatar
Havoc Pennington committed
467
    gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
Elliot Lee's avatar
Elliot Lee committed
468 469 470 471

  return radio_button;
}

472 473 474

/**
 * gtk_radio_button_new_with_mnemonic:
475
 * @group: (element-type GtkRadioButton) (allow-none): the radio button
476
 *         group, or %NULL
477 478 479
 * @label: the text of the button, with an underscore in front of the
 *         mnemonic character
 *
480 481 482
 * Creates a new #GtkRadioButton containing a label, adding it to the same
 * group as @group. The label will be created using
 * gtk_label_new_with_mnemonic(), so underscores in @label indicate the
483
 * mnemonic for the button.
484
 *
485
 * Returns: a new #GtkRadioButton
486
 */
487 488 489 490 491 492
GtkWidget*
gtk_radio_button_new_with_mnemonic (GSList      *group,
				    const gchar *label)
{
  GtkWidget *radio_button;

493 494 495 496
  radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, 
			       "label", label, 
			       "use-underline", TRUE, 
			       NULL);
497

498
  if (group)
Havoc Pennington's avatar
Havoc Pennington committed
499
    gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
500 501 502 503

  return radio_button;
}

504
/**
505
 * gtk_radio_button_new_from_widget: (constructor)
506
 * @radio_group_member: (allow-none): an existing #GtkRadioButton.
507
 *
508 509 510
 * Creates a new #GtkRadioButton, adding it to the same group as
 * @radio_group_member. As with gtk_radio_button_new(), a widget
 * should be packed into the radio button.
511
 *
512
 * Returns: (transfer none): a new radio button.
513
 */
Elliot Lee's avatar
Elliot Lee committed
514
GtkWidget*
515
gtk_radio_button_new_from_widget (GtkRadioButton *radio_group_member)
Elliot Lee's avatar
Elliot Lee committed
516 517
{
  GSList *l = NULL;
518 519
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
Elliot Lee's avatar
Elliot Lee committed
520 521 522
  return gtk_radio_button_new (l);
}

523
/**
524
 * gtk_radio_button_new_with_label_from_widget: (constructor)
525
 * @radio_group_member: (allow-none): widget to get radio group from or %NULL
526 527
 * @label: a text string to display next to the radio button.
 *
528 529
 * Creates a new #GtkRadioButton with a text label, adding it to
 * the same group as @radio_group_member.
530
 *
531
 * Returns: (transfer none): a new radio button.
532
 */
Elliot Lee's avatar
Elliot Lee committed
533
GtkWidget*
534
gtk_radio_button_new_with_label_from_widget (GtkRadioButton *radio_group_member,
Elliot Lee's avatar
Elliot Lee committed
535 536 537
					     const gchar    *label)
{
  GSList *l = NULL;
538 539
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
Elliot Lee's avatar
Elliot Lee committed
540 541 542
  return gtk_radio_button_new_with_label (l, label);
}

543
/**
544
 * gtk_radio_button_new_with_mnemonic_from_widget: (constructor)
545
 * @radio_group_member: (allow-none): widget to get radio group from or %NULL
546 547 548 549 550 551
 * @label: the text of the button, with an underscore in front of the
 *         mnemonic character
 *
 * Creates a new #GtkRadioButton containing a label. The label
 * will be created using gtk_label_new_with_mnemonic(), so underscores
 * in @label indicate the mnemonic for the button.
552
 *
553
 * Returns: (transfer none): a new #GtkRadioButton
554 555
 **/
GtkWidget*
556
gtk_radio_button_new_with_mnemonic_from_widget (GtkRadioButton *radio_group_member,
557 558 559
					        const gchar    *label)
{
  GSList *l = NULL;
560 561
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
562 563 564
  return gtk_radio_button_new_with_mnemonic (l, label);
}

565 566 567 568 569 570 571

/**
 * gtk_radio_button_get_group:
 * @radio_button: a #GtkRadioButton.
 *
 * Retrieves the group assigned to a radio button.
 *
572
 * Returns: (element-type GtkRadioButton) (transfer none): a linked list
573 574 575 576
 * containing all the radio buttons in the same group
 * as @radio_button. The returned list is owned by the radio button
 * and must not be modified or freed.
 */
Elliot Lee's avatar
Elliot Lee committed
577
GSList*
578
gtk_radio_button_get_group (GtkRadioButton *radio_button)
Elliot Lee's avatar
Elliot Lee committed
579 580 581
{
  g_return_val_if_fail (GTK_IS_RADIO_BUTTON (radio_button), NULL);

582
  return radio_button->priv->group;
Elliot Lee's avatar
Elliot Lee committed
583 584 585 586
}


static void
587
gtk_radio_button_destroy (GtkWidget *widget)
Elliot Lee's avatar
Elliot Lee committed
588
{
589
  GtkWidget *old_group_singleton = NULL;
590
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
591
  GtkRadioButtonPrivate *priv = radio_button->priv;
Elliot Lee's avatar
Elliot Lee committed
592 593
  GtkRadioButton *tmp_button;
  GSList *tmp_list;
594
  gboolean was_in_group;
Elliot Lee's avatar
Elliot Lee committed
595

596 597 598 599 600
  was_in_group = priv->group && priv->group->next;

  priv->group = g_slist_remove (priv->group, radio_button);
  if (priv->group && !priv->group->next)
    old_group_singleton = priv->group->data;
601

602
  tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
603 604 605 606 607 608

  while (tmp_list)
    {
      tmp_button = tmp_list->data;
      tmp_list = tmp_list->next;

609
      tmp_button->priv->group = priv->group;
Elliot Lee's avatar
Elliot Lee committed
610 611
    }

612
  /* this button is no longer in the group */
613
  priv->group = NULL;
614 615 616 617 618

  if (old_group_singleton)
    g_signal_emit (old_group_singleton, group_changed_signal, 0);
  if (was_in_group)
    g_signal_emit (radio_button, group_changed_signal, 0);
619

620
  GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->destroy (widget);
Elliot Lee's avatar
Elliot Lee committed
621 622
}

623 624 625 626 627
static gboolean
gtk_radio_button_focus (GtkWidget         *widget,
			GtkDirectionType   direction)
{
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
628
  GtkRadioButtonPrivate *priv = radio_button->priv;
629
  GSList *tmp_slist;
630 631 632 633

  /* Radio buttons with draw_indicator unset focus "normally", since
   * they look like buttons to the user.
   */
634
  if (!gtk_toggle_button_get_mode (GTK_TOGGLE_BUTTON (widget)))
Matthias Clasen's avatar
Matthias Clasen committed
635
    return GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->focus (widget, direction);
636

637 638
  if (gtk_widget_is_focus (widget))
    {
639 640
      GList *children, *focus_list, *tmp_list;
      GtkWidget *toplevel;
641
      GtkWidget *new_focus = NULL;
642
      GSList *l;
643

644 645 646
      if (direction == GTK_DIR_TAB_FORWARD ||
          direction == GTK_DIR_TAB_BACKWARD)
        return FALSE;
647

648 649 650 651
      toplevel = gtk_widget_get_toplevel (widget);
      children = NULL;
      for (l = priv->group; l; l = l->next)
        children = g_list_prepend (children, l->data);
652

653 654
      focus_list = _gtk_container_focus_sort (GTK_CONTAINER (toplevel), children, direction, widget);
      tmp_list = g_list_find (focus_list, widget);
655 656 657 658

      if (tmp_list)
	{
	  tmp_list = tmp_list->next;
659

660 661 662
	  while (tmp_list)
	    {
	      GtkWidget *child = tmp_list->data;
663

664
	      if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child))
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
		{
		  new_focus = child;
		  break;
		}

	      tmp_list = tmp_list->next;
	    }
	}

      if (!new_focus)
	{
	  tmp_list = focus_list;

	  while (tmp_list)
	    {
	      GtkWidget *child = tmp_list->data;
681

682
	      if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child))
683 684 685 686
		{
		  new_focus = child;
		  break;
		}
687

688 689 690
	      tmp_list = tmp_list->next;
	    }
	}
691 692 693

      g_list_free (focus_list);
      g_list_free (children);
694 695 696 697

      if (new_focus)
	{
	  gtk_widget_grab_focus (new_focus);
698

699
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
700 701 702 703 704 705 706
	}

      return TRUE;
    }
  else
    {
      GtkRadioButton *selected_button = NULL;
707

708 709 710 711
      /* We accept the focus if, we don't have the focus and
       *  - we are the currently active button in the group
       *  - there is no currently active radio button.
       */
712
      tmp_slist = priv->group;
713 714
      while (tmp_slist)
	{
715 716
	  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tmp_slist->data)) &&
	      gtk_widget_get_visible (tmp_slist->data))
717 718 719
	    selected_button = tmp_slist->data;
	  tmp_slist = tmp_slist->next;
	}
720

721 722 723 724 725 726 727 728
      if (selected_button && selected_button != radio_button)
	return FALSE;

      gtk_widget_grab_focus (widget);
      return TRUE;
    }
}

Elliot Lee's avatar
Elliot Lee committed
729 730 731
static void
gtk_radio_button_clicked (GtkButton *button)
{
732
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (button);
733
  GtkRadioButtonPrivate *priv = radio_button->priv;
734
  GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
Elliot Lee's avatar
Elliot Lee committed
735 736 737 738 739 740
  GtkToggleButton *tmp_button;
  GSList *tmp_list;
  gint toggled;

  toggled = FALSE;

Manish Singh's avatar
Manish Singh committed
741
  g_object_ref (GTK_WIDGET (button));
742

743
  if (gtk_toggle_button_get_active (toggle_button))
Elliot Lee's avatar
Elliot Lee committed
744 745
    {
      tmp_button = NULL;
746
      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
747 748 749 750 751 752

      while (tmp_list)
	{
	  tmp_button = tmp_list->data;
	  tmp_list = tmp_list->next;

753 754
          if (tmp_button != toggle_button &&
              gtk_toggle_button_get_active (tmp_button))
Elliot Lee's avatar
Elliot Lee committed
755 756 757 758 759
	    break;

	  tmp_button = NULL;
	}

760
      if (tmp_button)
Elliot Lee's avatar
Elliot Lee committed
761 762
	{
	  toggled = TRUE;
763 764
          _gtk_toggle_button_set_active (toggle_button,
                                         !gtk_toggle_button_get_active (toggle_button));
Elliot Lee's avatar
Elliot Lee committed
765 766 767 768 769
	}
    }
  else
    {
      toggled = TRUE;
770 771
      _gtk_toggle_button_set_active (toggle_button,
                                     !gtk_toggle_button_get_active (toggle_button));
772 773

      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
774 775 776 777 778
      while (tmp_list)
	{
	  tmp_button = tmp_list->data;
	  tmp_list = tmp_list->next;

779
	  if (gtk_toggle_button_get_active (tmp_button) && (tmp_button != toggle_button))
Elliot Lee's avatar
Elliot Lee committed
780 781 782 783 784 785 786 787
	    {
	      gtk_button_clicked (GTK_BUTTON (tmp_button));
	      break;
	    }
	}
    }

  if (toggled)
788 789 790 791 792
    {
      gtk_toggle_button_toggled (toggle_button);

      g_object_notify (G_OBJECT (toggle_button), "active");
    }
793

Elliot Lee's avatar
Elliot Lee committed
794
  gtk_widget_queue_draw (GTK_WIDGET (button));
795

Manish Singh's avatar
Manish Singh committed
796
  g_object_unref (button);
Elliot Lee's avatar
Elliot Lee committed
797
}