gtkaccellabel.c 31.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Tim Janik's avatar
Tim Janik committed
2 3 4 5 6 7
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * GtkAccelLabel: GtkLabel with accelerator monitoring facilities.
 * Copyright (C) 1998 Tim Janik
 *
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
Tim Janik's avatar
Tim Janik committed
9 10 11 12 13 14
 * 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
15
 * Lesser General Public License for more details.
Tim Janik's avatar
Tim Janik committed
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Tim Janik's avatar
Tim Janik committed
19
 */
20 21

/*
22
 * Modified by the GTK+ Team and others 1997-2001.  See the AUTHORS
23 24 25 26
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */
Manish Singh's avatar
Manish Singh committed
27

28
#include "config.h"
Manish Singh's avatar
Manish Singh committed
29 30
#include <string.h>

31 32
#include "gtkaccellabel.h"
#include "gtkaccelmap.h"
33
#include "gtkintl.h"
Tim Janik's avatar
Tim Janik committed
34
#include "gtkmain.h"
35
#include "gtkprivate.h"
36 37
#include "gtkrender.h"
#include "gtksizerequest.h"
38
#include "gtkstylecontextprivate.h"
39

40 41 42 43
/**
 * SECTION:gtkaccellabel
 * @Short_description: A label which displays an accelerator key on the right of the text
 * @Title: GtkAccelLabel
44
 * @See_also: #GtkAccelGroup
45 46
 *
 * The #GtkAccelLabel widget is a subclass of #GtkLabel that also displays an
47
 * accelerator key on the right of the label text, e.g. “Ctl+S”.
48 49 50 51 52 53 54 55
 * It is commonly used in menus to show the keyboard short-cuts for commands.
 *
 * The accelerator key to display is not set explicitly.
 * Instead, the #GtkAccelLabel displays the accelerators which have been added to
 * a particular widget. This widget is set by calling
 * gtk_accel_label_set_accel_widget().
 *
 * For example, a #GtkMenuItem widget may have an accelerator added to emit the
56
 * “activate” signal when the “Ctl+S” key combination is pressed.
57 58
 * A #GtkAccelLabel is created and added to the #GtkMenuItem, and
 * gtk_accel_label_set_accel_widget() is called with the #GtkMenuItem as the
59
 * second argument. The #GtkAccelLabel will now display “Ctl+S” after its label.
60 61 62 63 64 65 66 67 68 69
 *
 * Note that creating a #GtkMenuItem with gtk_menu_item_new_with_label() (or
 * one of the similar functions for #GtkCheckMenuItem and #GtkRadioMenuItem)
 * automatically adds a #GtkAccelLabel to the #GtkMenuItem and calls
 * gtk_accel_label_set_accel_widget() to set it up for you.
 *
 * A #GtkAccelLabel will only display accelerators which have %GTK_ACCEL_VISIBLE
 * set (see #GtkAccelFlags).
 * A #GtkAccelLabel can display multiple accelerators and even signal names,
 * though it is almost always used to display just one accelerator key.
70 71 72
 *
 * ## Creating a simple menu item with an accelerator key.
 *
73
 * |[<!-- language="C" -->
74 75 76
 *   GtkWidget *save_item;
 *   GtkAccelGroup *accel_group;
 *
77
 *   // Create a GtkAccelGroup and add it to the window.
78
 *   accel_group = gtk_accel_group_new ();
79 80
 *   gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
 *
81
 *   // Create the menu item using the convenience function.
82 83 84 85
 *   save_item = gtk_menu_item_new_with_label ("Save");
 *   gtk_widget_show (save_item);
 *   gtk_container_add (GTK_CONTAINER (menu), save_item);
 *
86 87 88 89 90
 *   // Now add the accelerator to the GtkMenuItem. Note that since we
 *   // called gtk_menu_item_new_with_label() to create the GtkMenuItem
 *   // the GtkAccelLabel is automatically set up to display the
 *   // GtkMenuItem accelerators. We just need to make sure we use
 *   // GTK_ACCEL_VISIBLE here.
91
 *   gtk_widget_add_accelerator (save_item, "activate", accel_group,
Matthias Clasen's avatar
Matthias Clasen committed
92
 *                               GDK_KEY_s, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
93
 * ]|
94 95
 */

Tim Janik's avatar
Tim Janik committed
96
enum {
97
  PROP_0,
98
  PROP_ACCEL_CLOSURE,
99 100
  PROP_ACCEL_WIDGET,
  LAST_PROP
Tim Janik's avatar
Tim Janik committed
101 102
};

103 104
struct _GtkAccelLabelPrivate
{
105
  GtkWidget     *accel_widget;       /* done */
106 107 108
  GClosure      *accel_closure;      /* has set function */
  GtkAccelGroup *accel_group;        /* set by set_accel_closure() */
  gchar         *accel_string;       /* has set function */
109
  guint          accel_padding;      /* should be style property? */
110
  guint16        accel_string_width; /* seems to be private */
111 112 113

  guint           accel_key;         /* manual accel key specification if != 0 */
  GdkModifierType accel_mods;
114 115
};

