gtkradiomenuitem.c 17.2 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"
Tim Janik's avatar
Tim Janik committed
26
#include "gtkaccellabel.h"
27
#include "gtkcheckmenuitemprivate.h"
28
#include "gtkmarshalers.h"
Elliot Lee's avatar
Elliot Lee committed
29
#include "gtkradiomenuitem.h"
30
#include "deprecated/gtkactivatable.h"
31 32
#include "gtkprivate.h"
#include "gtkintl.h"
33
#include "a11y/gtkradiomenuitemaccessible.h"
Elliot Lee's avatar
Elliot Lee committed
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
/**
 * SECTION:gtkradiomenuitem
 * @Short_description: A choice from multiple check menu items
 * @Title: GtkRadioMenuItem
 * @See_also: #GtkMenuItem, #GtkCheckMenuItem
 *
 * A radio menu item is a check menu item that belongs to a group. At each
 * instant exactly one of the radio menu items from a group is selected.
 *
 * The group list does not need to be freed, as each #GtkRadioMenuItem will
 * remove itself and its list item when it is destroyed.
 *
 * The correct way to create a group of radio menu items is approximatively
 * this:
 *
50 51
 * ## How to create a group of radio menu items.
 *
52
 * |[<!-- language="C" -->
53 54 55 56 57 58 59 60 61 62 63
 * GSList *group = NULL;
 * GtkWidget *item;
 * gint i;
 *
 * for (i = 0; i < 5; i++)
 * {
 *   item = gtk_radio_menu_item_new_with_label (group, "This is an example");
 *   group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
 *   if (i == 1)
 *     gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
 * }
64
 * ]|
65 66 67
 *
 * # CSS nodes
 *
68 69
 * |[<!-- language="plain" -->
 * menuitem
70 71
 * ├── radio.left
 * ╰── <child>
72 73
 * ]|
 *
74 75
 * GtkRadioMenuItem has a main CSS node with name menuitem, and a subnode
 * with name radio, which gets the .left or .right style class.
76 77
 */

78
struct _GtkRadioMenuItemPrivate
79 80 81 82
{
  GSList *group;
};

83 84 85 86 87 88
enum {
  PROP_0,
  PROP_GROUP
};


89
static void gtk_radio_menu_item_destroy        (GtkWidget             *widget);
Elliot Lee's avatar
Elliot Lee committed
90
static void gtk_radio_menu_item_activate       (GtkMenuItem           *menu_item);
91 92 93 94 95 96 97 98
static void gtk_radio_menu_item_set_property   (GObject               *object,
						guint                  prop_id,
						const GValue          *value,
						GParamSpec            *pspec);
static void gtk_radio_menu_item_get_property   (GObject               *object,
						guint                  prop_id,
						GValue                *value,
						GParamSpec            *pspec);
Elliot Lee's avatar
Elliot Lee committed
99

100 101
static guint group_changed_signal = 0;

102
G_DEFINE_TYPE_WITH_PRIVATE (GtkRadioMenuItem, gtk_radio_menu_item, GTK_TYPE_CHECK_MENU_ITEM)
Elliot Lee's avatar
Elliot Lee committed
103

104 105
/**
 * gtk_radio_menu_item_new:
106 107
 * @group: (element-type GtkRadioMenuItem) (allow-none): the group to which the
 *   radio menu item is to be attached, or %NULL
108 109 110 111 112
 *
 * Creates a new #GtkRadioMenuItem.
 *
 * Returns: a new #GtkRadioMenuItem
 */
