gtkimagemenuitem.c 37.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * Copyright (C) 2001 Red Hat, Inc.
 *
 * 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 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

27
#include "config.h"
28

29
#include "gtkimagemenuitem.h"
30

31
#include "gtkmenuitemprivate.h"
32
#include "gtkaccellabel.h"
33 34 35
#include "gtkstock.h"
#include "gtkiconfactory.h"
#include "gtkimage.h"
36
#include "gtkmenubar.h"
Matthias Clasen's avatar
Matthias Clasen committed
37 38
#include "gtkcontainer.h"
#include "gtkwindow.h"
39
#include "gtkactivatable.h"
40 41

#include "gtkintl.h"
42
#include "gtkprivate.h"
43

Matthias Clasen's avatar
Matthias Clasen committed
44

45

46
struct _GtkImageMenuItemPrivate
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
{
  GtkWidget     *image;

  gchar         *label;
  guint          use_stock         : 1;
  guint          always_show_image : 1;
};

enum {
  PROP_0,
  PROP_IMAGE,
  PROP_USE_STOCK,
  PROP_ACCEL_GROUP,
  PROP_ALWAYS_SHOW_IMAGE
};

static GtkActivatableIface *parent_activatable_iface;

65
static void gtk_image_menu_item_destroy              (GtkWidget        *widget);
66 67
static void gtk_image_menu_item_get_preferred_width  (GtkWidget        *widget,
                                                      gint             *minimum,
68
                                                      gint             *natural);
69 70
static void gtk_image_menu_item_get_preferred_height (GtkWidget        *widget,
                                                      gint             *minimum,
71
                                                      gint             *natural);
72
static void gtk_image_menu_item_get_preferred_height_for_width (GtkWidget *widget,
73 74 75
                                                                gint       width,
                                                                gint      *minimum,
                                                                gint      *natural);
76 77
static void gtk_image_menu_item_size_allocate        (GtkWidget        *widget,
                                                      GtkAllocation    *allocation);
78
static void gtk_image_menu_item_map                  (GtkWidget        *widget);
79 80 81
static void gtk_image_menu_item_remove               (GtkContainer     *container,
                                                      GtkWidget        *child);
static void gtk_image_menu_item_toggle_size_request  (GtkMenuItem      *menu_item,
82
                                                      gint             *requisition);
83
static void gtk_image_menu_item_set_label            (GtkMenuItem      *menu_item,
84
                                                      const gchar      *label);
85 86 87
static G_CONST_RETURN gchar *gtk_image_menu_item_get_label (GtkMenuItem *menu_item);

static void gtk_image_menu_item_forall               (GtkContainer    *container,
88 89 90
                                                      gboolean         include_internals,
                                                      GtkCallback      callback,
                                                      gpointer         callback_data);
91 92 93

static void gtk_image_menu_item_finalize             (GObject         *object);
static void gtk_image_menu_item_set_property         (GObject         *object,
94 95 96
                                                      guint            prop_id,
                                                      const GValue    *value,
                                                      GParamSpec      *pspec);
97
static void gtk_image_menu_item_get_property         (GObject         *object,
98 99 100
                                                      guint            prop_id,
                                                      GValue          *value,
                                                      GParamSpec      *pspec);
101
static void gtk_image_menu_item_screen_changed       (GtkWidget        *widget,
102
                                                      GdkScreen        *previous_screen);
103 104 105

static void gtk_image_menu_item_recalculate          (GtkImageMenuItem *image_menu_item);

106
static void gtk_image_menu_item_activatable_interface_init (GtkActivatableIface  *iface);
107
static void gtk_image_menu_item_update                     (GtkActivatable       *activatable,
108 109
                                                            GtkAction            *action,
                                                            const gchar          *property_name);
110
static void gtk_image_menu_item_sync_action_properties     (GtkActivatable       *activatable,
111
                                                            GtkAction            *action);
112

113 114

G_DEFINE_TYPE_WITH_CODE (GtkImageMenuItem, gtk_image_menu_item, GTK_TYPE_MENU_ITEM,
115 116
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
                                                gtk_image_menu_item_activatable_interface_init))
117

118