116 117
GParamSpec *props[LAST_PROP] = { NULL, };

118 119 120 121 122 123 124 125
static void         gtk_accel_label_set_property (GObject            *object,
						  guint               prop_id,
						  const GValue       *value,
						  GParamSpec         *pspec);
static void         gtk_accel_label_get_property (GObject            *object,
						  guint               prop_id,
						  GValue             *value,
						  GParamSpec         *pspec);
126
static void         gtk_accel_label_destroy      (GtkWidget          *widget);
127
static void         gtk_accel_label_finalize     (GObject            *object);
128 129
static gboolean     gtk_accel_label_draw         (GtkWidget          *widget,
                                                  cairo_t            *cr);
130
static const gchar *gtk_accel_label_get_string   (GtkAccelLabel      *accel_label);
Tim Janik's avatar
Tim Janik committed
131

132

133 134 135
static void         gtk_accel_label_get_preferred_width (GtkWidget           *widget,
                                                         gint                *min_width,
                                                         gint                *nat_width);
136 137


138
G_DEFINE_TYPE_WITH_PRIVATE (GtkAccelLabel, gtk_accel_label, GTK_TYPE_LABEL)
Tim Janik's avatar
Tim Janik committed
139 140 141 142

static void
gtk_accel_label_class_init (GtkAccelLabelClass *class)
{
143
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
144
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
Tim Janik's avatar
Tim Janik committed
145
  
146
  gobject_class->finalize = gtk_accel_label_finalize;
147 148
  gobject_class->set_property = gtk_accel_label_set_property;
  gobject_class->get_property = gtk_accel_label_get_property;
149

150
  widget_class->draw = gtk_accel_label_draw;
151
  widget_class->get_preferred_width = gtk_accel_label_get_preferred_width;
152
  widget_class->destroy = gtk_accel_label_destroy;
Tim Janik's avatar
Tim Janik committed
153

154 155
  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_ACCEL_LABEL);

Tim Janik's avatar
Tim Janik committed
156 157
  class->signal_quote1 = g_strdup ("<:");
  class->signal_quote2 = g_strdup (":>");
158 159

#ifndef GDK_WINDOWING_QUARTZ
160 161 162 163 164
  /* This is the text that should appear next to menu accelerators
   * that use the shift key. If the text on this key isn't typically
   * translated on keyboards used for your language, don't translate
   * this.
   */
165
  class->mod_name_shift = g_strdup (C_("keyboard label", "Shift"));
166 167 168 169 170
  /* This is the text that should appear next to menu accelerators
   * that use the control key. If the text on this key isn't typically
   * translated on keyboards used for your language, don't translate
   * this.
   */
171
  class->mod_name_control = g_strdup (C_("keyboard label", "Ctrl"));
172 173 174 175 176
  /* This is the text that should appear next to menu accelerators
   * that use the alt key. If the text on this key isn't typically
   * translated on keyboards used for your language, don't translate
   * this.
   */
177
  class->mod_name_alt = g_strdup (C_("keyboard label", "Alt"));
Tim Janik's avatar
Tim Janik committed
178
  class->mod_separator = g_strdup ("+");
179 180 181 182 183 184 185 186 187 188 189 190
#else /* GDK_WINDOWING_QUARTZ */

  /* U+21E7 UPWARDS WHITE ARROW */
  class->mod_name_shift = g_strdup ("\xe2\x87\xa7");
  /* U+2303 UP ARROWHEAD */
  class->mod_name_control = g_strdup ("\xe2\x8c\x83");
  /* U+2325 OPTION KEY */
  class->mod_name_alt = g_strdup ("\xe2\x8c\xa5");
  class->mod_separator = g_strdup ("");

#endif /* GDK_WINDOWING_QUARTZ */