Elliot Lee's avatar
Elliot Lee committed
113 114 115 116 117
GtkWidget*
gtk_radio_menu_item_new (GSList *group)
{
  GtkRadioMenuItem *radio_menu_item;

Manish Singh's avatar
Manish Singh committed
118
  radio_menu_item = g_object_new (GTK_TYPE_RADIO_MENU_ITEM, NULL);
Elliot Lee's avatar
Elliot Lee committed
119

120 121 122 123
  gtk_radio_menu_item_set_group (radio_menu_item, group);

  return GTK_WIDGET (radio_menu_item);
}
Elliot Lee's avatar
Elliot Lee committed
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
static void
gtk_radio_menu_item_set_property (GObject      *object,
				  guint         prop_id,
				  const GValue *value,
				  GParamSpec   *pspec)
{
  GtkRadioMenuItem *radio_menu_item;

  radio_menu_item = GTK_RADIO_MENU_ITEM (object);

  switch (prop_id)
    {
      GSList *slist;

    case PROP_GROUP:
140 141 142
      slist = g_value_get_object (value);
      if (slist)
        slist = gtk_radio_menu_item_get_group ((GtkRadioMenuItem*) g_value_get_object (value));
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
      gtk_radio_menu_item_set_group (radio_menu_item, slist);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_radio_menu_item_get_property (GObject    *object,
				  guint       prop_id,
				  GValue     *value,
				  GParamSpec *pspec)
{
  switch (prop_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

165 166 167
/**
 * gtk_radio_menu_item_set_group:
 * @radio_menu_item: a #GtkRadioMenuItem.
168
 * @group: (element-type GtkRadioMenuItem) (allow-none): the new group, or %NULL.
169 170 171
 *
 * Sets the group of a radio menu item, or changes it.
 */
172 173 174 175
void
gtk_radio_menu_item_set_group (GtkRadioMenuItem *radio_menu_item,
			       GSList           *group)
{
176
  GtkRadioMenuItemPrivate *priv;
177 178
  GtkWidget *old_group_singleton = NULL;
  GtkWidget *new_group_singleton = NULL;
179

180
  g_return_if_fail (GTK_IS_RADIO_MENU_ITEM (radio_menu_item));
181

182 183
  priv = radio_menu_item->priv;

184 185 186
  if (priv->group == group)
    return;

187
  if (priv->group)
Elliot Lee's avatar
Elliot Lee committed
188
    {
189
      GSList *slist;
190

191 192 193 194 195 196
      priv->group = g_slist_remove (priv->group, radio_menu_item);

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

      for (slist = priv->group; slist; slist = slist->next)
Elliot Lee's avatar
Elliot Lee committed
197
	{
198 199 200
	  GtkRadioMenuItem *tmp_item;
	  
	  tmp_item = slist->data;
201 202

	  tmp_item->priv->group = priv->group;
203 204 205
	}
    }
  
206 207
  if (group && !group->next)
    new_group_singleton = g_object_ref (group->data);
208 209 210

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

211 212 213 214 215 216 217 218 219
  if (group)
    {
      GSList *slist;
      
      for (slist = group; slist; slist = slist->next)
	{
	  GtkRadioMenuItem *tmp_item;
	  
	  tmp_item = slist->data;
220 221

	  tmp_item->priv->group = priv->group;
Elliot Lee's avatar
Elliot Lee committed
222
	}
223 224

      _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio_menu_item), FALSE);
Elliot Lee's avatar
Elliot Lee committed
225 226 227
    }
  else
    {
228
      _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (radio_menu_item), TRUE);
229 230
      /* gtk_widget_set_state (GTK_WIDGET (radio_menu_item), GTK_STATE_ACTIVE);
       */
Elliot Lee's avatar
Elliot Lee committed
231
    }
232 233 234

  g_object_ref (radio_menu_item);

235
  g_object_notify (G_OBJECT (radio_menu_item), "group");
236 237 238 239 240 241 242 243 244 245 246 247 248
  g_signal_emit (radio_menu_item, 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);
    }

  g_object_unref (radio_menu_item);
Elliot Lee's avatar
Elliot Lee committed
249 250
}

251 252 253

/**
 * gtk_radio_menu_item_new_with_label:
254 255
 * @group: (element-type GtkRadioMenuItem) (allow-none):
 *         group the radio menu item is inside, or %NULL
256 257 258 259 260 261
 * @label: the text for the label
 *
 * Creates a new #GtkRadioMenuItem whose child is a simple #GtkLabel.
 *
 * Returns: (transfer none): A new #GtkRadioMenuItem
 */
Elliot Lee's avatar
Elliot Lee committed
262 263 264 265
GtkWidget*
gtk_radio_menu_item_new_with_label (GSList *group,
				    const gchar *label)
{
266 267 268 269
  return g_object_new (GTK_TYPE_RADIO_MENU_ITEM,
          "group", (group) ? group->data : NULL,
          "label", label,
          NULL);
Elliot Lee's avatar
Elliot Lee committed
270 271
}

272 273 274

/**
 * gtk_radio_menu_item_new_with_mnemonic:
275 276
 * @group: (element-type GtkRadioMenuItem) (allow-none):
 *         group the radio menu item is inside, or %NULL
277 278 279 280 281 282
 * @label: the text of the button, with an underscore in front of the
 *         mnemonic character
 *
 * Creates a new #GtkRadioMenuItem containing a label. The label
 * will be created using gtk_label_new_with_mnemonic(), so underscores
 * in @label indicate the mnemonic for the menu item.
Matthias Clasen's avatar
Matthias Clasen committed
283 284 285
 *
 * Returns: a new #GtkRadioMenuItem
 */
