gtkimagemenuitem.c 37.7 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"
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 47 48 49 50 51 52 53 54 55
/**
 * SECTION:gtkimagemenuitem
 * @Short_description: A menu item with an icon
 * @Title: GtkImageMenuItem
 *
 * A GtkImageMenuItem is a menu item which has an icon next to the text label.
 *
 * Note that the user can disable display of menu icons, so make sure to still
 * fill in the text label.
 */

56

57
struct _GtkImageMenuItemPrivate
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
{
  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;

76
static void gtk_image_menu_item_destroy              (GtkWidget        *widget);
77 78
static void gtk_image_menu_item_get_preferred_width  (GtkWidget        *widget,
                                                      gint             *minimum,
79
                                                      gint             *natural);
80 81
static void gtk_image_menu_item_get_preferred_height (GtkWidget        *widget,
                                                      gint             *minimum,
82
                                                      gint             *natural);
83
static void gtk_image_menu_item_get_preferred_height_for_width (GtkWidget *widget,
84 85 86
                                                                gint       width,
                                                                gint      *minimum,
                                                                gint      *natural);
87 88
static void gtk_image_menu_item_size_allocate        (GtkWidget        *widget,
                                                      GtkAllocation    *allocation);
89
static void gtk_image_menu_item_map                  (GtkWidget        *widget);
90 91 92
static void gtk_image_menu_item_remove               (GtkContainer     *container,
                                                      GtkWidget        *child);
static void gtk_image_menu_item_toggle_size_request  (GtkMenuItem      *menu_item,
93
                                                      gint             *requisition);
94
static void gtk_image_menu_item_set_label            (GtkMenuItem      *menu_item,
95
                                                      const gchar      *label);
96
static const gchar * gtk_image_menu_item_get_label   (GtkMenuItem *menu_item);
97 98

static void gtk_image_menu_item_forall               (GtkContainer    *container,
99 100 101
                                                      gboolean         include_internals,
                                                      GtkCallback      callback,
                                                      gpointer         callback_data);
102 103 104

static void gtk_image_menu_item_finalize             (GObject         *object);
static void gtk_image_menu_item_set_property         (GObject         *object,
105 106 107
                                                      guint            prop_id,
                                                      const GValue    *value,
                                                      GParamSpec      *pspec);
108
static void gtk_image_menu_item_get_property         (GObject         *object,
109 110 111
                                                      guint            prop_id,
                                                      GValue          *value,
                                                      GParamSpec      *pspec);
112
static void gtk_image_menu_item_screen_changed       (GtkWidget        *widget,
113
                                                      GdkScreen        *previous_screen);
114 115 116

static void gtk_image_menu_item_recalculate          (GtkImageMenuItem *image_menu_item);

117
static void gtk_image_menu_item_activatable_interface_init (GtkActivatableIface  *iface);
118
static void gtk_image_menu_item_update                     (GtkActivatable       *activatable,
119 120
                                                            GtkAction            *action,
                                                            const gchar          *property_name);
121
static void gtk_image_menu_item_sync_action_properties     (GtkActivatable       *activatable,
122
                                                            GtkAction            *action);
123

124 125

G_DEFINE_TYPE_WITH_CODE (GtkImageMenuItem, gtk_image_menu_item, GTK_TYPE_MENU_ITEM,
126 127
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
                                                gtk_image_menu_item_activatable_interface_init))
128

129