191 192 193 194 195
  props[PROP_ACCEL_CLOSURE] =
    g_param_spec_boxed ("accel-closure",
                        P_("Accelerator Closure"),
                        P_("The closure to be monitored for accelerator changes"),
                        G_TYPE_CLOSURE,
196
                        GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
197 198 199 200 201 202

  props[PROP_ACCEL_WIDGET] =
    g_param_spec_object ("accel-widget",
                         P_("Accelerator Widget"),
                         P_("The widget to be monitored for accelerator changes"),
                         GTK_TYPE_WIDGET,
203
                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
204 205

  g_object_class_install_properties (gobject_class, LAST_PROP, props);
Tim Janik's avatar
Tim Janik committed
206 207 208
}

static void
209 210 211 212
gtk_accel_label_set_property (GObject      *object,
			      guint         prop_id,
			      const GValue *value,
			      GParamSpec   *pspec)
Tim Janik's avatar
Tim Janik committed
213
{
214 215 216 217
  GtkAccelLabel  *accel_label;

  accel_label = GTK_ACCEL_LABEL (object);

218
  switch (prop_id)
Tim Janik's avatar
Tim Janik committed
219
    {
220 221 222 223 224
    case PROP_ACCEL_CLOSURE:
      gtk_accel_label_set_accel_closure (accel_label, g_value_get_boxed (value));
      break;
    case PROP_ACCEL_WIDGET:
      gtk_accel_label_set_accel_widget (accel_label, g_value_get_object (value));
Tim Janik's avatar
Tim Janik committed
225 226
      break;
    default:
227
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Tim Janik's avatar
Tim Janik committed
228 229 230 231
      break;
    }
}

232 233 234 235 236
static void
gtk_accel_label_get_property (GObject    *object,
			      guint       prop_id,
			      GValue     *value,
			      GParamSpec *pspec)
Tim Janik's avatar
Tim Janik committed
237
{
238 239 240 241
  GtkAccelLabel  *accel_label;

  accel_label = GTK_ACCEL_LABEL (object);

242
  switch (prop_id)
Tim Janik's avatar
Tim Janik committed
243
    {
244
    case PROP_ACCEL_CLOSURE:
245
      g_value_set_boxed (value, accel_label->priv->accel_closure);
246 247
      break;
    case PROP_ACCEL_WIDGET:
248
      g_value_set_object (value, accel_label->priv->accel_widget);
Tim Janik's avatar
Tim Janik committed
249 250
      break;
    default:
251
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Tim Janik's avatar
Tim Janik committed
252 253 254 255 256 257 258
      break;
    }
}

static void
gtk_accel_label_init (GtkAccelLabel *accel_label)
{
259 260
  GtkAccelLabelPrivate *priv;

261
  accel_label->priv = gtk_accel_label_get_instance_private (accel_label);
262
  priv = accel_label->priv;
263 264 265 266 267 268

  priv->accel_padding = 3;
  priv->accel_widget = NULL;
  priv->accel_closure = NULL;
  priv->accel_group = NULL;
  priv->accel_string = NULL;
Tim Janik's avatar
Tim Janik committed
269 270
}

271 272 273 274 275 276 277 278
/**
 * gtk_accel_label_new:
 * @string: the label string. Must be non-%NULL.
 *
 * Creates a new #GtkAccelLabel.
 *
 * Returns: a new #GtkAccelLabel.
 */
Tim Janik's avatar
Tim Janik committed
279 280 281 282 283 284 285
GtkWidget*
gtk_accel_label_new (const gchar *string)
{
  GtkAccelLabel *accel_label;
  
  g_return_val_if_fail (string != NULL, NULL);
  
Manish Singh's avatar
Manish Singh committed
286
  accel_label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
Tim Janik's avatar
Tim Janik committed
287
  
288
  gtk_label_set_text (GTK_LABEL (accel_label), string);
Tim Janik's avatar
Tim Janik committed
289 290 291 292 293
  
  return GTK_WIDGET (accel_label);
}

static void
294
gtk_accel_label_destroy (GtkWidget *widget)
Tim Janik's avatar
Tim Janik committed
295
{
296
  GtkAccelLabel *accel_label = GTK_ACCEL_LABEL (widget);
Tim Janik's avatar
Tim Janik committed
297

298 299
  gtk_accel_label_set_accel_widget (accel_label, NULL);
  gtk_accel_label_set_accel_closure (accel_label, NULL);
300 301

  GTK_WIDGET_CLASS (gtk_accel_label_parent_class)->destroy (widget);
Tim Janik's avatar
Tim Janik committed
302 303 304
}

static void
305
gtk_accel_label_finalize (GObject *object)
Tim Janik's avatar
Tim Janik committed
306
{
307 308
  GtkAccelLabel *accel_label = GTK_ACCEL_LABEL (object);

309 310
  g_free (accel_label->priv->accel_string);

Matthias Clasen's avatar
Matthias Clasen committed
311
  G_OBJECT_CLASS (gtk_accel_label_parent_class)->finalize (object);
Tim Janik's avatar
Tim Janik committed
312 313
}

314
/**
315
 * gtk_accel_label_get_accel_widget:
316 317 318
 * @accel_label: a #GtkAccelLabel
 *
 * Fetches the widget monitored by this accelerator label. See
319
 * gtk_accel_label_set_accel_widget().
320
 *
321
 * Returns: (transfer none): the object monitored by the accelerator label, or %NULL.
322
 **/
323 324
GtkWidget*
gtk_accel_label_get_accel_widget (GtkAccelLabel *accel_label)
325 326 327
{
  g_return_val_if_fail (GTK_IS_ACCEL_LABEL (accel_label), NULL);

328
  return accel_label->priv->accel_widget;
329 330
}

331 332 333 334 335 336 337 338 339 340
/**
 * gtk_accel_label_get_accel_width:
 * @accel_label: a #GtkAccelLabel.
 *
 * Returns the width needed to display the accelerator key(s).
 * This is used by menus to align all of the #GtkMenuItem widgets, and shouldn't
 * be needed by applications.
 *
 * Returns: the width needed to display the accelerator key(s).
 */
341
guint
342
gtk_accel_label_get_accel_width (GtkAccelLabel *accel_label)
343
{
344
  g_return_val_if_fail (GTK_IS_ACCEL_LABEL (accel_label), 0);
345 346 347

  return (accel_label->priv->accel_string_width +
	  (accel_label->priv->accel_string_width ? accel_label->priv->accel_padding : 0));
348 349
}

350 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
static PangoLayout *
gtk_accel_label_get_accel_layout (GtkAccelLabel *accel_label)
{
  GtkWidget *widget = GTK_WIDGET (accel_label);
  GtkStyleContext *context;
  PangoAttrList *attrs;
  PangoLayout *layout;
  PangoFontDescription *font_desc;

  context = gtk_widget_get_style_context (widget);

  gtk_style_context_save (context);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_ACCELERATOR);

  layout = gtk_widget_create_pango_layout (widget, gtk_accel_label_get_string (accel_label));

  attrs = _gtk_style_context_get_pango_attributes (context);
  if (!attrs)
    attrs = pango_attr_list_new ();
  gtk_style_context_get (context,
                         gtk_widget_get_state_flags (widget),
                         "font", &font_desc,
                         NULL);
  pango_attr_list_change (attrs, pango_attr_font_desc_new (font_desc));
  pango_font_description_free (font_desc);
  pango_layout_set_attributes (layout, attrs);
  pango_attr_list_unref (attrs);

  gtk_style_context_restore (context);

  return layout;
}