119 120 121
static void
gtk_image_menu_item_class_init (GtkImageMenuItemClass *klass)
{
122 123 124 125 126
  GObjectClass *gobject_class = (GObjectClass*) klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
  GtkMenuItemClass *menu_item_class = (GtkMenuItemClass*) klass;
  GtkContainerClass *container_class = (GtkContainerClass*) klass;

127
  widget_class->destroy = gtk_image_menu_item_destroy;
Matthias Clasen's avatar
Matthias Clasen committed
128
  widget_class->screen_changed = gtk_image_menu_item_screen_changed;
129 130 131
  widget_class->get_preferred_width = gtk_image_menu_item_get_preferred_width;
  widget_class->get_preferred_height = gtk_image_menu_item_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_image_menu_item_get_preferred_height_for_width;
132
  widget_class->size_allocate = gtk_image_menu_item_size_allocate;
133
  widget_class->map = gtk_image_menu_item_map;
134 135 136

  container_class->forall = gtk_image_menu_item_forall;
  container_class->remove = gtk_image_menu_item_remove;
137

138
  menu_item_class->toggle_size_request = gtk_image_menu_item_toggle_size_request;
139 140
  menu_item_class->set_label           = gtk_image_menu_item_set_label;
  menu_item_class->get_label           = gtk_image_menu_item_get_label;
141

142
  gobject_class->finalize     = gtk_image_menu_item_finalize;
143 144
  gobject_class->set_property = gtk_image_menu_item_set_property;
  gobject_class->get_property = gtk_image_menu_item_get_property;
145

146 147 148
  g_object_class_install_property (gobject_class,
                                   PROP_IMAGE,
                                   g_param_spec_object ("image",
149 150
                                                        P_("Image widget"),
                                                        P_("Child widget to appear next to the menu text"),
151
                                                        GTK_TYPE_WIDGET,
152
                                                        GTK_PARAM_READWRITE));
153 154 155 156 157 158 159
  /**
   * GtkImageMenuItem:use-stock:
   *
   * If %TRUE, the label set in the menuitem is used as a
   * stock id to select the stock item for the item.
   *
   * Since: 2.16
160
   */
161 162 163
  g_object_class_install_property (gobject_class,
                                   PROP_USE_STOCK,
                                   g_param_spec_boolean ("use-stock",
164 165 166 167
                                                         P_("Use stock"),
                                                         P_("Whether to use the label text to create a stock menu item"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
168

169 170 171
  /**
   * GtkImageMenuItem:always-show-image:
   *
172
   * If %TRUE, the menu item will ignore the #GtkSettings:gtk-menu-images
173 174 175
   * setting and always show the image, if available.
   *
   * Use this property if the menuitem would be useless or hard to use
176
   * without the image.
177 178
   *
   * Since: 2.16
179
   */
180 181 182
  g_object_class_install_property (gobject_class,
                                   PROP_ALWAYS_SHOW_IMAGE,
                                   g_param_spec_boolean ("always-show-image",
183 184 185 186
                                                         P_("Always show image"),
                                                         P_("Whether the image will always be shown"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
187

188 189 190 191 192 193
  /**
   * GtkImageMenuItem:accel-group:
   *
   * The Accel Group to use for stock accelerator keys
   *
   * Since: 2.16
194
   */
195 196 197
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_GROUP,
                                   g_param_spec_object ("accel-group",
198 199 200 201
                                                        P_("Accel Group"),
                                                        P_("The Accel Group to use for stock accelerator keys"),
                                                        GTK_TYPE_ACCEL_GROUP,
                                                        GTK_PARAM_WRITABLE));
Matthias Clasen's avatar
Matthias Clasen committed
202

203
    g_type_class_add_private (klass, sizeof (GtkImageMenuItemPrivate));
204 205 206 207 208
}

static void
gtk_image_menu_item_init (GtkImageMenuItem *image_menu_item)
{
209
  GtkImageMenuItemPrivate *priv;
210

211 212
  image_menu_item->priv = G_TYPE_INSTANCE_GET_PRIVATE (image_menu_item,
                                                       GTK_TYPE_IMAGE_MENU_ITEM,
213
                                                       GtkImageMenuItemPrivate);
214
  priv = image_menu_item->priv;
215

216 217 218
  priv->image = NULL;
  priv->use_stock = FALSE;
  priv->label  = NULL;
219 220
}

221
static void
222 223
gtk_image_menu_item_finalize (GObject *object)
{
224
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (object)->priv;
225 226 227 228 229 230 231

  g_free (priv->label);
  priv->label  = NULL;

  G_OBJECT_CLASS (gtk_image_menu_item_parent_class)->finalize (object);
}

232
static void
233 234 235
gtk_image_menu_item_set_property (GObject         *object,
                                  guint            prop_id,
                                  const GValue    *value,
Tim Janik's avatar
Tim Janik committed
236
                                  GParamSpec      *pspec)
237
{
238
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
239

240 241 242
  switch (prop_id)
    {
    case PROP_IMAGE:
243 244 245 246 247
      gtk_image_menu_item_set_image (image_menu_item, (GtkWidget *) g_value_get_object (value));
      break;
    case PROP_USE_STOCK:
      gtk_image_menu_item_set_use_stock (image_menu_item, g_value_get_boolean (value));
      break;
248 249 250
    case PROP_ALWAYS_SHOW_IMAGE:
      gtk_image_menu_item_set_always_show_image (image_menu_item, g_value_get_boolean (value));
      break;
251 252
    case PROP_ACCEL_GROUP:
      gtk_image_menu_item_set_accel_group (image_menu_item, g_value_get_object (value));
253 254 255 256 257 258
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}
259

260 261 262 263
static void
gtk_image_menu_item_get_property (GObject         *object,
                                  guint            prop_id,
                                  GValue          *value,
Tim Janik's avatar
Tim Janik committed
264
                                  GParamSpec      *pspec)
265 266
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
267

268 269 270
  switch (prop_id)
    {
    case PROP_IMAGE:
271 272 273
      g_value_set_object (value, gtk_image_menu_item_get_image (image_menu_item));
      break;
    case PROP_USE_STOCK:
274
      g_value_set_boolean (value, gtk_image_menu_item_get_use_stock (image_menu_item));
275
      break;
276 277 278
    case PROP_ALWAYS_SHOW_IMAGE:
      g_value_set_boolean (value, gtk_image_menu_item_get_always_show_image (image_menu_item));
      break;
279 280 281 282
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
283 284
}

Matthias Clasen's avatar
Matthias Clasen committed
285 286 287
static gboolean
show_image (GtkImageMenuItem *image_menu_item)
{
288
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
Matthias Clasen's avatar
Matthias Clasen committed
289 290 291
  GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (image_menu_item));
  gboolean show;

292 293 294 295
  if (priv->always_show_image)
    show = TRUE;
  else
    g_object_get (settings, "gtk-menu-images", &show, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
296 297 298

  return show;
}
299

300 301 302 303
static void
gtk_image_menu_item_map (GtkWidget *widget)
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
304
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
305 306 307

  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->map (widget);

308 309
  if (priv->image)
    g_object_set (priv->image,
310 311 312 313
                  "visible", show_image (image_menu_item),
                  NULL);
}

314
static void
315
gtk_image_menu_item_destroy (GtkWidget *widget)
316
{
317
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
318
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
319

320
  if (priv->image)
321
    gtk_container_remove (GTK_CONTAINER (image_menu_item),
322
                          priv->image);
323

324
  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->destroy (widget);
325 326
}

327 328
static void
gtk_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
329
                                         gint        *requisition)
330
{
331
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (menu_item);
332
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
333
  GtkPackDirection pack_dir;
334 335
  GtkWidget *parent;
  GtkWidget *widget = GTK_WIDGET (menu_item);
336

337 338 339 340
  parent = gtk_widget_get_parent (widget);

  if (GTK_IS_MENU_BAR (parent))
    pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
341 342
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;
343

344 345
  *requisition = 0;

346
  if (priv->image && gtk_widget_get_visible (priv->image))
347
    {
348
      GtkRequisition image_requisition;
349
      guint toggle_spacing;
350

351
      gtk_widget_get_preferred_size (priv->image, &image_requisition, NULL);
352

353
      gtk_widget_style_get (GTK_WIDGET (menu_item),
354 355 356
                            "toggle-spacing", &toggle_spacing,
                            NULL);

357
      if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
358 359 360 361
        {
          if (image_requisition.width > 0)
            *requisition = image_requisition.width + toggle_spacing;
        }
362
      else
363 364 365 366
        {
          if (image_requisition.height > 0)
            *requisition = image_requisition.height + toggle_spacing;
        }
367
    }
368 369
}

370 371 372
static void
gtk_image_menu_item_recalculate (GtkImageMenuItem *image_menu_item)
{
373
  GtkImageMenuItemPrivate    *priv = image_menu_item->priv;
374 375 376 377 378 379 380
  GtkStockItem             stock_item;
  GtkWidget               *image;
  const gchar             *resolved_label = priv->label;

  if (priv->use_stock && priv->label)
    {

381
      if (!priv->image)
382 383 384 385
        {
          image = gtk_image_new_from_stock (priv->label, GTK_ICON_SIZE_MENU);
          gtk_image_menu_item_set_image (image_menu_item, image);
        }
386 387

      if (gtk_stock_lookup (priv->label, &stock_item))
388
          resolved_label = stock_item.label;
389

390
        gtk_menu_item_set_use_underline (GTK_MENU_ITEM (image_menu_item), TRUE);
391 392 393 394 395 396 397
    }

  GTK_MENU_ITEM_CLASS
    (gtk_image_menu_item_parent_class)->set_label (GTK_MENU_ITEM (image_menu_item), resolved_label);

}

398
static void
399
gtk_image_menu_item_set_label (GtkMenuItem      *menu_item,
400
                               const gchar      *label)
401
{
402
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (menu_item)->priv;
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418

  if (priv->label != label)
    {
      g_free (priv->label);
      priv->label = g_strdup (label);

      gtk_image_menu_item_recalculate (GTK_IMAGE_MENU_ITEM (menu_item));

      g_object_notify (G_OBJECT (menu_item), "label");

    }
}

static G_CONST_RETURN gchar *
gtk_image_menu_item_get_label (GtkMenuItem *menu_item)
{
419
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (menu_item)->priv;
420

421 422
  return priv->label;
}
423 424

static void
425
gtk_image_menu_item_get_preferred_width (GtkWidget        *widget,
426 427
                                         gint             *minimum,
                                         gint             *natural)
428
{
429
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
430
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
431 432
  gint child_width = 0;
  GtkPackDirection pack_dir;
433 434 435 436 437 438
  GtkWidget *parent;

  parent = gtk_widget_get_parent (widget);

  if (GTK_IS_MENU_BAR (parent))
    pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
439 440 441
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;

442
  if (priv->image && gtk_widget_get_visible (priv->image))
443 444
    {
      GtkRequisition child_requisition;
445

446
      gtk_widget_get_preferred_size (priv->image, &child_requisition, NULL);
447

448
      child_width = child_requisition.width;
449 450 451 452 453 454 455 456 457 458 459 460 461
    }

  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->get_preferred_width (widget, minimum, natural);

  if (pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT)
    {
      *minimum = MAX (*minimum, child_width);
      *natural = MAX (*natural, child_width);
    }
}

static void
gtk_image_menu_item_get_preferred_height (GtkWidget        *widget,
462 463
                                          gint             *minimum,
                                          gint             *natural)
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
  gint child_height = 0;
  GtkPackDirection pack_dir;
  GtkWidget *parent;

  parent = gtk_widget_get_parent (widget);

  if (GTK_IS_MENU_BAR (parent))
    pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;

  if (priv->image && gtk_widget_get_visible (priv->image))
    {
      GtkRequisition child_requisition;

      gtk_widget_get_preferred_size (priv->image, &child_requisition, NULL);

484 485
      child_height = child_requisition.height;
    }
486

487
  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->get_preferred_height (widget, minimum, natural);
488

489 490 491 492 493 494 495 496 497
  if (pack_dir == GTK_PACK_DIRECTION_RTL || pack_dir == GTK_PACK_DIRECTION_LTR)
    {
      *minimum = MAX (*minimum, child_height);
      *natural = MAX (*natural, child_height);
    }
}

static void
gtk_image_menu_item_get_preferred_height_for_width (GtkWidget        *widget,
498 499 500
                                                    gint              width,
                                                    gint             *minimum,
                                                    gint             *natural)
501 502 503 504 505 506 507 508 509 510 511
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
  gint child_height = 0;
  GtkPackDirection pack_dir;
  GtkWidget *parent;

  parent = gtk_widget_get_parent (widget);

  if (GTK_IS_MENU_BAR (parent))
    pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
512
  else
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
    pack_dir = GTK_PACK_DIRECTION_LTR;

  if (priv->image && gtk_widget_get_visible (priv->image))
    {
      GtkRequisition child_requisition;

      gtk_widget_get_preferred_size (priv->image, &child_requisition, NULL);

      child_height = child_requisition.height;
    }

  GTK_WIDGET_CLASS
    (gtk_image_menu_item_parent_class)->get_preferred_height_for_width (widget, width, minimum, natural);

  if (pack_dir == GTK_PACK_DIRECTION_RTL || pack_dir == GTK_PACK_DIRECTION_LTR)
    {
      *minimum = MAX (*minimum, child_height);
      *natural = MAX (*natural, child_height);
    }
532 533
}

534

535 536 537 538
static void
gtk_image_menu_item_size_allocate (GtkWidget     *widget,
                                   GtkAllocation *allocation)
{
539
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
540
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
541
  GtkAllocation widget_allocation;
542
  GtkPackDirection pack_dir;
543 544 545 546 547 548
  GtkWidget *parent;

  parent = gtk_widget_get_parent (widget);

  if (GTK_IS_MENU_BAR (parent))
    pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (parent));
549 550
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;
551 552

  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->size_allocate (widget, allocation);
553

554
  if (priv->image && gtk_widget_get_visible (priv->image))
555
    {
556
      gint x, y, offset;
557 558 559
      GtkStyleContext *context;
      GtkStateFlags state;
      GtkBorder padding;
560
      GtkRequisition child_requisition;
561
      GtkAllocation child_allocation;
562
      guint horizontal_padding, toggle_spacing;
563
      gint toggle_size;
564

565
      toggle_size = GTK_MENU_ITEM (image_menu_item)->priv->toggle_size;
566
      gtk_widget_style_get (widget,
567 568 569 570
                            "horizontal-padding", &horizontal_padding,
                            "toggle-spacing", &toggle_spacing,
                            NULL);

571 572 573
      /* Man this is lame hardcoding action, but I can't
       * come up with a solution that's really better.
       */
574

575
      gtk_widget_get_preferred_size (priv->image, &child_requisition, NULL);
576

577 578
      gtk_widget_get_allocation (widget, &widget_allocation);

579 580 581 582 583
      context = gtk_widget_get_style_context (widget);
      state = gtk_widget_get_state_flags (widget);
      gtk_style_context_get_padding (context, state, &padding);
      offset = gtk_container_get_border_width (GTK_CONTAINER (image_menu_item));

584
      if (pack_dir == GTK_PACK_DIRECTION_LTR ||
585 586 587 588
          pack_dir == GTK_PACK_DIRECTION_RTL)
        {
          if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
              (pack_dir == GTK_PACK_DIRECTION_LTR))
589
            x = offset + horizontal_padding + padding.left +
590 591
               (toggle_size - toggle_spacing - child_requisition.width) / 2;
          else
592
            x = widget_allocation.width - offset - horizontal_padding - padding.right -
593 594
              toggle_size + toggle_spacing +
              (toggle_size - toggle_spacing - child_requisition.width) / 2;
595 596

          y = (widget_allocation.height - child_requisition.height) / 2;
597
        }
598
      else
599 600 601
        {
          if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
              (pack_dir == GTK_PACK_DIRECTION_TTB))
602
            y = offset + horizontal_padding + padding.top +
603 604
              (toggle_size - toggle_spacing - child_requisition.height) / 2;
          else
605
            y = widget_allocation.height - offset - horizontal_padding - padding.bottom -
606 607
              toggle_size + toggle_spacing +
              (toggle_size - toggle_spacing - child_requisition.height) / 2;
608

609
          x = (widget_allocation.width - child_requisition.width) / 2;
610 611
        }

612 613
      child_allocation.width = child_requisition.width;
      child_allocation.height = child_requisition.height;
614 615
      child_allocation.x = widget_allocation.x + MAX (x, 0);
      child_allocation.y = widget_allocation.y + MAX (y, 0);
616

617
      gtk_widget_size_allocate (priv->image, &child_allocation);
618 619 620 621 622
    }
}