130 131 132
static void
gtk_image_menu_item_class_init (GtkImageMenuItemClass *klass)
{
133 134 135 136 137
  GObjectClass *gobject_class = (GObjectClass*) klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
  GtkMenuItemClass *menu_item_class = (GtkMenuItemClass*) klass;
  GtkContainerClass *container_class = (GtkContainerClass*) klass;

138
  widget_class->destroy = gtk_image_menu_item_destroy;
Matthias Clasen's avatar
Matthias Clasen committed
139
  widget_class->screen_changed = gtk_image_menu_item_screen_changed;
140 141 142
  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;
143
  widget_class->size_allocate = gtk_image_menu_item_size_allocate;
144
  widget_class->map = gtk_image_menu_item_map;
145 146 147

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

149
  menu_item_class->toggle_size_request = gtk_image_menu_item_toggle_size_request;
150 151
  menu_item_class->set_label           = gtk_image_menu_item_set_label;
  menu_item_class->get_label           = gtk_image_menu_item_get_label;
152

153
  gobject_class->finalize     = gtk_image_menu_item_finalize;
154 155
  gobject_class->set_property = gtk_image_menu_item_set_property;
  gobject_class->get_property = gtk_image_menu_item_get_property;
156

157 158 159
  g_object_class_install_property (gobject_class,
                                   PROP_IMAGE,
                                   g_param_spec_object ("image",
160 161
                                                        P_("Image widget"),
                                                        P_("Child widget to appear next to the menu text"),
162
                                                        GTK_TYPE_WIDGET,
163
                                                        GTK_PARAM_READWRITE));
164 165 166 167 168 169 170
  /**
   * 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
171
   */
172 173 174
  g_object_class_install_property (gobject_class,
                                   PROP_USE_STOCK,
                                   g_param_spec_boolean ("use-stock",
175 176 177 178
                                                         P_("Use stock"),
                                                         P_("Whether to use the label text to create a stock menu item"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
179

180 181 182
  /**
   * GtkImageMenuItem:always-show-image:
   *
183
   * If %TRUE, the menu item will ignore the #GtkSettings:gtk-menu-images
184 185 186
   * setting and always show the image, if available.
   *
   * Use this property if the menuitem would be useless or hard to use
187
   * without the image.
188 189
   *
   * Since: 2.16
190
   */
191 192 193
  g_object_class_install_property (gobject_class,
                                   PROP_ALWAYS_SHOW_IMAGE,
                                   g_param_spec_boolean ("always-show-image",
194 195 196 197
                                                         P_("Always show image"),
                                                         P_("Whether the image will always be shown"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
198

199 200 201 202 203 204
  /**
   * GtkImageMenuItem:accel-group:
   *
   * The Accel Group to use for stock accelerator keys
   *
   * Since: 2.16
205
   */
206 207 208
  g_object_class_install_property (gobject_class,
                                   PROP_ACCEL_GROUP,
                                   g_param_spec_object ("accel-group",
209 210 211 212
                                                        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
213

214
    g_type_class_add_private (klass, sizeof (GtkImageMenuItemPrivate));
215 216 217 218 219
}

static void
gtk_image_menu_item_init (GtkImageMenuItem *image_menu_item)
{
220
  GtkImageMenuItemPrivate *priv;
221

222 223
  image_menu_item->priv = G_TYPE_INSTANCE_GET_PRIVATE (image_menu_item,
                                                       GTK_TYPE_IMAGE_MENU_ITEM,
224
                                                       GtkImageMenuItemPrivate);
225
  priv = image_menu_item->priv;
226

227 228 229
  priv->image = NULL;
  priv->use_stock = FALSE;
  priv->label  = NULL;
230 231
}

232
static void
233 234
gtk_image_menu_item_finalize (GObject *object)
{
235
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (object)->priv;
236 237 238 239 240 241 242

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

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

243
static void
244 245 246
gtk_image_menu_item_set_property (GObject         *object,
                                  guint            prop_id,
                                  const GValue    *value,
Tim Janik's avatar
Tim Janik committed
247
                                  GParamSpec      *pspec)
248
{
249
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
250

251 252 253
  switch (prop_id)
    {
    case PROP_IMAGE:
254 255 256 257 258
      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;
259 260 261
    case PROP_ALWAYS_SHOW_IMAGE:
      gtk_image_menu_item_set_always_show_image (image_menu_item, g_value_get_boolean (value));
      break;
262 263
    case PROP_ACCEL_GROUP:
      gtk_image_menu_item_set_accel_group (image_menu_item, g_value_get_object (value));
264 265 266 267 268 269
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}
270

271 272 273 274
static void
gtk_image_menu_item_get_property (GObject         *object,
                                  guint            prop_id,
                                  GValue          *value,
Tim Janik's avatar
Tim Janik committed
275
                                  GParamSpec      *pspec)
276 277
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
278

279 280 281
  switch (prop_id)
    {
    case PROP_IMAGE:
282 283 284
      g_value_set_object (value, gtk_image_menu_item_get_image (image_menu_item));
      break;
    case PROP_USE_STOCK:
285
      g_value_set_boolean (value, gtk_image_menu_item_get_use_stock (image_menu_item));
286
      break;
287 288 289
    case PROP_ALWAYS_SHOW_IMAGE:
      g_value_set_boolean (value, gtk_image_menu_item_get_always_show_image (image_menu_item));
      break;
290 291 292 293
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
294 295
}

Matthias Clasen's avatar
Matthias Clasen committed
296 297 298
static gboolean
show_image (GtkImageMenuItem *image_menu_item)
{
299
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
Matthias Clasen's avatar
Matthias Clasen committed
300 301 302
  GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (image_menu_item));
  gboolean show;

303 304 305 306
  if (priv->always_show_image)
    show = TRUE;
  else
    g_object_get (settings, "gtk-menu-images", &show, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
307 308 309

  return show;
}
310

311 312 313 314
static void
gtk_image_menu_item_map (GtkWidget *widget)
{
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
315
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
316 317 318

  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->map (widget);

319 320
  if (priv->image)
    g_object_set (priv->image,
321 322 323 324
                  "visible", show_image (image_menu_item),
                  NULL);
}

325
static void
326
gtk_image_menu_item_destroy (GtkWidget *widget)
327
{
328
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
329
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
330

331
  if (priv->image)
332
    gtk_container_remove (GTK_CONTAINER (image_menu_item),
333
                          priv->image);
334

335
  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->destroy (widget);
336 337
}

338 339
static void
gtk_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
340
                                         gint        *requisition)
341
{
342
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (menu_item);
343
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
344
  GtkPackDirection pack_dir;
345 346
  GtkWidget *parent;
  GtkWidget *widget = GTK_WIDGET (menu_item);
347

348 349 350 351
  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));
352 353
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;
354

355 356
  *requisition = 0;

357
  if (priv->image && gtk_widget_get_visible (priv->image))
358
    {
359
      GtkRequisition image_requisition;
360
      guint toggle_spacing;
361

362
      gtk_widget_get_preferred_size (priv->image, &image_requisition, NULL);
363

364
      gtk_widget_style_get (GTK_WIDGET (menu_item),
365 366 367
                            "toggle-spacing", &toggle_spacing,
                            NULL);

368
      if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
369 370 371 372
        {
          if (image_requisition.width > 0)
            *requisition = image_requisition.width + toggle_spacing;
        }
373
      else
374 375 376 377
        {
          if (image_requisition.height > 0)
            *requisition = image_requisition.height + toggle_spacing;
        }
378
    }
379 380
}

381 382 383
static void
gtk_image_menu_item_recalculate (GtkImageMenuItem *image_menu_item)
{
384
  GtkImageMenuItemPrivate    *priv = image_menu_item->priv;
385 386 387 388 389 390 391
  GtkStockItem             stock_item;
  GtkWidget               *image;
  const gchar             *resolved_label = priv->label;

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

392
      if (!priv->image)
393 394 395 396
        {
          image = gtk_image_new_from_stock (priv->label, GTK_ICON_SIZE_MENU);
          gtk_image_menu_item_set_image (image_menu_item, image);
        }
397 398

      if (gtk_stock_lookup (priv->label, &stock_item))
399
          resolved_label = stock_item.label;
400

401
        gtk_menu_item_set_use_underline (GTK_MENU_ITEM (image_menu_item), TRUE);
402 403 404 405 406 407 408
    }

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

}

409
static void
410
gtk_image_menu_item_set_label (GtkMenuItem      *menu_item,
411
                               const gchar      *label)
412
{
413
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (menu_item)->priv;
414 415 416 417 418 419 420 421 422 423 424 425 426

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

    }
}

427
static const gchar *
428 429
gtk_image_menu_item_get_label (GtkMenuItem *menu_item)
{
430
  GtkImageMenuItemPrivate *priv = GTK_IMAGE_MENU_ITEM (menu_item)->priv;
431

432 433
  return priv->label;
}
434 435

static void
436
gtk_image_menu_item_get_preferred_width (GtkWidget        *widget,
437 438
                                         gint             *minimum,
                                         gint             *natural)
439
{
440
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
441
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
442
  GtkPackDirection pack_dir;
443 444 445 446 447 448
  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));
449 450 451
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;

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

454 455 456
  if ((pack_dir == GTK_PACK_DIRECTION_TTB || pack_dir == GTK_PACK_DIRECTION_BTT) &&
      priv->image &&
      gtk_widget_get_visible (priv->image))
457
    {
458 459 460 461 462 463
      gint child_minimum, child_natural;

      gtk_widget_get_preferred_width (priv->image, &child_minimum, &child_natural);

      *minimum = MAX (*minimum, child_minimum);
      *natural = MAX (*natural, child_natural);
464 465 466 467 468
    }
}

static void
gtk_image_menu_item_get_preferred_height (GtkWidget        *widget,
469 470
                                          gint             *minimum,
                                          gint             *natural)
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
{
  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);

491 492
      child_height = child_requisition.height;
    }
493

494
  GTK_WIDGET_CLASS (gtk_image_menu_item_parent_class)->get_preferred_height (widget, minimum, natural);
495

496 497 498 499 500 501 502 503 504
  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,
505 506 507
                                                    gint              width,
                                                    gint             *minimum,
                                                    gint             *natural)
508 509 510 511 512 513 514 515 516 517 518
{
  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));