286 287 288 289
GtkWidget*
gtk_radio_menu_item_new_with_mnemonic (GSList *group,
				       const gchar *label)
{
290 291 292 293 294
  return g_object_new (GTK_TYPE_RADIO_MENU_ITEM,
          "group", (group) ? group->data : NULL,
          "label", label,
          "use-underline", TRUE,
          NULL);
295 296
}

297
/**
298
 * gtk_radio_menu_item_new_from_widget: (constructor)
299
 * @group: (allow-none): An existing #GtkRadioMenuItem
300
 *
301
 * Creates a new #GtkRadioMenuItem adding it to the same group as @group.
302
 *
303
 * Returns: (transfer none): The new #GtkRadioMenuItem
304
 *
305 306 307 308 309 310 311
 * Since: 2.4
 **/
GtkWidget *
gtk_radio_menu_item_new_from_widget (GtkRadioMenuItem *group)
{
  GSList *list = NULL;
  
312
  g_return_val_if_fail (group == NULL || GTK_IS_RADIO_MENU_ITEM (group), NULL);
313 314 315 316 317 318 319 320

  if (group)
    list = gtk_radio_menu_item_get_group (group);
  
  return gtk_radio_menu_item_new (list);
}

/**
321
 * gtk_radio_menu_item_new_with_mnemonic_from_widget: (constructor)
322 323
 * @group: (allow-none): An existing #GtkRadioMenuItem
 * @label: (allow-none): the text of the button, with an underscore in front of the
324 325 326 327 328 329 330 331
 *         mnemonic character
 *
 * Creates a new GtkRadioMenuItem containing a label. The label will be
 * created using gtk_label_new_with_mnemonic(), so underscores in label
 * indicate the mnemonic for the menu item.
 *
 * The new #GtkRadioMenuItem is added to the same group as @group.
 *
332
 * Returns: (transfer none): The new #GtkRadioMenuItem
333
 *
334 335 336 337 338 339 340 341
 * Since: 2.4
 **/
GtkWidget *
gtk_radio_menu_item_new_with_mnemonic_from_widget (GtkRadioMenuItem *group,
						   const gchar *label)
{
  GSList *list = NULL;

342
  g_return_val_if_fail (group == NULL || GTK_IS_RADIO_MENU_ITEM (group), NULL);
343 344 345 346 347 348 349 350

  if (group)
    list = gtk_radio_menu_item_get_group (group);

  return gtk_radio_menu_item_new_with_mnemonic (list, label);
}

/**
351
 * gtk_radio_menu_item_new_with_label_from_widget: (constructor)
352 353
 * @group: (allow-none): an existing #GtkRadioMenuItem
 * @label: (allow-none): the text for the label
354 355 356 357
 *
 * Creates a new GtkRadioMenuItem whose child is a simple GtkLabel.
 * The new #GtkRadioMenuItem is added to the same group as @group.
 *
358
 * Returns: (transfer none): The new #GtkRadioMenuItem
359
 *
360 361 362 363 364 365 366 367
 * Since: 2.4
 **/
GtkWidget *
gtk_radio_menu_item_new_with_label_from_widget (GtkRadioMenuItem *group,
						const gchar *label)
{
  GSList *list = NULL;

368
  g_return_val_if_fail (group == NULL || GTK_IS_RADIO_MENU_ITEM (group), NULL);
369 370 371 372 373 374 375

  if (group)
    list = gtk_radio_menu_item_get_group (group);

  return gtk_radio_menu_item_new_with_label (list, label);
}

376 377 378 379 380 381 382
/**
 * gtk_radio_menu_item_get_group:
 * @radio_menu_item: a #GtkRadioMenuItem
 *
 * Returns the group to which the radio menu item belongs, as a #GList of
 * #GtkRadioMenuItem. The list belongs to GTK+ and should not be freed.
 *
383 384
 * Returns: (element-type GtkRadioMenuItem) (transfer none): the group
 *     of @radio_menu_item
385
 */
Elliot Lee's avatar
Elliot Lee committed
386
GSList*
Owen Taylor's avatar
Owen Taylor committed
387
gtk_radio_menu_item_get_group (GtkRadioMenuItem *radio_menu_item)
Elliot Lee's avatar
Elliot Lee committed
388 389 390
{
  g_return_val_if_fail (GTK_IS_RADIO_MENU_ITEM (radio_menu_item), NULL);

391
  return radio_menu_item->priv->group;
Elliot Lee's avatar
Elliot Lee committed
392 393 394 395 396
}