static void
gtk_image_menu_item_forall (GtkContainer   *container,
623
                            gboolean        include_internals,
624 625 626
                            GtkCallback     callback,
                            gpointer        callback_data)
{
627
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (container);
628
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
629 630 631 632 633

  GTK_CONTAINER_CLASS (gtk_image_menu_item_parent_class)->forall (container,
                                                                  include_internals,
                                                                  callback,
                                                                  callback_data);
634

635 636
  if (include_internals && priv->image)
    (* callback) (priv->image, callback_data);
637 638
}

639

640
static void
641 642 643
gtk_image_menu_item_activatable_interface_init (GtkActivatableIface  *iface)
{
  parent_activatable_iface = g_type_interface_peek_parent (iface);
644 645
  iface->update = gtk_image_menu_item_update;
  iface->sync_action_properties = gtk_image_menu_item_sync_action_properties;
646 647 648 649 650 651 652 653 654
}

static gboolean
activatable_update_stock_id (GtkImageMenuItem *image_menu_item, GtkAction *action)
{
  GtkWidget   *image;
  const gchar *stock_id  = gtk_action_get_stock_id (action);

  image = gtk_image_menu_item_get_image (image_menu_item);
655

656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
  if (GTK_IS_IMAGE (image) &&
      stock_id && gtk_icon_factory_lookup_default (stock_id))
    {
      gtk_image_set_from_stock (GTK_IMAGE (image), stock_id, GTK_ICON_SIZE_MENU);
      return TRUE;
    }

  return FALSE;
}