Tim Janik's avatar
Tim Janik committed
383
static void
384 385 386
gtk_accel_label_get_preferred_width (GtkWidget *widget,
                                     gint      *min_width,
                                     gint      *nat_width)
Tim Janik's avatar
Tim Janik committed
387
{
388
  GtkAccelLabel *accel_label = GTK_ACCEL_LABEL (widget);
389 390
  PangoLayout   *layout;
  gint           width;
391

392
  GTK_WIDGET_CLASS (gtk_accel_label_parent_class)->get_preferred_width (widget, min_width, nat_width);
393

394
  layout = gtk_accel_label_get_accel_layout (accel_label);
395
  pango_layout_get_pixel_size (layout, &width, NULL);
396 397
  accel_label->priv->accel_string_width = width;

Manish Singh's avatar
Manish Singh committed
398
  g_object_unref (layout);
Tim Janik's avatar
Tim Janik committed
399 400
}

401 402 403 404 405 406 407 408 409 410 411 412 413
static gint
get_first_baseline (PangoLayout *layout)
{
  PangoLayoutIter *iter;
  gint result;

  iter = pango_layout_get_iter (layout);
  result = pango_layout_iter_get_baseline (iter);
  pango_layout_iter_free (iter);

  return PANGO_PIXELS (result);
}

414
static gboolean 
415 416
gtk_accel_label_draw (GtkWidget *widget,
                      cairo_t   *cr)
Tim Janik's avatar
Tim Janik committed
417
{
418
  GtkAccelLabel *accel_label = GTK_ACCEL_LABEL (widget);
419
  GtkTextDirection direction;
420 421 422
  guint ac_width;
  GtkAllocation allocation;
  GtkRequisition requisition;
423 424

  direction = gtk_widget_get_direction (widget);
425 426
  ac_width = gtk_accel_label_get_accel_width (accel_label);
  gtk_widget_get_allocation (widget, &allocation);
427
  gtk_widget_get_preferred_size (widget, NULL, &requisition);
428

429
  if (allocation.width >= requisition.width + ac_width)
Tim Janik's avatar
Tim Janik committed
430
    {
431
      GtkStyleContext *context;
432 433 434 435 436 437 438
      PangoLayout *label_layout;
      PangoLayout *accel_layout;
      GtkLabel *label = GTK_LABEL (widget);

      gint x;
      gint y;

439
      context = gtk_widget_get_style_context (widget);
440 441 442 443 444
      label_layout = gtk_label_get_layout (GTK_LABEL (accel_label));

      cairo_save (cr);

      /* XXX: Mad hack: We modify the label's width so it renders
445 446
       * properly in its draw function that we chain to.
       */
447 448 449 450 451 452 453 454 455
      if (direction == GTK_TEXT_DIR_RTL)
        cairo_translate (cr, ac_width, 0);
      if (gtk_label_get_ellipsize (label))
        pango_layout_set_width (label_layout,
                                pango_layout_get_width (label_layout) 
                                - ac_width * PANGO_SCALE);
      
      allocation.width -= ac_width;
      gtk_widget_set_allocation (widget, &allocation);
456
      GTK_WIDGET_CLASS (gtk_accel_label_parent_class)->draw (widget, cr);
457 458 459 460
      allocation.width += ac_width;
      gtk_widget_set_allocation (widget, &allocation);
      if (gtk_label_get_ellipsize (label))
        pango_layout_set_width (label_layout,
461
                                pango_layout_get_width (label_layout)
462 463 464 465 466
                                + ac_width * PANGO_SCALE);

      cairo_restore (cr);

      if (direction == GTK_TEXT_DIR_RTL)
467
        x = 0;
Tim Janik's avatar
Tim Janik committed
468
      else
469
        x = gtk_widget_get_allocated_width (widget) - ac_width;
470 471 472

      gtk_label_get_layout_offsets (GTK_LABEL (accel_label), NULL, &y);

473
      accel_layout = gtk_accel_label_get_accel_layout (accel_label);
474 475
      y += get_first_baseline (label_layout) - get_first_baseline (accel_layout) - allocation.y;

476 477 478 479
      gtk_style_context_save (context);
      gtk_style_context_add_class (context, GTK_STYLE_CLASS_ACCELERATOR);
      gtk_render_layout (context, cr, x, y, accel_layout);
      gtk_style_context_restore (context);
480 481 482 483 484

      g_object_unref (accel_layout);
    }
  else
    {
485
      GTK_WIDGET_CLASS (gtk_accel_label_parent_class)->draw (widget, cr);
Tim Janik's avatar
Tim Janik committed
486 487
    }
  
488
  return FALSE;
Tim Janik's avatar
Tim Janik committed
489 490
}