static void
gtk_radio_menu_item_class_init (GtkRadioMenuItemClass *klass)
{
397
  GObjectClass *gobject_class;
398
  GtkWidgetClass *widget_class;
Elliot Lee's avatar
Elliot Lee committed
399 400
  GtkMenuItemClass *menu_item_class;

401
  gobject_class = G_OBJECT_CLASS (klass);
402
  widget_class = GTK_WIDGET_CLASS (klass);
403
  menu_item_class = GTK_MENU_ITEM_CLASS (klass);
Elliot Lee's avatar
Elliot Lee committed
404

405 406 407
  gobject_class->set_property = gtk_radio_menu_item_set_property;
  gobject_class->get_property = gtk_radio_menu_item_get_property;

408
  widget_class->destroy = gtk_radio_menu_item_destroy;
409

410
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_RADIO_MENU_ITEM_ACCESSIBLE);
411 412 413

  menu_item_class->activate = gtk_radio_menu_item_activate;

Matthias Clasen's avatar
Matthias Clasen committed
414 415 416 417 418 419 420
  /**
   * GtkRadioMenuItem:group:
   * 
   * The radio menu item whose group this widget belongs to.
   * 
   * Since: 2.8
   */
421 422 423 424
  g_object_class_install_property (gobject_class,
				   PROP_GROUP,
				   g_param_spec_object ("group",
							P_("Group"),
Matthias Clasen's avatar
Matthias Clasen committed
425
							P_("The radio menu item whose group this widget belongs to."),
426 427 428
							GTK_TYPE_RADIO_MENU_ITEM,
							GTK_PARAM_WRITABLE));

429 430 431 432 433 434 435
  /**
   * GtkStyle::group-changed:
   * @style: the object which received the signal
   *
   * Emitted when the group of radio menu items that a radio menu item belongs
   * to changes. This is emitted when a radio menu item switches from
   * being alone to being part of a group of 2 or more menu items, or
Matthias Clasen's avatar
Matthias Clasen committed
436
   * vice-versa, and when a button is moved from one group of 2 or
437
   * more menu items ton a different one, but not when the composition
438
   * of the group that a menu item belongs to changes.
439 440
   *
   * Since: 2.4
441
   */
442
  group_changed_signal = g_signal_new (I_("group-changed"),
443
				       G_OBJECT_CLASS_TYPE (gobject_class),
444 445 446
				       G_SIGNAL_RUN_FIRST,
				       G_STRUCT_OFFSET (GtkRadioMenuItemClass, group_changed),
				       NULL, NULL,
447
				       NULL,
448
				       G_TYPE_NONE, 0);
Elliot Lee's avatar
Elliot Lee committed
449 450 451 452 453
}

static void
gtk_radio_menu_item_init (GtkRadioMenuItem *radio_menu_item)
{
454
  GtkRadioMenuItemPrivate *priv;
455

456
  radio_menu_item->priv = gtk_radio_menu_item_get_instance_private (radio_menu_item);
457 458 459
  priv = radio_menu_item->priv;

  priv->group = g_slist_prepend (NULL, radio_menu_item);
460
  gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (radio_menu_item), TRUE);
Elliot Lee's avatar
Elliot Lee committed
461 462
}

463
static void
464
gtk_radio_menu_item_destroy (GtkWidget *widget)
465
{
466
  GtkRadioMenuItem *radio_menu_item = GTK_RADIO_MENU_ITEM (widget);
467
  GtkRadioMenuItemPrivate *priv = radio_menu_item->priv;
468
  GtkWidget *old_group_singleton = NULL;
469 470
  GtkRadioMenuItem *tmp_menu_item;
  GSList *tmp_list;
471
  gboolean was_in_group;
472

473 474 475 476 477
  was_in_group = priv->group && priv->group->next;

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

479
  tmp_list = priv->group;
480 481 482 483 484 485

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

486
      tmp_menu_item->priv->group = priv->group;
487 488
    }

489
  /* this radio menu item is no longer in the group */
490
  priv->group = NULL;
491
  
492 493 494 495
  if (old_group_singleton)
    g_signal_emit (old_group_singleton, group_changed_signal, 0);
  if (was_in_group)
    g_signal_emit (radio_menu_item, group_changed_signal, 0);
496

497
  GTK_WIDGET_CLASS (gtk_radio_menu_item_parent_class)->destroy (widget);
498 499
}