static gboolean
activatable_update_gicon (GtkImageMenuItem *image_menu_item, GtkAction *action)
{
  GtkWidget   *image;
  GIcon       *icon = gtk_action_get_gicon (action);
  const gchar *stock_id = gtk_action_get_stock_id (action);

  image = gtk_image_menu_item_get_image (image_menu_item);

  if (icon && GTK_IS_IMAGE (image) &&
      !(stock_id && gtk_icon_factory_lookup_default (stock_id)))
    {
      gtk_image_set_from_gicon (GTK_IMAGE (image), icon, GTK_ICON_SIZE_MENU);
      return TRUE;
    }

  return FALSE;
}

static void
activatable_update_icon_name (GtkImageMenuItem *image_menu_item, GtkAction *action)
{
  GtkWidget   *image;
  const gchar *icon_name = gtk_action_get_icon_name (action);

  image = gtk_image_menu_item_get_image (image_menu_item);
692 693

  if (GTK_IS_IMAGE (image) &&
694 695 696 697 698 699 700
      (gtk_image_get_storage_type (GTK_IMAGE (image)) == GTK_IMAGE_EMPTY ||
       gtk_image_get_storage_type (GTK_IMAGE (image)) == GTK_IMAGE_ICON_NAME))
    {
      gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, GTK_ICON_SIZE_MENU);
    }
}

