gtkradiobutton.c 24.4 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 29

#include "gtkradiobutton.h"

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

37 38 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
/**
 * 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.
 *
72 73
 * ## How to create a group of two radio buttons.
 *
74
 * |[<!-- language="C" -->
75 76 77 78
 * void create_radio_buttons (void) {
 *
 *    GtkWidget *window, *radio1, *radio2, *box, *entry;
 *    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
79 80
 *    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
 *    gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
81
 *
82
 *    // Create a radio button with a GtkEntry widget
83
 *    radio1 = gtk_radio_button_new (NULL);
84
 *    entry = gtk_entry_new ();
85 86 87
 *    gtk_container_add (GTK_CONTAINER (radio1), entry);
 *
 *
88
 *    // Create a radio button with a label
89
 *    radio2 = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (radio1),
90
 *                                                          "I’m the second radio button.");
91
 *
92
 *    // Pack them into a box, then show all the widgets
93 94 95 96 97 98
 *    gtk_box_pack_start (GTK_BOX (box), radio1, TRUE, TRUE, 2);
 *    gtk_box_pack_start (GTK_BOX (box), radio2, TRUE, TRUE, 2);
 *    gtk_container_add (GTK_CONTAINER (window), box);
 *    gtk_widget_show_all (window);
 *    return;
 * }
99
 * ]|
100 101 102 103 104 105 106 107 108
 *
 * 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.
 */


109
struct _GtkRadioButtonPrivate
110 111 112 113
{
  GSList *group;
};

114
enum {
Manish Singh's avatar
Manish Singh committed
115
  PROP_0,
116
  PROP_GROUP
117 118
};

Elliot Lee's avatar
Elliot Lee committed
119

120
static void     gtk_radio_button_destroy        (GtkWidget           *widget);
121 122 123 124
static gboolean gtk_radio_button_focus          (GtkWidget           *widget,
						 GtkDirectionType     direction);
static void     gtk_radio_button_clicked        (GtkButton           *button);
static void     gtk_radio_button_draw_indicator (GtkCheckButton      *check_button,
125
						 cairo_t             *cr);
Manish Singh's avatar
Manish Singh committed
126 127 128 129 130 131 132 133
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
134

135
G_DEFINE_TYPE_WITH_PRIVATE (GtkRadioButton, gtk_radio_button, GTK_TYPE_CHECK_BUTTON)
Elliot Lee's avatar
Elliot Lee committed
136

137
static guint group_changed_signal = 0;
Elliot Lee's avatar
Elliot Lee committed
138 139 140 141

static void
gtk_radio_button_class_init (GtkRadioButtonClass *class)
{
Manish Singh's avatar
Manish Singh committed
142
  GObjectClass *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
143 144
  GtkButtonClass *button_class;
  GtkCheckButtonClass *check_button_class;
145
  GtkWidgetClass *widget_class;
Elliot Lee's avatar
Elliot Lee committed
146

Manish Singh's avatar
Manish Singh committed
147
  gobject_class = G_OBJECT_CLASS (class);
148
  widget_class = (GtkWidgetClass*) class;
Elliot Lee's avatar
Elliot Lee committed
149 150 151
  button_class = (GtkButtonClass*) class;
  check_button_class = (GtkCheckButtonClass*) class;

Manish Singh's avatar
Manish Singh committed
152 153
  gobject_class->set_property = gtk_radio_button_set_property;
  gobject_class->get_property = gtk_radio_button_get_property;
154

155 156 157 158 159
  /**
   * GtkRadioButton:group:
   *
   * Sets a new group for a radio button.
   */
Manish Singh's avatar
Manish Singh committed
160 161 162
  g_object_class_install_property (gobject_class,
				   PROP_GROUP,
				   g_param_spec_object ("group",
163
							P_("Group"),
164
							P_("The radio button whose group this widget belongs to."),
Manish Singh's avatar
Manish Singh committed
165
							GTK_TYPE_RADIO_BUTTON,
166
							GTK_PARAM_WRITABLE));
167
  widget_class->destroy = gtk_radio_button_destroy;
168 169
  widget_class->focus = gtk_radio_button_focus;

Elliot Lee's avatar
Elliot Lee committed
170 171 172
  button_class->clicked = gtk_radio_button_clicked;

  check_button_class->draw_indicator = gtk_radio_button_draw_indicator;
173 174 175 176

  class->group_changed = NULL;

  /**
177
   * GtkRadioButton::group-changed:
178
   * @button: the object which received the signal
179 180 181 182
   *
   * 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
183
   * vice-versa, and when a button is moved from one group of 2 or
184 185
   * more buttons to a different one, but not when the composition
   * of the group that a button belongs to changes.
186 187
   *
   * Since: 2.4
188
   */
189
  group_changed_signal = g_signal_new (I_("group-changed"),
190
				       G_OBJECT_CLASS_TYPE (gobject_class),
191 192 193 194 195
				       G_SIGNAL_RUN_FIRST,
				       G_STRUCT_OFFSET (GtkRadioButtonClass, group_changed),
				       NULL, NULL,
				       _gtk_marshal_VOID__VOID,
				       G_TYPE_NONE, 0);
196

197
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_RADIO_BUTTON_ACCESSIBLE);
Elliot Lee's avatar
Elliot Lee committed
198 199 200 201 202
}

