gtkmodelmenuitem.c 13.4 KB
Newer Older
1
/*
2
 * Copyright © 2011, 2013 Canonical Limited
3 4 5 6 7 8 9 10 11 12 13 14
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, 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
 * Lesser General Public License for more details.
 *
 * 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/>.
16 17 18 19 20 21 22 23
 *
 * Author: Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

#include "gtkmodelmenuitem.h"

24
#include "gtkaccellabel.h"
25 26
#include "gtkimage.h"
#include "gtkbox.h"
27

28 29 30
struct _GtkModelMenuItem
{
  GtkCheckMenuItem parent_instance;
31
  GtkMenuTrackerItemRole role;
32
  gboolean has_indicator;
33 34 35 36
};

typedef GtkCheckMenuItemClass GtkModelMenuItemClass;

37
G_DEFINE_TYPE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM)
38

39 40 41 42 43 44 45 46 47
enum
{
  PROP_0,
  PROP_ACTION_ROLE,
  PROP_ICON,
  PROP_TEXT,
  PROP_TOGGLED,
  PROP_ACCEL
};
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62
static void
gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item,
                                         gint        *requisition)
{
  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (menu_item);

  if (item->has_indicator)
    GTK_MENU_ITEM_CLASS (gtk_model_menu_item_parent_class)
      ->toggle_size_request (menu_item, requisition);

  else
    *requisition = 0;
}

63 64 65 66 67 68
static void
gtk_model_menu_item_activate (GtkMenuItem *item)
{
  /* block the automatic toggle behaviour -- just do nothing */
}

69 70 71 72 73 74 75 76 77 78 79
static void
gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item,
                                    cairo_t          *cr)
{
  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (check_item);

  if (item->has_indicator)
    GTK_CHECK_MENU_ITEM_CLASS (gtk_model_menu_item_parent_class)
      ->draw_indicator (check_item, cr);
}

80
static void
81 82
gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
                                       gboolean          has_indicator)
83
{
84 85
  if (has_indicator == item->has_indicator)
    return;
86

87
  item->has_indicator = has_indicator;
88

89
  gtk_widget_queue_resize (GTK_WIDGET (item));
90 91 92
}

static void
93 94
gtk_model_menu_item_set_action_role (GtkModelMenuItem       *item,
                                     GtkMenuTrackerItemRole  role)
95
{
96 97
  AtkObject *accessible;
  AtkRole a11y_role;
98

99 100
  if (role == item->role)
    return;
101

102 103
  gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == GTK_MENU_TRACKER_ITEM_ROLE_RADIO);
  gtk_model_menu_item_set_has_indicator (item, role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL);
104

105 106
  accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
  switch (role)
107
    {
108 109 110
    case GTK_MENU_TRACKER_ITEM_ROLE_NORMAL:
      a11y_role = ATK_ROLE_MENU_ITEM;
      break;
111

112 113 114
    case GTK_MENU_TRACKER_ITEM_ROLE_CHECK:
      a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
      break;
115

116 117 118
    case GTK_MENU_TRACKER_ITEM_ROLE_RADIO:
      a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
      break;
119

120 121 122
    default:
      g_assert_not_reached ();
    }
123

124
  atk_object_set_role (accessible, a11y_role);
125
  g_object_notify (G_OBJECT (item), "action-role");
126
}
127

128 129 130 131 132
static void
gtk_model_menu_item_set_icon (GtkModelMenuItem *item,
                              GIcon            *icon)
{
  GtkWidget *child;
133

134 135
  g_return_if_fail (GTK_IS_MODEL_MENU_ITEM (item));
  g_return_if_fail (icon == NULL || G_IS_ICON (icon));
136

137
  child = gtk_bin_get_child (GTK_BIN (item));
138

139 140 141 142 143 144 145 146 147 148 149 150 151 152
  /* There are only three possibilities here:
   *
   *   - no child
   *   - accel label child
   *   - already a box
   *
   * Handle the no-child case by having GtkMenuItem create the accel
   * label, then we will only have two possible cases.
   */
  if (child == NULL)
    {
      gtk_menu_item_get_label (GTK_MENU_ITEM (item));
      child = gtk_bin_get_child (GTK_BIN (item));
      g_assert (GTK_IS_LABEL (child));
153 154
    }

155 156 157
  /* If it is a box, make sure there are no images inside of it already.
   */
  if (GTK_IS_BOX (child))
158
    {
159 160 161 162 163 164 165
      GList *children;

      children = gtk_container_get_children (GTK_CONTAINER (child));
      while (children)
        {
          if (GTK_IS_IMAGE (children->data))
            gtk_widget_destroy (children->data);
166

167 168 169
          children = g_list_delete_link (children, children);
        }
    }
170
  else
171
    {
172
      GtkWidget *box;
173

174 175 176
      if (icon == NULL)
        return;

177
      box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
178

179 180 181 182 183
      /* Reparent the child without destroying it */
      g_object_ref (child);
      gtk_container_remove (GTK_CONTAINER (item), child);
      gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0);
      g_object_unref (child);
184

185 186
      gtk_container_add (GTK_CONTAINER (item), box);
      gtk_widget_show (box);
187

188 189
      /* Now we have a box */
      child = box;
190 191
    }