701 702
static void
gtk_image_menu_item_update (GtkActivatable *activatable,
703 704
                            GtkAction      *action,
                            const gchar    *property_name)
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
{
  GtkImageMenuItem *image_menu_item;
  gboolean   use_appearance;

  image_menu_item = GTK_IMAGE_MENU_ITEM (activatable);

  parent_activatable_iface->update (activatable, action, property_name);

  use_appearance = gtk_activatable_get_use_action_appearance (activatable);
  if (!use_appearance)
    return;

  if (strcmp (property_name, "stock-id") == 0)
    activatable_update_stock_id (image_menu_item, action);
  else if (strcmp (property_name, "gicon") == 0)
    activatable_update_gicon (image_menu_item, action);
  else if (strcmp (property_name, "icon-name") == 0)
    activatable_update_icon_name (image_menu_item, action);
}

725
static void
726
gtk_image_menu_item_sync_action_properties (GtkActivatable *activatable,
727
                                            GtkAction      *action)
728 729 730 731 732 733 734
{
  GtkImageMenuItem *image_menu_item;
  GtkWidget *image;
  gboolean   use_appearance;

  image_menu_item = GTK_IMAGE_MENU_ITEM (activatable);

735
  parent_activatable_iface->sync_action_properties (activatable, action);
736 737 738 739 740 741 742 743 744 745 746 747 748 749

  if (!action)
    return;

  use_appearance = gtk_activatable_get_use_action_appearance (activatable);
  if (!use_appearance)
    return;

  image = gtk_image_menu_item_get_image (image_menu_item);
  if (image && !GTK_IS_IMAGE (image))
    {
      gtk_image_menu_item_set_image (image_menu_item, NULL);
      image = NULL;
    }
750

751 752 753 754 755
  if (!image)
    {
      image = gtk_image_new ();
      gtk_widget_show (image);
      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (activatable),
756
                                     image);
757
    }