519
  else
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
    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);
    }
539 540
}

541

542 543 544 545
static void
gtk_image_menu_item_size_allocate (GtkWidget     *widget,
                                   GtkAllocation *allocation)
{
546
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
547
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
548
  GtkAllocation widget_allocation;
549
  GtkPackDirection pack_dir;
550 551 552 553 554 555
  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));
556 557
  else
    pack_dir = GTK_PACK_DIRECTION_LTR;
558 559

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

561
  if (priv->image && gtk_widget_get_visible (priv->image))
562
    {
563
      gint x, y, offset;
564 565 566
      GtkStyleContext *context;
      GtkStateFlags state;
      GtkBorder padding;
567
      GtkRequisition child_requisition;
568
      GtkAllocation child_allocation;
569
      guint horizontal_padding, toggle_spacing;
570
      gint toggle_size;
571

572
      toggle_size = GTK_MENU_ITEM (image_menu_item)->priv->toggle_size;
573
      gtk_widget_style_get (widget,
574 575 576 577
                            "horizontal-padding", &horizontal_padding,
                            "toggle-spacing", &toggle_spacing,
                            NULL);

578 579 580
      /* Man this is lame hardcoding action, but I can't
       * come up with a solution that's really better.
       */
581

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

584 585
      gtk_widget_get_allocation (widget, &widget_allocation);

586 587 588 589 590
      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));