192 193 194 195 196 197
  g_assert (GTK_IS_BOX (child));

  /* child is now a box containing a label and no image.  Add the icon,
   * if appropriate.
   */
  if (icon != NULL)
198
    {
199
      GtkWidget *image;
200

201 202 203 204
      image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
      gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0);
      gtk_widget_show (image);
    }
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

  g_object_notify (G_OBJECT (item), "icon");
}

static GIcon *
gtk_model_menu_item_get_icon (GtkModelMenuItem *item)
{
  GtkWidget *child;
  GIcon *icon = NULL;

  child = gtk_bin_get_child (GTK_BIN (item));
  if (GTK_IS_BOX (child))
    {
      GList *children, *l;

      children = gtk_container_get_children (GTK_CONTAINER (child));
      for (l = children; l; l = l->next)
        {
          if (GTK_IS_IMAGE (l->data))
            {
              gtk_image_get_gicon (GTK_IMAGE (l->data), &icon, NULL);
              break;
            }
        }
      g_list_free (children);
    }

  return icon;
233
}
234

235 236 237 238 239 240
static void
gtk_model_menu_item_set_text (GtkModelMenuItem *item,
                              const gchar      *text)
{
  GtkWidget *child;
  GList *children;
241

242 243 244 245 246 247 248
  child = gtk_bin_get_child (GTK_BIN (item));
  if (child == NULL)
    {
      gtk_menu_item_get_label (GTK_MENU_ITEM (item));
      child = gtk_bin_get_child (GTK_BIN (item));
      g_assert (GTK_IS_LABEL (child));
    }
249

250 251 252 253 254
  if (GTK_IS_LABEL (child))
    {
      gtk_label_set_text_with_mnemonic (GTK_LABEL (child), text);
      return;
    }
255

256 257
  if (!GTK_IS_CONTAINER (child))
    return;
258

259
  children = gtk_container_get_children (GTK_CONTAINER (child));
260

261 262 263 264
  while (children)
    {
      if (GTK_IS_LABEL (children->data))
        gtk_label_set_label (GTK_LABEL (children->data), text);
265

266 267
      children = g_list_delete_link (children, children);
    }
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

  g_object_notify (G_OBJECT (item), "text");
}

static const gchar *
gtk_model_menu_item_get_text (GtkModelMenuItem *item)
{
  GtkWidget *child;

  child = gtk_bin_get_child (GTK_BIN (item));

  if (GTK_IS_LABEL (child))
    return gtk_label_get_text (GTK_LABEL (child));

  if (GTK_IS_CONTAINER (child))
    {
      GList *children, *l;
      const gchar *text = NULL;

      children = gtk_container_get_children (GTK_CONTAINER (child));
      for (l = children; l; l = l->next)
        {
          if (GTK_IS_LABEL (l->data))
            {
              text = gtk_label_get_text (GTK_LABEL (l->data));
              break;
            }
        }
      g_list_free (children);

      return text;
    }

  return NULL;
302
}
303

304 305 306 307 308 309 310 311
static void
gtk_model_menu_item_set_accel (GtkModelMenuItem *item,
                               const gchar      *accel)
{
  GtkWidget *child;
  GList *children;
  GdkModifierType modifiers;
  guint key;
312

313 314 315 316 317 318 319 320 321 322 323
  if (accel)
    {
      gtk_accelerator_parse (accel, &key, &modifiers);
      if (!key)
        modifiers = 0;
    }
  else
    {
      key = 0;
      modifiers = 0;
    }
324

325 326 327 328 329 330
  child = gtk_bin_get_child (GTK_BIN (item));
  if (child == NULL)
    {
      gtk_menu_item_get_label (GTK_MENU_ITEM (item));
      child = gtk_bin_get_child (GTK_BIN (item));
      g_assert (GTK_IS_LABEL (child));
331 332
    }

333
  if (GTK_IS_ACCEL_LABEL (child))
334 335 336 337
    {
      gtk_accel_label_set_accel (GTK_ACCEL_LABEL (child), key, modifiers);
      return;
    }
338

339
  if (!GTK_IS_CONTAINER (child))
340 341
    return;

342
  children = gtk_container_get_children (GTK_CONTAINER (child));
343

344 345 346 347 348 349 350
  while (children)
    {
      if (GTK_IS_ACCEL_LABEL (children->data))
        gtk_accel_label_set_accel (children->data, key, modifiers);

      children = g_list_delete_link (children, children);
    }
351 352
}