491 492 493
static void
refetch_widget_accel_closure (GtkAccelLabel *accel_label)
{
494 495 496
  GClosure *closure = NULL;
  GList *clist, *list;
  
497
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));
498
  g_return_if_fail (GTK_IS_WIDGET (accel_label->priv->accel_widget));
499
  
500
  clist = gtk_widget_list_accel_closures (accel_label->priv->accel_widget);
501 502 503 504 505 506 507 508
  for (list = clist; list; list = list->next)
    {
      /* we just take the first closure used */
      closure = list->data;
      break;
    }
  g_list_free (clist);
  gtk_accel_label_set_accel_closure (accel_label, closure);
509 510
}

511 512 513 514 515 516 517 518 519 520 521
static void
accel_widget_weak_ref_cb (GtkAccelLabel *accel_label,
                          GtkWidget     *old_accel_widget)
{
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));
  g_return_if_fail (GTK_IS_WIDGET (accel_label->priv->accel_widget));

  g_signal_handlers_disconnect_by_func (accel_label->priv->accel_widget,
                                        refetch_widget_accel_closure,
                                        accel_label);
  accel_label->priv->accel_widget = NULL;
522
  g_object_notify_by_pspec (G_OBJECT (accel_label), props[PROP_ACCEL_WIDGET]);
523 524
}

Matthias Clasen's avatar
Matthias Clasen committed
525 526 527 528 529
/**
 * gtk_accel_label_set_accel_widget:
 * @accel_label: a #GtkAccelLabel
 * @accel_widget: the widget to be monitored.
 *
530 531
 * Sets the widget to be monitored by this accelerator label.
 */
532 533
void
gtk_accel_label_set_accel_widget (GtkAccelLabel *accel_label,
534
                                  GtkWidget     *accel_widget)
535 536 537 538
{
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));
  if (accel_widget)
    g_return_if_fail (GTK_IS_WIDGET (accel_widget));
539

540
  if (accel_widget != accel_label->priv->accel_widget)
541
    {
542
      if (accel_label->priv->accel_widget)
543 544 545 546 547 548 549 550
        {
          gtk_accel_label_set_accel_closure (accel_label, NULL);
          g_signal_handlers_disconnect_by_func (accel_label->priv->accel_widget,
                                                refetch_widget_accel_closure,
                                                accel_label);
          g_object_weak_unref (G_OBJECT (accel_label->priv->accel_widget),
                               (GWeakNotify) accel_widget_weak_ref_cb, accel_label);
        }
551 552
      accel_label->priv->accel_widget = accel_widget;
      if (accel_label->priv->accel_widget)
553 554 555 556 557 558 559 560
        {
          g_object_weak_ref (G_OBJECT (accel_label->priv->accel_widget),
                             (GWeakNotify) accel_widget_weak_ref_cb, accel_label);
          g_signal_connect_object (accel_label->priv->accel_widget, "accel-closures-changed",
                                   G_CALLBACK (refetch_widget_accel_closure),
                                   accel_label, G_CONNECT_SWAPPED);
          refetch_widget_accel_closure (accel_label);
        }
561
      g_object_notify_by_pspec (G_OBJECT (accel_label), props[PROP_ACCEL_WIDGET]);
562 563 564
    }
}

565 566 567
static void
gtk_accel_label_reset (GtkAccelLabel *accel_label)
{
568
  g_clear_pointer (&accel_label->priv->accel_string, g_free);
569 570 571 572
  
  gtk_widget_queue_resize (GTK_WIDGET (accel_label));
}

573 574 575 576 577 578 579
static void
check_accel_changed (GtkAccelGroup  *accel_group,
		     guint           keyval,
		     GdkModifierType modifier,
		     GClosure       *accel_closure,
		     GtkAccelLabel  *accel_label)
{
580
  if (accel_closure == accel_label->priv->accel_closure)
581
    gtk_accel_label_reset (accel_label);
582 583
}

584 585 586 587 588 589 590 591
/**
 * gtk_accel_label_set_accel_closure:
 * @accel_label: a #GtkAccelLabel
 * @accel_closure: the closure to monitor for accelerator changes.
 *
 * Sets the closure to be monitored by this accelerator label. The closure
 * must be connected to an accelerator group; see gtk_accel_group_connect().
 **/
Tim Janik's avatar
Tim Janik committed
592
void
593 594
gtk_accel_label_set_accel_closure (GtkAccelLabel *accel_label,
				   GClosure      *accel_closure)