static void
gtk_radio_button_init (GtkRadioButton *radio_button)
{
203
  GtkRadioButtonPrivate *priv;
204

205
  radio_button->priv = gtk_radio_button_get_instance_private (radio_button);
206 207
  priv = radio_button->priv;

208
  gtk_widget_set_receives_default (GTK_WIDGET (radio_button), FALSE);
209

210
  _gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), TRUE);
211

212
  priv->group = g_slist_prepend (NULL, radio_button);
Elliot Lee's avatar
Elliot Lee committed
213 214
}

215
static void
Manish Singh's avatar
Manish Singh committed
216 217 218 219
gtk_radio_button_set_property (GObject      *object,
			       guint         prop_id,
			       const GValue *value,
			       GParamSpec   *pspec)
220
{
221 222 223 224
  GtkRadioButton *radio_button;

  radio_button = GTK_RADIO_BUTTON (object);

Manish Singh's avatar
Manish Singh committed
225
  switch (prop_id)
226 227
    {
      GSList *slist;
228
      GtkRadioButton *button;
229

Manish Singh's avatar
Manish Singh committed
230
    case PROP_GROUP:
231
        button = g_value_get_object (value);
232 233 234

      if (button)
	slist = gtk_radio_button_get_group (button);
235 236 237 238 239
      else
	slist = NULL;
      gtk_radio_button_set_group (radio_button, slist);
      break;
    default:
Manish Singh's avatar
Manish Singh committed
240
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
241 242 243 244 245
      break;
    }
}

static void
Manish Singh's avatar
Manish Singh committed
246 247 248 249
gtk_radio_button_get_property (GObject    *object,
			       guint       prop_id,
			       GValue     *value,
			       GParamSpec *pspec)
250
{
Manish Singh's avatar
Manish Singh committed
251
  switch (prop_id)
252 253
    {
    default:
Manish Singh's avatar
Manish Singh committed
254
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
255 256 257 258
      break;
    }
}

259 260 261
/**
 * gtk_radio_button_set_group:
 * @radio_button: a #GtkRadioButton.
262 263
 * @group: (element-type GtkRadioButton) (allow-none): an existing radio
 *     button group, such as one returned from gtk_radio_button_get_group(), or %NULL.
264
 *
265
 * Sets a #GtkRadioButton’s group. It should be noted that this does not change
266 267 268 269
 * 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.
 */
270 271
void
gtk_radio_button_set_group (GtkRadioButton *radio_button,
272
			    GSList         *group)
Elliot Lee's avatar
Elliot Lee committed
273
{
274
  GtkRadioButtonPrivate *priv;
275 276
  GtkWidget *old_group_singleton = NULL;
  GtkWidget *new_group_singleton = NULL;
277

278
  g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));
279 280 281

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

283 284 285
  priv = radio_button->priv;

  if (priv->group)
286
    {
287
      GSList *slist;
288

289 290 291 292 293 294
      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)
295
	{
296 297 298
	  GtkRadioButton *tmp_button;
	  
	  tmp_button = slist->data;
299 300

	  tmp_button->priv->group = priv->group;
301 302
	}
    }