Elliot Lee's avatar
Elliot Lee committed
500 501 502
static void
gtk_radio_menu_item_activate (GtkMenuItem *menu_item)
{
503
  GtkRadioMenuItem *radio_menu_item = GTK_RADIO_MENU_ITEM (menu_item);
504
  GtkRadioMenuItemPrivate *priv = radio_menu_item->priv;
505
  GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM (menu_item);
Elliot Lee's avatar
Elliot Lee committed
506
  GtkCheckMenuItem *tmp_menu_item;
507
  GtkAction        *action;
Elliot Lee's avatar
Elliot Lee committed
508
  GSList *tmp_list;
509
  gboolean active;
Elliot Lee's avatar
Elliot Lee committed
510 511
  gint toggled;

512 513
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;

514 515 516 517
  action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (menu_item));
  if (action && gtk_menu_item_get_submenu (menu_item) == NULL)
    gtk_action_activate (action);

518 519
  G_GNUC_END_IGNORE_DEPRECATIONS;

Elliot Lee's avatar
Elliot Lee committed
520 521
  toggled = FALSE;

522 523
  active = gtk_check_menu_item_get_active (check_menu_item);
  if (active)
Elliot Lee's avatar
Elliot Lee committed
524 525
    {
      tmp_menu_item = NULL;
526
      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
527 528 529 530 531 532

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

533 534
          if (gtk_check_menu_item_get_active (tmp_menu_item) &&
              tmp_menu_item != check_menu_item)
Elliot Lee's avatar
Elliot Lee committed
535 536 537 538 539 540 541 542
	    break;

	  tmp_menu_item = NULL;
	}

      if (tmp_menu_item)
	{
	  toggled = TRUE;
543
          _gtk_check_menu_item_set_active (check_menu_item, !active);
Elliot Lee's avatar
Elliot Lee committed
544 545 546 547 548
	}
    }
  else
    {
      toggled = TRUE;
549
      _gtk_check_menu_item_set_active (check_menu_item, !active);
Elliot Lee's avatar
Elliot Lee committed
550

551
      tmp_list = priv->group;
Elliot Lee's avatar
Elliot Lee committed
552 553 554 555 556
      while (tmp_list)
	{
	  tmp_menu_item = tmp_list->data;
	  tmp_list = tmp_list->next;

557 558
          if (gtk_check_menu_item_get_active (tmp_menu_item) &&
              tmp_menu_item != check_menu_item)
Elliot Lee's avatar
Elliot Lee committed
559
	    {
560
              gtk_menu_item_activate (GTK_MENU_ITEM (tmp_menu_item));
Elliot Lee's avatar
Elliot Lee committed
561 562 563 564 565 566
	      break;
	    }
	}
    }

  if (toggled)
567 568 569
    {
      gtk_check_menu_item_toggled (check_menu_item);
    }
570

571
  gtk_widget_queue_draw (GTK_WIDGET (radio_menu_item));
Elliot Lee's avatar
Elliot Lee committed
572
}
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628

/**
 * gtk_radio_menu_item_join_group:
 * @radio_menu_item: a #GtkRadioMenuItem
 * @group_source: (allow-none): a #GtkRadioMenuItem whose group we are
 *   joining, or %NULL to remove the @radio_menu_item from its current
 *   group
 *
 * Joins a #GtkRadioMenuItem object to the group of another #GtkRadioMenuItem
 * object.
 *
 * This function should be used by language bindings to avoid the memory
 * manangement of the opaque #GSList of gtk_radio_menu_item_get_group()
 * and gtk_radio_menu_item_set_group().
 *
 * A common way to set up a group of #GtkRadioMenuItem instances is:
 *
 * |[
 *   GtkRadioMenuItem *last_item = NULL;
 *
 *   while ( ...more items to add... )
 *     {
 *       GtkRadioMenuItem *radio_item;
 *
 *       radio_item = gtk_radio_menu_item_new (...);
 *
 *       gtk_radio_menu_item_join_group (radio_item, last_item);
 *       last_item = radio_item;
 *     }
 * ]|
 *
 * Since: 3.18
 */
void
gtk_radio_menu_item_join_group (GtkRadioMenuItem *radio_menu_item,
                                GtkRadioMenuItem *group_source)
{
  g_return_if_fail (GTK_IS_RADIO_MENU_ITEM (radio_menu_item));
  g_return_if_fail (group_source == NULL || GTK_IS_RADIO_MENU_ITEM (group_source));

  if (group_source != NULL)
    {
      GSList *group = gtk_radio_menu_item_get_group (group_source);

      if (group == NULL)
        {
          /* if the group source does not have a group, we force one */
          gtk_radio_menu_item_set_group (group_source, NULL);
          group = gtk_radio_menu_item_get_group (group_source);
        }

      gtk_radio_menu_item_set_group (radio_menu_item, group);
    }
  else
    gtk_radio_menu_item_set_group (radio_menu_item, NULL);
}