Tim Janik's avatar
Tim Janik committed
595 596
{
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));
597 598 599
  if (accel_closure)
    g_return_if_fail (gtk_accel_group_from_accel_closure (accel_closure) != NULL);

600
  if (accel_closure != accel_label->priv->accel_closure)
Tim Janik's avatar
Tim Janik committed
601
    {
602
      if (accel_label->priv->accel_closure)
Tim Janik's avatar
Tim Janik committed
603
	{
604
	  g_signal_handlers_disconnect_by_func (accel_label->priv->accel_group,
Manish Singh's avatar
Manish Singh committed
605
						check_accel_changed,
606
						accel_label);
607 608
	  accel_label->priv->accel_group = NULL;
	  g_closure_unref (accel_label->priv->accel_closure);
Tim Janik's avatar
Tim Janik committed
609
	}
610 611
      accel_label->priv->accel_closure = accel_closure;
      if (accel_label->priv->accel_closure)
Tim Janik's avatar
Tim Janik committed
612
	{
613 614 615
	  g_closure_ref (accel_label->priv->accel_closure);
	  accel_label->priv->accel_group = gtk_accel_group_from_accel_closure (accel_closure);
	  g_signal_connect_object (accel_label->priv->accel_group, "accel-changed",
616 617
				   G_CALLBACK (check_accel_changed),
				   accel_label, 0);
Tim Janik's avatar
Tim Janik committed
618
	}
619
      gtk_accel_label_reset (accel_label);
620
      g_object_notify_by_pspec (G_OBJECT (accel_label), props[PROP_ACCEL_CLOSURE]);
Tim Janik's avatar
Tim Janik committed
621 622 623
    }
}

624 625 626 627 628 629 630 631
static gboolean
find_accel (GtkAccelKey *key,
	    GClosure    *closure,
	    gpointer     data)
{
  return data == (gpointer) closure;
}

632 633 634
static const gchar *
gtk_accel_label_get_string (GtkAccelLabel *accel_label)
{
635
  if (!accel_label->priv->accel_string)
636 637
    gtk_accel_label_refetch (accel_label);
  
638
  return accel_label->priv->accel_string;
639 640
}

641
/* Underscores in key names are better displayed as spaces
642
 * E.g., Page_Up should be “Page Up”.
643 644 645 646 647 648
 *
 * Some keynames also have prefixes that are not suitable
 * for display, e.g XF86AudioMute, so strip those out, too.
 *
 * This function is only called on untranslated keynames,
 * so no need to be UTF-8 safe.
649 650
 */
static void
651 652
append_without_underscores (GString *s,
                            gchar   *str)
653
{
654
  gchar *p;
655

656
  if (g_str_has_prefix (str, "XF86"))
657
    p = str + 4;
658
  else if (g_str_has_prefix (str, "ISO_"))
659 660 661
    p = str + 4;
  else
    p = str;
662

663 664 665 666 667 668 669
  for ( ; *p; p++)
    {
      if (*p == '_')
        g_string_append_c (s, ' ');
      else
        g_string_append_c (s, *p);
    }
670 671
}

672 673 674 675 676 677 678 679 680 681 682
/* On Mac, if the key has symbolic representation (e.g. arrow keys),
 * append it to gstring and return TRUE; otherwise return FALSE.
 * See http://docs.info.apple.com/article.html?path=Mac/10.5/en/cdb_symbs.html 
 * for the list of special keys. */
static gboolean
append_keyval_symbol (guint    accelerator_key,
                      GString *gstring)
{
#ifdef GDK_WINDOWING_QUARTZ
  switch (accelerator_key)
  {
683
  case GDK_KEY_Return:
684 685 686 687
    /* U+21A9 LEFTWARDS ARROW WITH HOOK */
    g_string_append (gstring, "\xe2\x86\xa9");
    return TRUE;

688
  case GDK_KEY_ISO_Enter:
689 690 691 692
    /* U+2324 UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS */
    g_string_append (gstring, "\xe2\x8c\xa4");
    return TRUE;

693
  case GDK_KEY_Left:
694 695 696 697
    /* U+2190 LEFTWARDS ARROW */
    g_string_append (gstring, "\xe2\x86\x90");
    return TRUE;

698
  case GDK_KEY_Up:
699 700 701 702
    /* U+2191 UPWARDS ARROW */
    g_string_append (gstring, "\xe2\x86\x91");
    return TRUE;

703
  case GDK_KEY_Right:
704 705 706 707
    /* U+2192 RIGHTWARDS ARROW */
    g_string_append (gstring, "\xe2\x86\x92");
    return TRUE;

708
  case GDK_KEY_Down:
709 710 711 712
    /* U+2193 DOWNWARDS ARROW */
    g_string_append (gstring, "\xe2\x86\x93");
    return TRUE;

713
  case GDK_KEY_Page_Up:
714 715 716 717
    /* U+21DE UPWARDS ARROW WITH DOUBLE STROKE */
    g_string_append (gstring, "\xe2\x87\x9e");
    return TRUE;

718
  case GDK_KEY_Page_Down:
719 720 721 722
    /* U+21DF DOWNWARDS ARROW WITH DOUBLE STROKE */
    g_string_append (gstring, "\xe2\x87\x9f");
    return TRUE;

723
  case GDK_KEY_Home:
724 725 726 727
    /* U+2196 NORTH WEST ARROW */
    g_string_append (gstring, "\xe2\x86\x96");
    return TRUE;

728
  case GDK_KEY_End:
729 730 731 732
    /* U+2198 SOUTH EAST ARROW */
    g_string_append (gstring, "\xe2\x86\x98");
    return TRUE;

733
  case GDK_KEY_Escape:
734 735 736 737
    /* U+238B BROKEN CIRCLE WITH NORTHWEST ARROW */
    g_string_append (gstring, "\xe2\x8e\x8b");
    return TRUE;

738
  case GDK_KEY_BackSpace:
739 740 741 742
    /* U+232B ERASE TO THE LEFT */
    g_string_append (gstring, "\xe2\x8c\xab");
    return TRUE;

743
  case GDK_KEY_Delete:
744 745 746 747 748 749 750 751 752 753 754 755
    /* U+2326 ERASE TO THE RIGHT */
    g_string_append (gstring, "\xe2\x8c\xa6");
    return TRUE;

  default:
    return FALSE;
  }
#else /* !GDK_WINDOWING_QUARTZ */
  return FALSE;
#endif
}