591
      if (pack_dir == GTK_PACK_DIRECTION_LTR ||
592 593 594 595
          pack_dir == GTK_PACK_DIRECTION_RTL)
        {
          if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
              (pack_dir == GTK_PACK_DIRECTION_LTR))
596
            x = offset + horizontal_padding + padding.left +
597 598
               (toggle_size - toggle_spacing - child_requisition.width) / 2;
          else
599
            x = widget_allocation.width - offset - horizontal_padding - padding.right -
600 601
              toggle_size + toggle_spacing +
              (toggle_size - toggle_spacing - child_requisition.width) / 2;
602 603

          y = (widget_allocation.height - child_requisition.height) / 2;
604
        }
605
      else
606 607 608
        {
          if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
              (pack_dir == GTK_PACK_DIRECTION_TTB))
609
            y = offset + horizontal_padding + padding.top +
610 611
              (toggle_size - toggle_spacing - child_requisition.height) / 2;
          else
612
            y = widget_allocation.height - offset - horizontal_padding - padding.bottom -
613 614
              toggle_size + toggle_spacing +
              (toggle_size - toggle_spacing - child_requisition.height) / 2;
615

616
          x = (widget_allocation.width - child_requisition.width) / 2;
617 618
        }

619 620
      child_allocation.width = child_requisition.width;
      child_allocation.height = child_requisition.height;
621 622
      child_allocation.x = widget_allocation.x + MAX (x, 0);
      child_allocation.y = widget_allocation.y + MAX (y, 0);
623

624
      gtk_widget_size_allocate (priv->image, &child_allocation);
625 626 627 628 629
    }
}