303
  
304 305
  if (group && !group->next)
    new_group_singleton = g_object_ref (group->data);
306 307 308

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

309
  if (group)
Elliot Lee's avatar
Elliot Lee committed
310
    {
311 312 313
      GSList *slist;
      
      for (slist = group; slist; slist = slist->next)
Elliot Lee's avatar
Elliot Lee committed
314
	{
315 316 317
	  GtkRadioButton *tmp_button;
	  
	  tmp_button = slist->data;
318 319

	  tmp_button->priv->group = priv->group;
Elliot Lee's avatar
Elliot Lee committed
320 321
	}
    }
322

323 324
  g_object_ref (radio_button);
  
325
  g_object_notify (G_OBJECT (radio_button), "group");
326 327 328 329 330 331 332 333 334 335 336 337
  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);
    }

338
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_button), group == NULL);
339 340

  g_object_unref (radio_button);
341 342
}

343 344 345 346 347 348 349 350 351 352 353 354
/**
 * 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:
355
 * |[<!-- language="C" -->
356 357 358
 *   GtkRadioButton *radio_button;
 *   GtkRadioButton *last_button;
 *
359
 *   while ( ...more buttons to add... )
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
 *     {
 *        radio_button = gtk_radio_button_new (...);
 *
 *        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);
    }
}

398 399
/**
 * gtk_radio_button_new:
400 401
 * @group: (element-type GtkRadioButton) (allow-none): an existing
 *         radio button group, or %NULL if you are creating a new group.
402 403 404 405
 *
 * Creates a new #GtkRadioButton. To be of any practical value, a widget should
 * then be packed into the radio button.
 *
406
 * Returns: a new radio button
407
 */
408 409 410 411 412
GtkWidget*
gtk_radio_button_new (GSList *group)
{
  GtkRadioButton *radio_button;

Manish Singh's avatar
Manish Singh committed
413
  radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, NULL);
414

415 416
  if (group)
    gtk_radio_button_set_group (radio_button, group);
Elliot Lee's avatar
Elliot Lee committed
417 418 419 420

  return GTK_WIDGET (radio_button);
}

421 422
/**
 * gtk_radio_button_new_with_label:
423 424
 * @group: (element-type GtkRadioButton) (allow-none): an existing
 *         radio button group, or %NULL if you are creating a new group.
425 426 427 428
 * @label: the text label to display next to the radio button.
 *
 * Creates a new #GtkRadioButton with a text label.
 *
429
 * Returns: a new radio button.
430
 */
Elliot Lee's avatar
Elliot Lee committed
431 432 433 434 435 436
GtkWidget*
gtk_radio_button_new_with_label (GSList      *group,
				 const gchar *label)
{
  GtkWidget *radio_button;

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

439
  if (group)
Havoc Pennington's avatar
Havoc Pennington committed
440
    gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
Elliot Lee's avatar
Elliot Lee committed
441 442 443 444

  return radio_button;
}

445 446 447

/**
 * gtk_radio_button_new_with_mnemonic:
448
 * @group: (element-type GtkRadioButton) (allow-none): the radio button
449
 *         group, or %NULL
450 451 452
 * @label: the text of the button, with an underscore in front of the
 *         mnemonic character
 *
453 454 455
 * 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
456
 * mnemonic for the button.
457
 *
458
 * Returns: a new #GtkRadioButton
459
 */
460 461 462 463 464 465
GtkWidget*
gtk_radio_button_new_with_mnemonic (GSList      *group,
				    const gchar *label)
{
  GtkWidget *radio_button;

466 467 468 469
  radio_button = g_object_new (GTK_TYPE_RADIO_BUTTON, 
			       "label", label, 
			       "use-underline", TRUE, 
			       NULL);
470

471
  if (group)
Havoc Pennington's avatar
Havoc Pennington committed
472
    gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_button), group);
473 474 475 476

  return radio_button;
}

477
/**
478
 * gtk_radio_button_new_from_widget: (constructor)
479
 * @radio_group_member: (allow-none): an existing #GtkRadioButton.
480
 *
481 482 483
 * 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.
484
 *
485
 * Returns: (transfer none): a new radio button.
486
 */