758

759 760 761 762
  if (!activatable_update_stock_id (image_menu_item, action) &&
      !activatable_update_gicon (image_menu_item, action))
    activatable_update_icon_name (image_menu_item, action);

763 764
  gtk_image_menu_item_set_always_show_image (image_menu_item,
                                             gtk_action_get_always_show_image (action));
765 766 767
}


Matthias Clasen's avatar
Matthias Clasen committed
768 769 770 771 772 773
/**
 * gtk_image_menu_item_new:
 * @returns: a new #GtkImageMenuItem.
 *
 * Creates a new #GtkImageMenuItem with an empty label.
 **/
774
GtkWidget*
775 776 777 778 779
gtk_image_menu_item_new (void)
{
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
}

Matthias Clasen's avatar
Matthias Clasen committed
780 781 782 783 784
/**
 * gtk_image_menu_item_new_with_label:
 * @label: the text of the menu item.
 * @returns: a new #GtkImageMenuItem.
 *
785 786
 * Creates a new #GtkImageMenuItem containing a label.
 */
787 788
GtkWidget*
gtk_image_menu_item_new_with_label (const gchar *label)
789
{
790 791 792
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "label", label,
                       NULL);
793 794
}

795 796 797

/**
 * gtk_image_menu_item_new_with_mnemonic:
Matthias Clasen's avatar
Matthias Clasen committed
798
 * @label: the text of the menu item, with an underscore in front of the
799 800 801 802 803 804
 *         mnemonic character
 * @returns: a new #GtkImageMenuItem
 *
 * Creates a new #GtkImageMenuItem 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.
805
 */
806 807 808
GtkWidget*
gtk_image_menu_item_new_with_mnemonic (const gchar *label)
{
809 810 811 812
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "use-underline", TRUE,
                       "label", label,
                       NULL);
813 814
}

Matthias Clasen's avatar
Matthias Clasen committed
815 816 817
/**
 * gtk_image_menu_item_new_from_stock:
 * @stock_id: the name of the stock item.
818
 * @accel_group: (allow-none): the #GtkAccelGroup to add the menu items
819
 *   accelerator to, or %NULL.
Matthias Clasen's avatar
Matthias Clasen committed
820 821
 * @returns: a new #GtkImageMenuItem.
 *
822 823
 * Creates a new #GtkImageMenuItem containing the image and text from a
 * stock item. Some stock ids have preprocessor macros like #GTK_STOCK_OK
Matthias Clasen's avatar
Matthias Clasen committed
824
 * and #GTK_STOCK_APPLY.
825
 *
Matthias Clasen's avatar
Matthias Clasen committed
826 827 828 829 830
 * If you want this menu item to have changeable accelerators, then pass in
 * %NULL for accel_group. Next call gtk_menu_item_set_accel_path() with an
 * appropriate path for the menu item, use gtk_stock_lookup() to look up the
 * standard accelerator for the stock item, and if one is found, call
 * gtk_accel_map_add_entry() to register it.
831
 */
832
GtkWidget*
833 834
gtk_image_menu_item_new_from_stock (const gchar   *stock_id,
                                    GtkAccelGroup *accel_group)
835
{
836 837 838 839 840
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "label", stock_id,
                       "use-stock", TRUE,
                       "accel-group", accel_group,
                       NULL);
841
}
842

843 844 845 846 847 848 849 850 851 852 853 854
/**
 * gtk_image_menu_item_set_use_stock:
 * @image_menu_item: a #GtkImageMenuItem
 * @use_stock: %TRUE if the menuitem should use a stock item
 *
 * If %TRUE, the label set in the menuitem is used as a
 * stock id to select the stock item for the item.
 *
 * Since: 2.16
 */