static void
gtk_image_menu_item_forall (GtkContainer   *container,
630
                            gboolean        include_internals,
631 632 633
                            GtkCallback     callback,
                            gpointer        callback_data)
{
634
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (container);
635
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
636 637 638 639 640

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

642 643
  if (include_internals && priv->image)
    (* callback) (priv->image, callback_data);
644 645
}

646

647
static void
648 649 650
gtk_image_menu_item_activatable_interface_init (GtkActivatableIface  *iface)
{
  parent_activatable_iface = g_type_interface_peek_parent (iface);
651 652
  iface->update = gtk_image_menu_item_update;
  iface->sync_action_properties = gtk_image_menu_item_sync_action_properties;
653 654 655 656 657 658 659 660 661
}

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);
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 692 693 694 695 696 697 698
  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);
699 700

  if (GTK_IS_IMAGE (image) &&
701 702 703 704 705 706 707
      (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);
    }
}

708 709
static void
gtk_image_menu_item_update (GtkActivatable *activatable,
710 711
                            GtkAction      *action,
                            const gchar    *property_name)
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
{
  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);
}

732
static void
733
gtk_image_menu_item_sync_action_properties (GtkActivatable *activatable,
734
                                            GtkAction      *action)
735 736 737 738 739 740 741
{
  GtkImageMenuItem *image_menu_item;
  GtkWidget *image;
  gboolean   use_appearance;

  image_menu_item = GTK_IMAGE_MENU_ITEM (activatable);

742
  parent_activatable_iface->sync_action_properties (activatable, action);
743 744 745 746 747 748 749 750 751 752 753 754 755 756

  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;
    }
757

758 759 760 761 762
  if (!image)
    {
      image = gtk_image_new ();
      gtk_widget_show (image);
      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (activatable),
763
                                     image);
764
    }
765

766 767 768 769
  if (!activatable_update_stock_id (image_menu_item, action) &&
      !activatable_update_gicon (image_menu_item, action))
    activatable_update_icon_name (image_menu_item, action);

770 771
  gtk_image_menu_item_set_always_show_image (image_menu_item,
                                             gtk_action_get_always_show_image (action));
772 773 774
}


Matthias Clasen's avatar
Matthias Clasen committed
775 776 777 778
/**
 * gtk_image_menu_item_new:
 *
 * Creates a new #GtkImageMenuItem with an empty label.
Matthias Clasen's avatar
Matthias Clasen committed
779 780 781
 *
 * Returns: a new #GtkImageMenuItem
 */
782
GtkWidget*
783 784 785 786 787
gtk_image_menu_item_new (void)
{
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
}

Matthias Clasen's avatar
Matthias Clasen committed
788 789 790 791
/**
 * gtk_image_menu_item_new_with_label:
 * @label: the text of the menu item.
 *
792
 * Creates a new #GtkImageMenuItem containing a label.
Matthias Clasen's avatar
Matthias Clasen committed
793 794
 *
 * Returns: a new #GtkImageMenuItem.
795
 */
796 797
GtkWidget*
gtk_image_menu_item_new_with_label (const gchar *label)
798
{
799 800 801
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "label", label,
                       NULL);
802 803
}

804 805
/**
 * gtk_image_menu_item_new_with_mnemonic:
Matthias Clasen's avatar
Matthias Clasen committed
806
 * @label: the text of the menu item, with an underscore in front of the
807 808 809 810 811
 *         mnemonic character
 *
 * 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.
Matthias Clasen's avatar
Matthias Clasen committed
812 813
 *
 * Returns: a new #GtkImageMenuItem
814
 */
815 816 817
GtkWidget*
gtk_image_menu_item_new_with_mnemonic (const gchar *label)
{
818 819 820 821
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "use-underline", TRUE,
                       "label", label,
                       NULL);
822 823
}

Matthias Clasen's avatar
Matthias Clasen committed
824 825 826
/**
 * gtk_image_menu_item_new_from_stock:
 * @stock_id: the name of the stock item.
827
 * @accel_group: (allow-none): the #GtkAccelGroup to add the menu items
828
 *   accelerator to, or %NULL.
Matthias Clasen's avatar
Matthias Clasen committed
829
 *
830 831
 * 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
832
 * and #GTK_STOCK_APPLY.
833
 *
834 835 836 837 838
 * 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.
Matthias Clasen's avatar
Matthias Clasen committed
839 840
 *
 * Returns: a new #GtkImageMenuItem.
841
 */