Elliot Lee's avatar
Elliot Lee committed
487
GtkWidget*
488
gtk_radio_button_new_from_widget (GtkRadioButton *radio_group_member)
Elliot Lee's avatar
Elliot Lee committed
489 490
{
  GSList *l = NULL;
491 492
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
Elliot Lee's avatar
Elliot Lee committed
493 494 495
  return gtk_radio_button_new (l);
}

496
/**
497
 * gtk_radio_button_new_with_label_from_widget: (constructor)
498
 * @radio_group_member: (allow-none): widget to get radio group from or %NULL
499 500
 * @label: a text string to display next to the radio button.
 *
501 502
 * Creates a new #GtkRadioButton with a text label, adding it to
 * the same group as @radio_group_member.
503
 *
504
 * Returns: (transfer none): a new radio button.
505
 */
Elliot Lee's avatar
Elliot Lee committed
506
GtkWidget*
507
gtk_radio_button_new_with_label_from_widget (GtkRadioButton *radio_group_member,
Elliot Lee's avatar
Elliot Lee committed
508 509 510
					     const gchar    *label)
{
  GSList *l = NULL;
511 512
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
Elliot Lee's avatar
Elliot Lee committed
513 514 515
  return gtk_radio_button_new_with_label (l, label);
}

516
/**
517
 * gtk_radio_button_new_with_mnemonic_from_widget: (constructor)
518
 * @radio_group_member: (allow-none): widget to get radio group from or %NULL
519 520 521 522 523 524
 * @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.
525
 *
526
 * Returns: (transfer none): a new #GtkRadioButton
527 528
 **/
GtkWidget*
529
gtk_radio_button_new_with_mnemonic_from_widget (GtkRadioButton *radio_group_member,
530 531 532
					        const gchar    *label)
{
  GSList *l = NULL;
533 534
  if (radio_group_member)
    l = gtk_radio_button_get_group (radio_group_member);
535 536 537
  return gtk_radio_button_new_with_mnemonic (l, label);
}

538 539 540 541 542 543 544

/**
 * gtk_radio_button_get_group:
 * @radio_button: a #GtkRadioButton.
 *
 * Retrieves the group assigned to a radio button.
 *
545
 * Returns: (element-type GtkRadioButton) (transfer none): a linked list
546 547 548 549
 * 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
550
GSList*
551
gtk_radio_button_get_group (GtkRadioButton *radio_button)
Elliot Lee's avatar
Elliot Lee committed
552 553 554
{
  g_return_val_if_fail (GTK_IS_RADIO_BUTTON (radio_button), NULL);

555
  return radio_button->priv->group;
Elliot Lee's avatar
Elliot Lee committed
556 557 558 559
}


static void
560
gtk_radio_button_destroy (GtkWidget *widget)
Elliot Lee's avatar
Elliot Lee committed
561
{
562
  GtkWidget *old_group_singleton = NULL;
563
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
564
  GtkRadioButtonPrivate *priv = radio_button->priv;
Elliot Lee's avatar
Elliot Lee committed
565 566
  GtkRadioButton *tmp_button;
  GSList *tmp_list;
567
  gboolean was_in_group;
Elliot Lee's avatar
Elliot Lee committed
568

569 570 571 572 573
  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;
574

575
  tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
576 577 578 579 580 581

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

582
      tmp_button->priv->group = priv->group;
Elliot Lee's avatar
Elliot Lee committed
583 584
    }

585
  /* this button is no longer in the group */
586
  priv->group = NULL;
587 588 589 590 591

  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);
592

593
  GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->destroy (widget);
Elliot Lee's avatar
Elliot Lee committed
594 595
}

596 597 598 599 600 601
static void
get_coordinates (GtkWidget    *widget,
		 GtkWidget    *reference,
		 gint         *x,
		 gint         *y)
{
602 603 604 605 606 607
  GtkAllocation allocation;

  gtk_widget_get_allocation (widget, &allocation);
  *x = allocation.x + allocation.width / 2;
  *y = allocation.y + allocation.height / 2;

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
  gtk_widget_translate_coordinates (widget, reference, *x, *y, x, y);
}