void
gtk_image_menu_item_set_use_stock (GtkImageMenuItem *image_menu_item,
855
                                   gboolean          use_stock)
856
{
857
  GtkImageMenuItemPrivate *priv;
858 859

  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
860

861
  priv = image_menu_item->priv;
862

863
  if (priv->use_stock != use_stock)
864
    {
865
      priv->use_stock = use_stock;
866

867 868 869 870 871 872 873 874 875 876 877 878 879 880
      gtk_image_menu_item_recalculate (image_menu_item);

      g_object_notify (G_OBJECT (image_menu_item), "use-stock");
    }
}

/**
 * gtk_image_menu_item_get_use_stock:
 * @image_menu_item: a #GtkImageMenuItem
 *
 * Checks whether the label set in the menuitem is used as a
 * stock id to select the stock item for the item.
 *
 * Returns: %TRUE if the label set in the menuitem is used as a
Matthias Clasen's avatar
Matthias Clasen committed
881
 *     stock id to select the stock item for the item
882 883 884 885 886 887 888 889
 *
 * Since: 2.16
 */
gboolean
gtk_image_menu_item_get_use_stock (GtkImageMenuItem *image_menu_item)
{
  g_return_val_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item), FALSE);

890
  return image_menu_item->priv->use_stock;
891 892
}

893 894 895 896 897
/**
 * gtk_image_menu_item_set_always_show_image:
 * @image_menu_item: a #GtkImageMenuItem
 * @always_show: %TRUE if the menuitem should always show the image
 *
898
 * If %TRUE, the menu item will ignore the #GtkSettings:gtk-menu-images
899 900 901
 * setting and always show the image, if available.
 *
 * Use this property if the menuitem would be useless or hard to use
902 903
 * without the image.
 *
904 905 906 907 908 909
 * Since: 2.16
 */
void
gtk_image_menu_item_set_always_show_image (GtkImageMenuItem *image_menu_item,
                                           gboolean          always_show)
{
910
  GtkImageMenuItemPrivate *priv;
911 912 913

  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));

914
  priv = image_menu_item->priv;
915 916 917 918 919

  if (priv->always_show_image != always_show)
    {
      priv->always_show_image = always_show;

920
      if (priv->image)
921 922
        {
          if (show_image (image_menu_item))
923
            gtk_widget_show (priv->image);
924
          else
925
            gtk_widget_hide (priv->image);
926 927 928 929 930 931 932 933 934 935
        }

      g_object_notify (G_OBJECT (image_menu_item), "always-show-image");
    }
}

/**
 * gtk_image_menu_item_get_always_show_image:
 * @image_menu_item: a #GtkImageMenuItem
 *
Matthias Clasen's avatar
Matthias Clasen committed
936
 * Returns whether the menu item will ignore the #GtkSettings:gtk-menu-images
937
 * setting and always show the image, if available.
938
 *
939 940 941 942 943 944 945 946 947
 * Returns: %TRUE if the menu item will always show the image
 *
 * Since: 2.16
 */
gboolean
gtk_image_menu_item_get_always_show_image (GtkImageMenuItem *image_menu_item)
{
  g_return_val_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item), FALSE);

948
  return image_menu_item->priv->always_show_image;
949 950
}

951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967

/**
 * gtk_image_menu_item_set_accel_group:
 * @image_menu_item: a #GtkImageMenuItem
 * @accel_group: the #GtkAccelGroup
 *
 * Specifies an @accel_group to add the menu items accelerator to
 * (this only applies to stock items so a stock item must already
 * be set, make sure to call gtk_image_menu_item_set_use_stock()
 * and gtk_menu_item_set_label() with a valid stock item first).
 *
 * If you want this menu item to have changeable accelerators then
 * you shouldnt need this (see gtk_image_menu_item_new_from_stock()).
 *
 * Since: 2.16
 */
void
968 969
gtk_image_menu_item_set_accel_group (GtkImageMenuItem *image_menu_item,
                                     GtkAccelGroup    *accel_group)
970
{
971
  GtkImageMenuItemPrivate    *priv;
972 973 974
  GtkStockItem             stock_item;

  /* Silent return for the constructor */
975
  if (!accel_group)
976
    return;
977

978 979 980
  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));

981
  priv = image_menu_item->priv;
982 983 984 985

  if (priv->use_stock && priv->label && gtk_stock_lookup (priv->label, &stock_item))
    if (stock_item.keyval)
      {
986 987 988 989 990 991 992 993
        gtk_widget_add_accelerator (GTK_WIDGET (image_menu_item),
                                    "activate",
                                    accel_group,
                                    stock_item.keyval,
                                    stock_item.modifier,
                                    GTK_ACCEL_VISIBLE);

        g_object_notify (G_OBJECT (image_menu_item), "accel-group");
994
      }
995 996
}