842
GtkWidget*
843 844
gtk_image_menu_item_new_from_stock (const gchar   *stock_id,
                                    GtkAccelGroup *accel_group)
845
{
846 847 848 849 850
  return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM,
                       "label", stock_id,
                       "use-stock", TRUE,
                       "accel-group", accel_group,
                       NULL);
851
}
852

853 854 855 856 857 858 859 860 861 862 863 864
/**
 * 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,
865
                                   gboolean          use_stock)
866
{
867
  GtkImageMenuItemPrivate *priv;
868 869

  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
870

871
  priv = image_menu_item->priv;
872

873
  if (priv->use_stock != use_stock)
874
    {
875
      priv->use_stock = use_stock;
876

877 878 879 880 881 882 883 884 885 886 887 888 889 890
      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
891
 *     stock id to select the stock item for the item
892 893 894 895 896 897 898 899
 *
 * 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);

900
  return image_menu_item->priv->use_stock;
901 902
}

903 904 905 906 907
/**
 * gtk_image_menu_item_set_always_show_image:
 * @image_menu_item: a #GtkImageMenuItem
 * @always_show: %TRUE if the menuitem should always show the image
 *
908
 * If %TRUE, the menu item will ignore the #GtkSettings:gtk-menu-images
909 910 911
 * setting and always show the image, if available.
 *
 * Use this property if the menuitem would be useless or hard to use
912 913
 * without the image.
 *
914 915 916 917 918 919
 * Since: 2.16
 */
void
gtk_image_menu_item_set_always_show_image (GtkImageMenuItem *image_menu_item,
                                           gboolean          always_show)
{
920
  GtkImageMenuItemPrivate *priv;
921 922 923

  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));

924
  priv = image_menu_item->priv;
925 926 927 928 929

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

930
      if (priv->image)
931 932
        {
          if (show_image (image_menu_item))
933
            gtk_widget_show (priv->image);
934
          else
935
            gtk_widget_hide (priv->image);
936 937 938 939 940 941 942 943 944 945
        }

      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
946
 * Returns whether the menu item will ignore the #GtkSettings:gtk-menu-images
947
 * setting and always show the image, if available.
948
 *
949 950 951 952 953 954 955 956 957
 * 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);

958
  return image_menu_item->priv->always_show_image;
959 960
}

961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977

/**
 * 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
978 979
gtk_image_menu_item_set_accel_group (GtkImageMenuItem *image_menu_item,
                                     GtkAccelGroup    *accel_group)
980
{
981
  GtkImageMenuItemPrivate    *priv;
982 983 984
  GtkStockItem             stock_item;

  /* Silent return for the constructor */
985
  if (!accel_group)
986
    return;
987

988 989 990
  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));

991
  priv = image_menu_item->priv;
992 993 994 995

  if (priv->use_stock && priv->label && gtk_stock_lookup (priv->label, &stock_item))
    if (stock_item.keyval)
      {
996 997 998 999 1000 1001 1002 1003
        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");
1004
      }
1005 1006
}

1007
/**
Matthias Clasen's avatar
Matthias Clasen committed
1008 1009
 * gtk_image_menu_item_set_image:
 * @image_menu_item: a #GtkImageMenuItem.
1010 1011
 * @image: (allow-none): a widget to set as the image for the menu item.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1012
 * Sets the image of @image_menu_item to the given widget.
Matthias Clasen's avatar
Matthias Clasen committed
1013 1014
 * Note that it depends on the show-menu-images setting whether
 * the image will be displayed or not.
1015
 */
1016
void
1017 1018
gtk_image_menu_item_set_image (GtkImageMenuItem *image_menu_item,
                               GtkWidget        *image)