static gint
left_right_compare (gconstpointer a,
		    gconstpointer b,
		    gpointer      data)
{
  gint x1, y1, x2, y2;

  get_coordinates ((GtkWidget *)a, data, &x1, &y1);
  get_coordinates ((GtkWidget *)b, data, &x2, &y2);

  if (y1 == y2)
    return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
  else
    return (y1 < y2) ? -1 : 1;
}

static gint
up_down_compare (gconstpointer a,
		 gconstpointer b,
		 gpointer      data)
{
  gint x1, y1, x2, y2;
  
  get_coordinates ((GtkWidget *)a, data, &x1, &y1);
  get_coordinates ((GtkWidget *)b, data, &x2, &y2);
  
  if (x1 == x2)
    return (y1 < y2) ? -1 : ((y1 == y2) ? 0 : 1);
  else
    return (x1 < x2) ? -1 : 1;
}

static gboolean
gtk_radio_button_focus (GtkWidget         *widget,
			GtkDirectionType   direction)
{
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
648
  GtkRadioButtonPrivate *priv = radio_button->priv;
649
  GSList *tmp_slist;
650 651 652 653

  /* Radio buttons with draw_indicator unset focus "normally", since
   * they look like buttons to the user.
   */
654
  if (!gtk_toggle_button_get_mode (GTK_TOGGLE_BUTTON (widget)))
Matthias Clasen's avatar
Matthias Clasen committed
655
    return GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->focus (widget, direction);
656 657 658 659 660 661 662 663 664 665 666
  
  if (gtk_widget_is_focus (widget))
    {
      GSList *focus_list, *tmp_list;
      GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
      GtkWidget *new_focus = NULL;

      switch (direction)
	{
	case GTK_DIR_LEFT:
	case GTK_DIR_RIGHT:
667
	  focus_list = g_slist_copy (priv->group);
668 669 670 671
	  focus_list = g_slist_sort_with_data (focus_list, left_right_compare, toplevel);
	  break;
	case GTK_DIR_UP:
	case GTK_DIR_DOWN:
672
	  focus_list = g_slist_copy (priv->group);
673 674
	  focus_list = g_slist_sort_with_data (focus_list, up_down_compare, toplevel);
	  break;
675 676 677 678 679
	case GTK_DIR_TAB_FORWARD:
	case GTK_DIR_TAB_BACKWARD:
          /* fall through */
        default:
	  return FALSE;
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
	}

      if (direction == GTK_DIR_LEFT || direction == GTK_DIR_UP)
	focus_list = g_slist_reverse (focus_list);

      tmp_list = g_slist_find (focus_list, widget);

      if (tmp_list)
	{
	  tmp_list = tmp_list->next;
	  
	  while (tmp_list)
	    {
	      GtkWidget *child = tmp_list->data;
	      
695
	      if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child))
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
		{
		  new_focus = child;
		  break;
		}

	      tmp_list = tmp_list->next;
	    }
	}

      if (!new_focus)
	{
	  tmp_list = focus_list;

	  while (tmp_list)
	    {
	      GtkWidget *child = tmp_list->data;
	      
713
	      if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child))
714 715 716 717 718 719 720 721 722 723 724 725 726 727
		{
		  new_focus = child;
		  break;
		}
	      
	      tmp_list = tmp_list->next;
	    }
	}
      
      g_slist_free (focus_list);

      if (new_focus)
	{
	  gtk_widget_grab_focus (new_focus);
728

729
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
730 731 732 733 734 735 736 737 738 739 740 741 742
	}

      return TRUE;
    }
  else
    {
      GtkRadioButton *selected_button = NULL;
      
      /* 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.
       */
      
743
      tmp_slist = priv->group;
744 745
      while (tmp_slist)
	{
746
	  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tmp_slist->data)))
747 748 749 750 751 752 753 754 755 756 757 758
	    selected_button = tmp_slist->data;
	  tmp_slist = tmp_slist->next;
	}
      
      if (selected_button && selected_button != radio_button)
	return FALSE;

      gtk_widget_grab_focus (widget);
      return TRUE;
    }
}