756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
gchar *
_gtk_accel_label_class_get_accelerator_label (GtkAccelLabelClass *klass,
					      guint               accelerator_key,
					      GdkModifierType     accelerator_mods)
{
  GString *gstring;
  gboolean seen_mod = FALSE;
  gunichar ch;
  
  gstring = g_string_new ("");
  
  if (accelerator_mods & GDK_SHIFT_MASK)
    {
      g_string_append (gstring, klass->mod_name_shift);
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_CONTROL_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);
      g_string_append (gstring, klass->mod_name_control);
      seen_mod = TRUE;
    }
779
  if (accelerator_mods & GDK_MOD1_MASK)
780 781 782 783 784 785
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);
      g_string_append (gstring, klass->mod_name_alt);
      seen_mod = TRUE;
    }
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
  if (accelerator_mods & GDK_MOD2_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      g_string_append (gstring, "Mod2");
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_MOD3_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      g_string_append (gstring, "Mod3");
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_MOD4_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      g_string_append (gstring, "Mod4");
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_MOD5_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      g_string_append (gstring, "Mod5");
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_SUPER_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      /* This is the text that should appear next to menu accelerators
       * that use the super key. If the text on this key isn't typically
       * translated on keyboards used for your language, don't translate
       * this.
       */
828
      g_string_append (gstring, C_("keyboard label", "Super"));
829 830 831 832 833 834 835 836 837 838 839 840
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_HYPER_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

      /* This is the text that should appear next to menu accelerators
       * that use the hyper key. If the text on this key isn't typically
       * translated on keyboards used for your language, don't translate
       * this.
       */
841
      g_string_append (gstring, C_("keyboard label", "Hyper"));
842 843 844 845 846 847 848
      seen_mod = TRUE;
    }
  if (accelerator_mods & GDK_META_MASK)
    {
      if (seen_mod)
	g_string_append (gstring, klass->mod_separator);

849
#ifndef GDK_WINDOWING_QUARTZ
850 851 852 853 854
      /* This is the text that should appear next to menu accelerators
       * that use the meta key. If the text on this key isn't typically
       * translated on keyboards used for your language, don't translate
       * this.
       */
855
      g_string_append (gstring, C_("keyboard label", "Meta"));
856 857 858 859
#else
      /* Command key symbol U+2318 PLACE OF INTEREST SIGN */
      g_string_append (gstring, "\xe2\x8c\x98");
#endif
860 861
      seen_mod = TRUE;
    }
862 863
  
  ch = gdk_keyval_to_unicode (accelerator_key);
864
  if (ch && ch < 0x80 && (g_unichar_isgraph (ch) || ch == ' '))
865
    {
866 867 868
      if (seen_mod)
        g_string_append (gstring, klass->mod_separator);

869 870 871
      switch (ch)
	{
	case ' ':
872
	  g_string_append (gstring, C_("keyboard label", "Space"));
873 874
	  break;
	case '\\':
875
	  g_string_append (gstring, C_("keyboard label", "Backslash"));
876 877 878 879 880 881
	  break;
	default:
	  g_string_append_unichar (gstring, g_unichar_toupper (ch));
	  break;
	}
    }
882
  else if (!append_keyval_symbol (accelerator_key, gstring))
883 884
    {
      gchar *tmp;
885 886

      tmp = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key));
887
      if (tmp != NULL)
888
	{
889 890 891
          if (seen_mod)
            g_string_append (gstring, klass->mod_separator);

892 893
	  if (tmp[0] != 0 && tmp[1] == 0)
	    g_string_append_c (gstring, g_ascii_toupper (tmp[0]));
894
	  else
895
	    {
896 897 898
	      const gchar *str;
              str = g_dpgettext2 (GETTEXT_PACKAGE, "keyboard label", tmp);
	      if (str == tmp)
899
                append_without_underscores (gstring, tmp);
900 901 902
	      else
		g_string_append (gstring, str);
	    }
903
	}