353 354 355 356 357 358 359 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 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 425
static gchar *
gtk_model_menu_item_get_accel (GtkModelMenuItem *item)
{
  GtkWidget *child;
  GtkWidget *accel_label = NULL;

  child = gtk_bin_get_child (GTK_BIN (item));

  if (GTK_IS_ACCEL_LABEL (child))
    accel_label = child;
  else if (GTK_IS_CONTAINER (child))
    {
      GList *children, *l;

      children = gtk_container_get_children (GTK_CONTAINER (child));
      for (l = children; l; l = l->next)
        {
          if (GTK_IS_ACCEL_LABEL (l->data))
            {
              accel_label = GTK_WIDGET (l->data);
              break;
            }
        }
      g_list_free (children);
    }

  if (accel_label)
    {
      guint key;
      GdkModifierType mods;

      gtk_accel_label_get_accel (GTK_ACCEL_LABEL (accel_label), &key, &mods);

      return gtk_accelerator_name (key, mods);
    }

  return NULL;
}

void
gtk_model_menu_item_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object);

  switch (prop_id)
    {
    case PROP_ACTION_ROLE:
      g_value_set_enum (value, item->role);
      break;

    case PROP_ICON:
      g_value_set_object (value, gtk_model_menu_item_get_icon (item));
      break;

    case PROP_TEXT:
      g_value_set_string (value, gtk_model_menu_item_get_text (item));
      break;

    case PROP_TOGGLED:
      g_value_set_boolean (value, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)));
      break;

    case PROP_ACCEL:
      g_value_take_string (value, gtk_model_menu_item_get_accel (item));
      break;

    default:
      g_assert_not_reached ();
    }
}
426
void
427 428 429 430
gtk_model_menu_item_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
431
{
432 433
  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object);

434 435 436 437 438
  switch (prop_id)
    {
    case PROP_ACTION_ROLE:
      gtk_model_menu_item_set_action_role (item, g_value_get_enum (value));
      break;
439

440 441 442
    case PROP_ICON:
      gtk_model_menu_item_set_icon (item, g_value_get_object (value));
      break;
443

444 445
    case PROP_TEXT:
      gtk_model_menu_item_set_text (item, g_value_get_string (value));
446 447
      break;

448 449
    case PROP_TOGGLED:
      _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), g_value_get_boolean (value));
450
      g_object_notify (object, "active");
451 452
      break;

453 454
    case PROP_ACCEL:
      gtk_model_menu_item_set_accel (item, g_value_get_string (value));
455 456 457 458 459
      break;

    default:
      g_assert_not_reached ();
    }
460 461 462
}

static void
463
gtk_model_menu_item_init (GtkModelMenuItem *item)
464 465 466 467 468 469
{
}

static void
gtk_model_menu_item_class_init (GtkModelMenuItemClass *class)
{
470
  GtkCheckMenuItemClass *check_class = GTK_CHECK_MENU_ITEM_CLASS (class);
471 472 473
  GtkMenuItemClass *item_class = GTK_MENU_ITEM_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);

474 475 476
  check_class->draw_indicator = gtk_model_menu_item_draw_indicator;

  item_class->toggle_size_request = gtk_model_menu_item_toggle_size_request;
477
  item_class->activate = gtk_model_menu_item_activate;
478

479
  object_class->get_property = gtk_model_menu_item_get_property;
480 481 482
  object_class->set_property = gtk_model_menu_item_set_property;

  g_object_class_install_property (object_class, PROP_ACTION_ROLE,
483 484 485
                                   g_param_spec_enum ("action-role", "action role", "action role",
                                                      GTK_TYPE_MENU_TRACKER_ITEM_ROLE,
                                                      GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
486
                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
487 488
  g_object_class_install_property (object_class, PROP_ICON,
                                   g_param_spec_object ("icon", "icon", "icon", G_TYPE_ICON,
489
                                                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
490 491
  g_object_class_install_property (object_class, PROP_TEXT,
                                   g_param_spec_string ("text", "text", "text", NULL,
492
                                                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
493 494
  g_object_class_install_property (object_class, PROP_TOGGLED,
                                   g_param_spec_boolean ("toggled", "toggled", "toggled", FALSE,
495
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
496 497
  g_object_class_install_property (object_class, PROP_ACCEL,
                                   g_param_spec_string ("accel", "accel", "accel", NULL,
498
                                                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
499 500

  gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), ATK_ROLE_MENU_ITEM);
501 502
}

503
GtkWidget *
504
gtk_model_menu_item_new (void)
505
{
506
  return g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL);
507
}