Elliot Lee's avatar
Elliot Lee committed
759 760 761
static void
gtk_radio_button_clicked (GtkButton *button)
{
762
  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (button);
763
  GtkRadioButtonPrivate *priv = radio_button->priv;
764
  GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
Elliot Lee's avatar
Elliot Lee committed
765 766 767 768 769 770
  GtkToggleButton *tmp_button;
  GSList *tmp_list;
  gint toggled;

  toggled = FALSE;

Manish Singh's avatar
Manish Singh committed
771
  g_object_ref (GTK_WIDGET (button));
772

773
  if (gtk_toggle_button_get_active (toggle_button))
Elliot Lee's avatar
Elliot Lee committed
774 775
    {
      tmp_button = NULL;
776
      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
777 778 779 780 781 782

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

783 784
          if (tmp_button != toggle_button &&
              gtk_toggle_button_get_active (tmp_button))
Elliot Lee's avatar
Elliot Lee committed
785 786 787 788 789
	    break;

	  tmp_button = NULL;
	}

790
      if (tmp_button)
Elliot Lee's avatar
Elliot Lee committed
791 792
	{
	  toggled = TRUE;
793 794
          _gtk_toggle_button_set_active (toggle_button,
                                         !gtk_toggle_button_get_active (toggle_button));
Elliot Lee's avatar
Elliot Lee committed
795 796 797 798 799
	}
    }
  else
    {
      toggled = TRUE;
800 801
      _gtk_toggle_button_set_active (toggle_button,
                                     !gtk_toggle_button_get_active (toggle_button));
802 803

      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
804 805 806 807 808
      while (tmp_list)
	{
	  tmp_button = tmp_list->data;
	  tmp_list = tmp_list->next;

809
	  if (gtk_toggle_button_get_active (tmp_button) && (tmp_button != toggle_button))
Elliot Lee's avatar
Elliot Lee committed
810 811 812 813 814 815 816 817
	    {
	      gtk_button_clicked (GTK_BUTTON (tmp_button));
	      break;
	    }
	}
    }

  if (toggled)
818 819 820 821 822
    {
      gtk_toggle_button_toggled (toggle_button);

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

Elliot Lee's avatar
Elliot Lee committed
824
  gtk_widget_queue_draw (GTK_WIDGET (button));
825

Manish Singh's avatar
Manish Singh committed
826
  g_object_unref (button);
Elliot Lee's avatar
Elliot Lee committed
827 828 829 830
}

static void
gtk_radio_button_draw_indicator (GtkCheckButton *check_button,
831
				 cairo_t        *cr)
Elliot Lee's avatar
Elliot Lee committed
832
{
833
  GtkAllocation allocation;
Elliot Lee's avatar
Elliot Lee committed
834 835
  GtkWidget *widget;
  GtkButton *button;
836
  GtkStyleContext *context;
Elliot Lee's avatar
Elliot Lee committed
837
  gint x, y;
838
  gint indicator_size, indicator_spacing;
839
  gint baseline;
840
  guint border_width;
Elliot Lee's avatar
Elliot Lee committed
841

842
  widget = GTK_WIDGET (check_button);
843
  button = GTK_BUTTON (check_button);
844
  context = gtk_widget_get_style_context (widget);
845

846 847
  border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
  _gtk_check_button_get_props (check_button, &indicator_size, &indicator_spacing);
848

849
  gtk_widget_get_allocation (widget, &allocation);
850
  baseline = gtk_widget_get_allocated_baseline (widget);
851

852
  x = indicator_spacing + border_width;
853 854 855 856 857
  if (baseline == -1)
    y = (allocation.height - indicator_size) / 2;
  else
    y = CLAMP (baseline - indicator_size * button->priv->baseline_align,
	       0, allocation.height - indicator_size);
858

859
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
860
    x = allocation.width - (indicator_size + x);
861

862
  gtk_style_context_save (context);
863

864 865 866 867
  gtk_render_background (context, cr,
                         border_width, border_width,
                         allocation.width - (2 * border_width),
                         allocation.height - (2 * border_width));
868

869
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_RADIO);
870 871 872

  gtk_render_option (context, cr,
                     x, y, indicator_size, indicator_size);
873

874
  gtk_style_context_restore (context);
Elliot Lee's avatar
Elliot Lee committed
875
}