997
/**
Matthias Clasen's avatar
Matthias Clasen committed
998 999
 * gtk_image_menu_item_set_image:
 * @image_menu_item: a #GtkImageMenuItem.
1000 1001
 * @image: (allow-none): a widget to set as the image for the menu item.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1002
 * Sets the image of @image_menu_item to the given widget.
Matthias Clasen's avatar
Matthias Clasen committed
1003 1004
 * Note that it depends on the show-menu-images setting whether
 * the image will be displayed or not.
1005
 */
1006
void
1007 1008
gtk_image_menu_item_set_image (GtkImageMenuItem *image_menu_item,
                               GtkWidget        *image)
1009
{
1010
  GtkImageMenuItemPrivate *priv;
1011

1012
  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
1013

1014 1015 1016
  priv = image_menu_item->priv;

  if (image == priv->image)
1017 1018
    return;

1019
  if (priv->image)
1020
    gtk_container_remove (GTK_CONTAINER (image_menu_item),
1021
                          priv->image);
1022

1023
  priv->image = image;
1024 1025 1026 1027 1028

  if (image == NULL)
    return;

  gtk_widget_set_parent (image, GTK_WIDGET (image_menu_item));
1029
  g_object_set (image,
1030 1031 1032
                "visible", show_image (image_menu_item),
                "no-show-all", TRUE,
                NULL);
1033

1034
  g_object_notify (G_OBJECT (image_menu_item), "image");
1035 1036
}

Matthias Clasen's avatar
Matthias Clasen committed
1037 1038
/**
 * gtk_image_menu_item_get_image:
1039
 * @image_menu_item: a #GtkImageMenuItem
Matthias Clasen's avatar
Matthias Clasen committed
1040 1041 1042
 *
 * Gets the widget that is currently set as the image of @image_menu_item.
 * See gtk_image_menu_item_set_image().
1043 1044
 *
 * Return value: (transfer none): the widget set as image of @image_menu_item
Matthias Clasen's avatar
Matthias Clasen committed
1045
 **/
1046 1047 1048 1049 1050
GtkWidget*
gtk_image_menu_item_get_image (GtkImageMenuItem *image_menu_item)
{
  g_return_val_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item), NULL);

1051
  return image_menu_item->priv->image;
1052 1053
}

1054
static void
1055 1056 1057
gtk_image_menu_item_remove (GtkContainer *container,
                            GtkWidget    *child)
{
1058
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (container);
1059
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
1060

1061
  if (child == priv->image)
1062 1063
    {
      gboolean widget_was_visible;
1064

1065
      widget_was_visible = gtk_widget_get_visible (child);
1066

1067
      gtk_widget_unparent (child);
1068 1069
      priv->image = NULL;

1070 1071
      if (widget_was_visible &&
          gtk_widget_get_visible (GTK_WIDGET (container)))
1072
        gtk_widget_queue_resize (GTK_WIDGET (container));
1073 1074

      g_object_notify (G_OBJECT (image_menu_item), "image");
1075 1076 1077
    }
  else
    {
1078
      GTK_CONTAINER_CLASS (gtk_image_menu_item_parent_class)->remove (container, child);
1079 1080 1081
    }
}

1082
static void
Matthias Clasen's avatar
Matthias Clasen committed
1083 1084
show_image_change_notify (GtkImageMenuItem *image_menu_item)
{
1085
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
1086 1087

  if (priv->image)
Matthias Clasen's avatar
Matthias Clasen committed
1088 1089
    {
      if (show_image (image_menu_item))
1090
        gtk_widget_show (priv->image);
Matthias Clasen's avatar
Matthias Clasen committed
1091
      else
1092
        gtk_widget_hide (priv->image);
Matthias Clasen's avatar
Matthias Clasen committed
1093 1094 1095 1096 1097
    }
}

static void
traverse_container (GtkWidget *widget,
1098
                    gpointer   data)
Matthias Clasen's avatar
Matthias Clasen committed
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
{
  if (GTK_IS_IMAGE_MENU_ITEM (widget))
    show_image_change_notify (GTK_IMAGE_MENU_ITEM (widget));
  else if (GTK_IS_CONTAINER (widget))
    gtk_container_forall (GTK_CONTAINER (widget), traverse_container, NULL);
}

static void
gtk_image_menu_item_setting_changed (GtkSettings *settings)
{
1109
  GList *list, *l;
Matthias Clasen's avatar
Matthias Clasen committed
1110 1111 1112

  list = gtk_window_list_toplevels ();

1113
  for (l = list; l; l = l->next)
1114 1115
    gtk_container_forall (GTK_CONTAINER (l->data),
                          traverse_container, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1116

1117
  g_list_free (list);
Matthias Clasen's avatar
Matthias Clasen committed
1118 1119 1120 1121
}

static void
gtk_image_menu_item_screen_changed (GtkWidget *widget,
1122
                                    GdkScreen *previous_screen)
Matthias Clasen's avatar
Matthias Clasen committed
1123 1124
{
  GtkSettings *settings;