1019
{
1020
  GtkImageMenuItemPrivate *priv;
1021

1022
  g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
1023

1024 1025 1026
  priv = image_menu_item->priv;

  if (image == priv->image)
1027 1028
    return;

1029
  if (priv->image)
1030
    gtk_container_remove (GTK_CONTAINER (image_menu_item),
1031
                          priv->image);
1032

1033
  priv->image = image;
1034 1035 1036 1037 1038

  if (image == NULL)
    return;

  gtk_widget_set_parent (image, GTK_WIDGET (image_menu_item));
1039
  g_object_set (image,
1040 1041 1042
                "visible", show_image (image_menu_item),
                "no-show-all", TRUE,
                NULL);
1043

1044
  g_object_notify (G_OBJECT (image_menu_item), "image");
1045 1046
}

Matthias Clasen's avatar
Matthias Clasen committed
1047 1048
/**
 * gtk_image_menu_item_get_image:
1049
 * @image_menu_item: a #GtkImageMenuItem
Matthias Clasen's avatar
Matthias Clasen committed
1050 1051 1052
 *
 * Gets the widget that is currently set as the image of @image_menu_item.
 * See gtk_image_menu_item_set_image().
1053 1054
 *
 * Return value: (transfer none): the widget set as image of @image_menu_item
Matthias Clasen's avatar
Matthias Clasen committed
1055
 **/
1056 1057 1058 1059 1060
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);

1061
  return image_menu_item->priv->image;
1062 1063
}

1064
static void
1065 1066 1067
gtk_image_menu_item_remove (GtkContainer *container,
                            GtkWidget    *child)
{
1068
  GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (container);
1069
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
1070

1071
  if (child == priv->image)
1072 1073
    {
      gboolean widget_was_visible;
1074

1075
      widget_was_visible = gtk_widget_get_visible (child);
1076

1077
      gtk_widget_unparent (child);
1078 1079
      priv->image = NULL;

1080 1081
      if (widget_was_visible &&
          gtk_widget_get_visible (GTK_WIDGET (container)))
1082
        gtk_widget_queue_resize (GTK_WIDGET (container));
1083 1084

      g_object_notify (G_OBJECT (image_menu_item), "image");
1085 1086 1087
    }
  else
    {
1088
      GTK_CONTAINER_CLASS (gtk_image_menu_item_parent_class)->remove (container, child);
1089 1090 1091
    }
}

1092
static void
Matthias Clasen's avatar
Matthias Clasen committed
1093 1094
show_image_change_notify (GtkImageMenuItem *image_menu_item)
{
1095
  GtkImageMenuItemPrivate *priv = image_menu_item->priv;
1096 1097

  if (priv->image)
1098 1099
    {
      if (show_image (image_menu_item))
1100
        gtk_widget_show (priv->image);
1101
      else
1102
        gtk_widget_hide (priv->image);
1103 1104 1105 1106 1107
    }
}

static void
traverse_container (GtkWidget *widget,
1108
                    gpointer   data)
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
{
  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)
{
1119
  GList *list, *l;
1120 1121 1122

  list = gtk_window_list_toplevels ();

1123
  for (l = list; l; l = l->next)
1124 1125
    gtk_container_forall (GTK_CONTAINER (l->data),
                          traverse_container, NULL);
1126

1127
  g_list_free (list);
Matthias Clasen's avatar
Matthias Clasen committed
1128 1129 1130 1131
}

static void
gtk_image_menu_item_screen_changed (GtkWidget *widget,
1132
                                    GdkScreen *previous_screen)
Matthias Clasen's avatar
Matthias Clasen committed
1133 1134
{
  GtkSettings *settings;
1135
  gulong show_image_connection;
Matthias Clasen's avatar
Matthias Clasen committed
1136

1137
  if (!gtk_widget_has_screen (widget))
Matthias Clasen's avatar
Matthias Clasen committed
1138
    return;
1139

1140
  settings = gtk_widget_get_settings (widget);
1141 1142

  show_image_connection =
1143 1144
    g_signal_handler_find (settings, G_SIGNAL_MATCH_FUNC, 0, 0,
                           NULL, gtk_image_menu_item_setting_changed, NULL);
1145

1146 1147
  if (show_image_connection)
    return;
Matthias Clasen's avatar
Matthias Clasen committed
1148

1149 1150
  g_signal_connect (settings, "notify::gtk-menu-images",
                    G_CALLBACK (gtk_image_menu_item_setting_changed), NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1151 1152 1153

  show_image_change_notify (GTK_IMAGE_MENU_ITEM (widget));
}