904 905 906 907 908
    }

  return g_string_free (gstring, FALSE);
}

909 910 911 912 913 914 915 916 917 918
/**
 * gtk_accel_label_refetch:
 * @accel_label: a #GtkAccelLabel.
 *
 * Recreates the string representing the accelerator keys.
 * This should not be needed since the string is automatically updated whenever
 * accelerators are added or removed from the associated widget.
 *
 * Returns: always returns %FALSE.
 */
Tim Janik's avatar
Tim Janik committed
919 920 921
gboolean
gtk_accel_label_refetch (GtkAccelLabel *accel_label)
{
922 923
  gboolean enable_accels;

Tim Janik's avatar
Tim Janik committed
924 925
  g_return_val_if_fail (GTK_IS_ACCEL_LABEL (accel_label), FALSE);

926
  g_clear_pointer (&accel_label->priv->accel_string, g_free);
927

928 929 930 931
  g_object_get (gtk_widget_get_settings (GTK_WIDGET (accel_label)),
                "gtk-enable-accels", &enable_accels,
                NULL);

932
  if (enable_accels && (accel_label->priv->accel_closure || accel_label->priv->accel_key))
Tim Janik's avatar
Tim Janik committed
933
    {
934 935 936 937 938 939 940 941 942 943 944
      gboolean have_accel = FALSE;
      guint accel_key;
      GdkModifierType accel_mods;

      /* First check for a manual accel set with _set_accel() */
      if (accel_label->priv->accel_key)
        {
          accel_mods = accel_label->priv->accel_mods;
          accel_key = accel_label->priv->accel_key;
          have_accel = TRUE;
        }
945 946

      /* If we don't have a hardcoded value, check the accel group */
947
      if (!have_accel)
948
        {
949 950 951
          GtkAccelKey *key;

          key = gtk_accel_group_find (accel_label->priv->accel_group, find_accel, accel_label->priv->accel_closure);
952 953 954 955 956

          if (key && key->accel_flags & GTK_ACCEL_VISIBLE)
            {
              accel_key = key->accel_key;
              accel_mods = key->accel_mods;
957
              have_accel = TRUE;
958 959
            }
        }
960

961
      /* If we found a key using either method, set it */
962
      if (have_accel)
Tim Janik's avatar
Tim Janik committed
963
	{
964 965 966
	  GtkAccelLabelClass *klass;

	  klass = GTK_ACCEL_LABEL_GET_CLASS (accel_label);
967 968
	  accel_label->priv->accel_string =
	      _gtk_accel_label_class_get_accelerator_label (klass, accel_key, accel_mods);
Tim Janik's avatar
Tim Janik committed
969
	}
970 971 972 973

      else
        /* Otherwise we have a closure with no key.  Show "-/-". */
        accel_label->priv->accel_string = g_strdup ("-/-");
Tim Janik's avatar
Tim Janik committed
974
    }
975

976 977
  if (!accel_label->priv->accel_string)
    accel_label->priv->accel_string = g_strdup ("");
Tim Janik's avatar
Tim Janik committed
978 979 980 981 982

  gtk_widget_queue_resize (GTK_WIDGET (accel_label));

  return FALSE;
}
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004

/**
 * gtk_accel_label_set_accel:
 * @accel_label: a #GtkAccelLabel
 * @accelerator_key: a keyval, or 0
 * @accelerator_mods: the modifier mask for the accel
 *
 * Manually sets a keyval and modifier mask as the accelerator rendered
 * by @accel_label.
 *
 * If a keyval and modifier are explicitly set then these values are
 * used regardless of any associated accel closure or widget.
 *
 * Providing an @accelerator_key of 0 removes the manual setting.
 *
 * Since: 3.6
 */
void
gtk_accel_label_set_accel (GtkAccelLabel   *accel_label,
                           guint            accelerator_key,
                           GdkModifierType  accelerator_mods)
{
1005 1006
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));

1007 1008 1009 1010 1011
  accel_label->priv->accel_key = accelerator_key;
  accel_label->priv->accel_mods = accelerator_mods;

  gtk_accel_label_reset (accel_label);
}
1012 1013 1014 1015

/**
 * gtk_accel_label_get_accel:
 * @accel_label: a #GtkAccelLabel
1016 1017
 * @accelerator_key: (out): return location for the keyval
 * @accelerator_mods: (out): return location for the modifier mask
1018
 *
1019
 * Gets the keyval and modifier mask set with
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
 * gtk_accel_label_set_accel().
 *
 * Since: 3.12
 */
void
gtk_accel_label_get_accel (GtkAccelLabel   *accel_label,
                           guint           *accelerator_key,
                           GdkModifierType *accelerator_mods)
{
  g_return_if_fail (GTK_IS_ACCEL_LABEL (accel_label));

  *accelerator_key = accel_label->priv->accel_key;
  *accelerator_mods = accel_label->priv->accel_mods;
}