gtkaccelgroup.c 44 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2
 * Copyright (C) 1998, 2001 Tim Janik
Tim Janik's avatar
Tim Janik committed
3 4
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Tim Janik's avatar
Tim Janik committed
6 7 8 9 10 11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
12
 * Lesser General Public License for more details.
Tim Janik's avatar
Tim Janik committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Tim Janik's avatar
Tim Janik committed
15 16 17 18
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
19 20

/*
21
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22 23 24 25
 * 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
26

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

Tim Janik's avatar
Tim Janik committed
31
#include "gtkaccelgroup.h"
32
#include "gtkaccelgroupprivate.h"
33
#include "gtkaccellabel.h" /* For _gtk_accel_label_class_get_accelerator_label */
34
#include "gtkaccelmap.h"
35
#include "gtkintl.h"
Manish Singh's avatar
Manish Singh committed
36
#include "gtkmain.h"		/* For _gtk_boolean_handled_accumulator */
Tim Janik's avatar
Tim Janik committed
37
#include "gdk/gdkkeysyms.h"
38
#include "gtkmarshalers.h"
39

40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
/**
 * SECTION:gtkaccelgroup
 * @Short_description: Groups of global keyboard accelerators for an entire GtkWindow
 * @Title: Accelerator Groups
 * @See_also:gtk_window_add_accel_group(), gtk_accel_map_change_entry(),
 * gtk_item_factory_new(), gtk_label_new_with_mnemonic()
 * 
 * A #GtkAccelGroup represents a group of keyboard accelerators,
 * typically attached to a toplevel #GtkWindow (with
 * gtk_window_add_accel_group()). Usually you won't need to create a
 * #GtkAccelGroup directly; instead, when using #GtkItemFactory, GTK+
 * automatically sets up the accelerators for your menus in the item
 * factory's #GtkAccelGroup.
 * 
 * 
 * Note that <firstterm>accelerators</firstterm> are different from
 * <firstterm>mnemonics</firstterm>. Accelerators are shortcuts for
 * activating a menu item; they appear alongside the menu item they're a
 * shortcut for. For example "Ctrl+Q" might appear alongside the "Quit"
 * menu item. Mnemonics are shortcuts for GUI elements such as text
 * entries or buttons; they appear as underlined characters. See
 * gtk_label_new_with_mnemonic(). Menu items can have both accelerators
 * and mnemonics, of course.
 */

66 67
#define GTK_ACCEL_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_ACCEL_GROUP, GtkAccelGroupPrivate))

68
/* --- prototypes --- */
Tim Janik's avatar
Tim Janik committed
69 70 71 72 73 74 75
static void gtk_accel_group_finalize     (GObject    *object);
static void gtk_accel_group_get_property (GObject    *object,
                                          guint       param_id,
                                          GValue     *value,
                                          GParamSpec *pspec);
static void accel_closure_invalidate     (gpointer    data,
                                          GClosure   *closure);
76

Tim Janik's avatar
Tim Janik committed
77 78

/* --- variables --- */
79 80 81 82 83 84 85 86 87
static guint  signal_accel_activate      = 0;
static guint  signal_accel_changed       = 0;
static guint  quark_acceleratable_groups = 0;
static guint  default_accel_mod_mask     = (GDK_SHIFT_MASK   |
                                            GDK_CONTROL_MASK |
                                            GDK_MOD1_MASK    |
                                            GDK_SUPER_MASK   |
                                            GDK_HYPER_MASK   |
                                            GDK_META_MASK);
88

Tim Janik's avatar
Tim Janik committed
89

Tim Janik's avatar
Tim Janik committed
90
enum {
91 92 93
  PROP_0,
  PROP_IS_LOCKED,
  PROP_MODIFIER_MASK,
Tim Janik's avatar
Tim Janik committed
94 95
};

Matthias Clasen's avatar
Matthias Clasen committed
96
G_DEFINE_TYPE (GtkAccelGroup, gtk_accel_group, G_TYPE_OBJECT)
97

Matthias Clasen's avatar
Matthias Clasen committed
98
/* --- functions --- */
99
static void
100
gtk_accel_group_class_init (GtkAccelGroupClass *class)
Tim Janik's avatar
Tim Janik committed
101
{
102 103 104 105 106
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  quark_acceleratable_groups = g_quark_from_static_string ("gtk-acceleratable-accel-groups");

  object_class->finalize = gtk_accel_group_finalize;
Tim Janik's avatar
Tim Janik committed
107
  object_class->get_property = gtk_accel_group_get_property;
108 109

  class->accel_changed = NULL;
110

Tim Janik's avatar
Tim Janik committed
111
  g_object_class_install_property (object_class,
112 113 114 115 116 117
                                   PROP_IS_LOCKED,
                                   g_param_spec_boolean ("is-locked",
                                                         "Is locked",
                                                         "Is the accel group locked",
                                                         FALSE,
                                                         G_PARAM_READABLE));
118

Tim Janik's avatar
Tim Janik committed
119 120
  g_object_class_install_property (object_class,
                                   PROP_MODIFIER_MASK,
121 122 123 124
                                   g_param_spec_flags ("modifier-mask",
                                                       "Modifier Mask",
                                                       "Modifier Mask",
                                                       GDK_TYPE_MODIFIER_TYPE,
Matthias Clasen's avatar
Matthias Clasen committed
125
                                                       default_accel_mod_mask,
126
                                                       G_PARAM_READABLE));
Tim Janik's avatar
Tim Janik committed
127

128 129 130 131 132 133 134 135 136 137 138 139
  /**
   * GtkAccelGroup::accel-activate:
   * @accel_group: the #GtkAccelGroup which received the signal
   * @acceleratable: the object on which the accelerator was activated
   * @keyval: the accelerator keyval
   * @modifier: the modifier combination of the accelerator
   *
   * The accel-activate signal is an implementation detail of
   * #GtkAccelGroup and not meant to be used by applications.
   * 
   * Returns: %TRUE if the accelerator was activated
   */
Manish Singh's avatar
Manish Singh committed
140
  signal_accel_activate =
141
    g_signal_new (I_("accel-activate"),
Manish Singh's avatar
Manish Singh committed
142 143 144 145 146 147 148 149 150
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_DETAILED,
		  0,
		  _gtk_boolean_handled_accumulator, NULL,
		  _gtk_marshal_BOOLEAN__OBJECT_UINT_FLAGS,
		  G_TYPE_BOOLEAN, 3,
		  G_TYPE_OBJECT,
		  G_TYPE_UINT,
		  GDK_TYPE_MODIFIER_TYPE);
151 152 153 154 155 156 157 158 159 160 161 162 163 164
  /**
   * GtkAccelGroup::accel-changed:
   * @accel_group: the #GtkAccelGroup which received the signal
   * @keyval: the accelerator keyval
   * @modifier: the modifier combination of the accelerator
   * @accel_closure: the #GClosure of the accelerator
   *
   * The accel-changed signal is emitted when a #GtkAccelGroupEntry
   * is added to or removed from the accel group. 
   *
   * Widgets like #GtkAccelLabel which display an associated 
   * accelerator should connect to this signal, and rebuild 
   * their visual representation if the @accel_closure is theirs.
   */
Manish Singh's avatar
Manish Singh committed
165
  signal_accel_changed =
166
    g_signal_new (I_("accel-changed"),
Manish Singh's avatar
Manish Singh committed
167 168 169 170 171 172 173 174 175
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
		  G_STRUCT_OFFSET (GtkAccelGroupClass, accel_changed),
		  NULL, NULL,
		  _gtk_marshal_VOID__UINT_FLAGS_BOXED,
		  G_TYPE_NONE, 3,
		  G_TYPE_UINT,
		  GDK_TYPE_MODIFIER_TYPE,
		  G_TYPE_CLOSURE);
176 177

  g_type_class_add_private (object_class, sizeof (GtkAccelGroupPrivate));
178 179
}

180 181
static void
gtk_accel_group_finalize (GObject *object)
182
{
183
  GtkAccelGroup *accel_group = GTK_ACCEL_GROUP (object);
184 185
  guint i;
  
186
  for (i = 0; i < accel_group->priv->n_accels; i++)
187
    {
188
      GtkAccelGroupEntry *entry = &accel_group->priv->priv_accels[i];
189 190 191

      if (entry->accel_path_quark)
	{
192
	  const gchar *accel_path = g_quark_to_string (entry->accel_path_quark);
193 194 195

	  _gtk_accel_map_remove_group (accel_path, accel_group);
	}
196
      g_closure_remove_invalidate_notifier (entry->closure, accel_group, accel_closure_invalidate);
197 198 199

      /* remove quick_accel_add() refcount */
      g_closure_unref (entry->closure);
200
    }
201

202
  g_free (accel_group->priv->priv_accels);
203

Matthias Clasen's avatar
Matthias Clasen committed
204
  G_OBJECT_CLASS (gtk_accel_group_parent_class)->finalize (object);
Tim Janik's avatar
Tim Janik committed
205 206
}

Tim Janik's avatar
Tim Janik committed
207 208 209 210 211 212 213 214
static void
gtk_accel_group_get_property (GObject    *object,
                              guint       param_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  GtkAccelGroup *accel_group = GTK_ACCEL_GROUP (object);

215 216 217
  switch (param_id)
    {
    case PROP_IS_LOCKED:
218
      g_value_set_boolean (value, accel_group->priv->lock_count > 0);
219 220
      break;
    case PROP_MODIFIER_MASK:
221
      g_value_set_flags (value, accel_group->priv->modifier_mask);
222 223 224 225 226
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    }
Tim Janik's avatar
Tim Janik committed
227 228
}

229 230
static void
gtk_accel_group_init (GtkAccelGroup *accel_group)
Tim Janik's avatar
Tim Janik committed
231
{
232 233 234 235 236 237 238 239 240
  GtkAccelGroupPrivate *priv = GTK_ACCEL_GROUP_GET_PRIVATE (accel_group);

  priv->lock_count = 0;
  priv->modifier_mask = gtk_accelerator_get_default_mod_mask ();
  priv->acceleratables = NULL;
  priv->n_accels = 0;
  priv->priv_accels = NULL;

  accel_group->priv = priv;
Tim Janik's avatar
Tim Janik committed
241 242
}

243
/**
Matthias Clasen's avatar
Matthias Clasen committed
244
 * gtk_accel_group_new:
245
 * @returns: a new #GtkAccelGroup object
246
 * 
247 248
 * Creates a new #GtkAccelGroup. 
 */
Tim Janik's avatar
Tim Janik committed
249
GtkAccelGroup*
250
gtk_accel_group_new (void)
Tim Janik's avatar
Tim Janik committed
251
{
252
  return g_object_new (GTK_TYPE_ACCEL_GROUP, NULL);
Tim Janik's avatar
Tim Janik committed
253 254
}

255 256 257
/**
 * gtk_accel_group_get_is_locked:
 * @accel_group: a #GtkAccelGroup
258
 *
259
 * Locks are added and removed using gtk_accel_group_lock() and
260
 * gtk_accel_group_unlock().
261
 *
Matthias Clasen's avatar
Matthias Clasen committed
262 263 264
 * Returns: %TRUE if there are 1 or more locks on the @accel_group,
 * %FALSE otherwise.
 *
265
 * Since: 2.14
266
 */
267
gboolean
268
gtk_accel_group_get_is_locked (GtkAccelGroup *accel_group)
Tim Janik's avatar
Tim Janik committed
269
{
270
  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), FALSE);
Tim Janik's avatar
Tim Janik committed
271

272
  return accel_group->priv->lock_count > 0;
Tim Janik's avatar
Tim Janik committed
273 274
}

275 276 277
/**
 * gtk_accel_group_get_modifier_mask:
 * @accel_group: a #GtkAccelGroup
278
 *
Matthias Clasen's avatar
Matthias Clasen committed
279
 * Gets a #GdkModifierType representing the mask for this
280
 * @accel_group. For example, #GDK_CONTROL_MASK, #GDK_SHIFT_MASK, etc.
281
 *
Matthias Clasen's avatar
Matthias Clasen committed
282 283
 * Returns: the modifier mask for this accel group.
 *
284
 * Since: 2.14
285
 */
Tim Janik's avatar
Tim Janik committed
286 287 288 289 290
GdkModifierType
gtk_accel_group_get_modifier_mask (GtkAccelGroup *accel_group)
{
  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), 0);

291
  return accel_group->priv->modifier_mask;
Tim Janik's avatar
Tim Janik committed
292 293
}

Tim Janik's avatar
Tim Janik committed
294
static void
295 296
accel_group_weak_ref_detach (GSList  *free_list,
			     GObject *stale_object)
Tim Janik's avatar
Tim Janik committed
297
{
298
  GSList *slist;
Tim Janik's avatar
Tim Janik committed
299 300 301 302 303 304
  
  for (slist = free_list; slist; slist = slist->next)
    {
      GtkAccelGroup *accel_group;
      
      accel_group = slist->data;
305
      accel_group->priv->acceleratables = g_slist_remove (accel_group->priv->acceleratables, stale_object);
306
      g_object_unref (accel_group);
Tim Janik's avatar
Tim Janik committed
307 308
    }
  g_slist_free (free_list);
309
  g_object_set_qdata (stale_object, quark_acceleratable_groups, NULL);
Tim Janik's avatar
Tim Janik committed
310 311 312
}

void
313 314
_gtk_accel_group_attach (GtkAccelGroup *accel_group,
			 GObject       *object)
Tim Janik's avatar
Tim Janik committed
315 316 317
{
  GSList *slist;
  
318
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
319
  g_return_if_fail (G_IS_OBJECT (object));
320
  g_return_if_fail (g_slist_find (accel_group->priv->acceleratables, object) == NULL);
Tim Janik's avatar
Tim Janik committed
321
  
322
  g_object_ref (accel_group);
323
  accel_group->priv->acceleratables = g_slist_prepend (accel_group->priv->acceleratables, object);
324
  slist = g_object_get_qdata (object, quark_acceleratable_groups);
325
  if (slist)
326 327 328
    g_object_weak_unref (object,
			 (GWeakNotify) accel_group_weak_ref_detach,
			 slist);
Tim Janik's avatar
Tim Janik committed
329
  slist = g_slist_prepend (slist, accel_group);
330 331 332 333
  g_object_set_qdata (object, quark_acceleratable_groups, slist);
  g_object_weak_ref (object,
		     (GWeakNotify) accel_group_weak_ref_detach,
		     slist);
Tim Janik's avatar
Tim Janik committed
334 335 336
}

void
337 338
_gtk_accel_group_detach (GtkAccelGroup *accel_group,
			 GObject       *object)
Tim Janik's avatar
Tim Janik committed
339 340 341
{
  GSList *slist;
  
342
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
343
  g_return_if_fail (G_IS_OBJECT (object));
344
  g_return_if_fail (g_slist_find (accel_group->priv->acceleratables, object) != NULL);
Tim Janik's avatar
Tim Janik committed
345
  
346
  accel_group->priv->acceleratables = g_slist_remove (accel_group->priv->acceleratables, object);
347 348 349 350
  slist = g_object_get_qdata (object, quark_acceleratable_groups);
  g_object_weak_unref (object,
		       (GWeakNotify) accel_group_weak_ref_detach,
		       slist);
Tim Janik's avatar
Tim Janik committed
351
  slist = g_slist_remove (slist, accel_group);
352
  g_object_set_qdata (object, quark_acceleratable_groups, slist);
353
  if (slist)
354 355 356 357 358 359
    g_object_weak_ref (object,
		       (GWeakNotify) accel_group_weak_ref_detach,
		       slist);
  g_object_unref (accel_group);
}

360 361
/**
 * gtk_accel_groups_from_object:
362
 * @object:        a #GObject, usually a #GtkWindow
363 364
 *
 * Gets a list of all accel groups which are attached to @object.
365 366
 *
 * Returns: (element-type GtkAccelGroup) (transfer none): a list of all accel groups which are attached to @object
367
 */
368
GSList*
369
gtk_accel_groups_from_object (GObject *object)
370 371 372 373 374 375
{
  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
  
  return g_object_get_qdata (object, quark_acceleratable_groups);
}

376 377 378 379 380 381 382 383
/**
 * gtk_accel_group_find:
 * @accel_group: a #GtkAccelGroup
 * @find_func: a function to filter the entries of @accel_group with
 * @data: data to pass to @find_func
 * @returns: the key of the first entry passing @find_func. The key is 
 * owned by GTK+ and must not be freed.
 *
Matthias Clasen's avatar
Matthias Clasen committed
384 385
 * Finds the first entry in an accelerator group for which 
 * @find_func returns %TRUE and returns its #GtkAccelKey.
386 387
 *
 */
388
GtkAccelKey*
389 390 391
gtk_accel_group_find (GtkAccelGroup        *accel_group,
		      GtkAccelGroupFindFunc find_func,
		      gpointer              data)
392 393 394 395 396 397 398 399
{
  GtkAccelKey *key = NULL;
  guint i;

  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), NULL);
  g_return_val_if_fail (find_func != NULL, NULL);

  g_object_ref (accel_group);
400 401 402
  for (i = 0; i < accel_group->priv->n_accels; i++)
    if (find_func (&accel_group->priv->priv_accels[i].key,
		   accel_group->priv->priv_accels[i].closure,
403 404
		   data))
      {
405
	key = &accel_group->priv->priv_accels[i].key;
406 407 408 409 410
	break;
      }
  g_object_unref (accel_group);

  return key;
Tim Janik's avatar
Tim Janik committed
411 412
}

413
/**
Matthias Clasen's avatar
Matthias Clasen committed
414
 * gtk_accel_group_lock:
415 416
 * @accel_group: a #GtkAccelGroup
 * 
Matthias Clasen's avatar
Matthias Clasen committed
417 418
 * Locks the given accelerator group.
 *
419 420 421
 * Locking an acelerator group prevents the accelerators contained
 * within it to be changed during runtime. Refer to
 * gtk_accel_map_change_entry() about runtime accelerator changes.
422 423 424 425
 *
 * If called more than once, @accel_group remains locked until
 * gtk_accel_group_unlock() has been called an equivalent number
 * of times.
426
 */
Tim Janik's avatar
Tim Janik committed
427
void
428
gtk_accel_group_lock (GtkAccelGroup *accel_group)
Tim Janik's avatar
Tim Janik committed
429
{
430
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
Tim Janik's avatar
Tim Janik committed
431
  
432
  accel_group->priv->lock_count += 1;
Tim Janik's avatar
Tim Janik committed
433

434
  if (accel_group->priv->lock_count == 1) {
435
    /* State change from unlocked to locked */
436
    g_object_notify (G_OBJECT (accel_group), "is-locked");
437
  }
Tim Janik's avatar
Tim Janik committed
438 439
}

440
/**
Matthias Clasen's avatar
Matthias Clasen committed
441
 * gtk_accel_group_unlock:
442 443
 * @accel_group: a #GtkAccelGroup
 * 
Matthias Clasen's avatar
Matthias Clasen committed
444
 * Undoes the last call to gtk_accel_group_lock() on this @accel_group.
445
 */
Tim Janik's avatar
Tim Janik committed
446
void
447
gtk_accel_group_unlock (GtkAccelGroup *accel_group)
Tim Janik's avatar
Tim Janik committed
448
{
449
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
450
  g_return_if_fail (accel_group->priv->lock_count > 0);
Tim Janik's avatar
Tim Janik committed
451

452
  accel_group->priv->lock_count -= 1;
Tim Janik's avatar
Tim Janik committed
453

454
  if (accel_group->priv->lock_count < 1) {
455
    /* State change from locked to unlocked */
456
    g_object_notify (G_OBJECT (accel_group), "is-locked");
457
  }
Tim Janik's avatar
Tim Janik committed
458 459
}

460
static void
461 462
accel_closure_invalidate (gpointer  data,
			  GClosure *closure)
Tim Janik's avatar
Tim Janik committed
463
{
464 465 466
  GtkAccelGroup *accel_group = GTK_ACCEL_GROUP (data);

  gtk_accel_group_disconnect (accel_group, closure);
Tim Janik's avatar
Tim Janik committed
467 468
}

469 470 471
static int
bsearch_compare_accels (const void *d1,
			const void *d2)
Tim Janik's avatar
Tim Janik committed
472
{
473 474 475 476 477 478 479
  const GtkAccelGroupEntry *entry1 = d1;
  const GtkAccelGroupEntry *entry2 = d2;

  if (entry1->key.accel_key == entry2->key.accel_key)
    return entry1->key.accel_mods < entry2->key.accel_mods ? -1 : entry1->key.accel_mods > entry2->key.accel_mods;
  else
    return entry1->key.accel_key < entry2->key.accel_key ? -1 : 1;
Tim Janik's avatar
Tim Janik committed
480 481
}

482 483 484 485 486 487 488
static void
quick_accel_add (GtkAccelGroup  *accel_group,
		 guint           accel_key,
		 GdkModifierType accel_mods,
		 GtkAccelFlags   accel_flags,
		 GClosure       *closure,
		 GQuark          path_quark)
Tim Janik's avatar
Tim Janik committed
489
{
490
  guint pos, i = accel_group->priv->n_accels++;
491 492
  GtkAccelGroupEntry key;

493
  /* find position */
494 495 496
  key.key.accel_key = accel_key;
  key.key.accel_mods = accel_mods;
  for (pos = 0; pos < i; pos++)
497
    if (bsearch_compare_accels (&key, accel_group->priv->priv_accels + pos) < 0)
498
      break;
499 500

  /* insert at position, ref closure */
501 502 503 504 505 506 507 508
  accel_group->priv->priv_accels = g_renew (GtkAccelGroupEntry, accel_group->priv->priv_accels, accel_group->priv->n_accels);
  g_memmove (accel_group->priv->priv_accels + pos + 1, accel_group->priv->priv_accels + pos,
	     (i - pos) * sizeof (accel_group->priv->priv_accels[0]));
  accel_group->priv->priv_accels[pos].key.accel_key = accel_key;
  accel_group->priv->priv_accels[pos].key.accel_mods = accel_mods;
  accel_group->priv->priv_accels[pos].key.accel_flags = accel_flags;
  accel_group->priv->priv_accels[pos].closure = g_closure_ref (closure);
  accel_group->priv->priv_accels[pos].accel_path_quark = path_quark;
509
  g_closure_sink (closure);
510 511 512 513 514 515 516 517 518 519 520 521 522
  
  /* handle closure invalidation and reverse lookups */
  g_closure_add_invalidate_notifier (closure, accel_group, accel_closure_invalidate);

  /* get accel path notification */
  if (path_quark)
    _gtk_accel_map_add_group (g_quark_to_string (path_quark), accel_group);

  /* connect and notify changed */
  if (accel_key)
    {
      gchar *accel_name = gtk_accelerator_name (accel_key, accel_mods);
      GQuark accel_quark = g_quark_from_string (accel_name);
523

524 525 526 527 528 529 530 531 532 533 534 535
      g_free (accel_name);
      
      /* setup handler */
      g_signal_connect_closure_by_id (accel_group, signal_accel_activate, accel_quark, closure, FALSE);
      
      /* and notify */
      g_signal_emit (accel_group, signal_accel_changed, accel_quark, accel_key, accel_mods, closure);
    }
}

static void
quick_accel_remove (GtkAccelGroup      *accel_group,
Matthias Clasen's avatar
Fixes  
Matthias Clasen committed
536
                    guint               pos)
537 538
{
  GQuark accel_quark = 0;
539
  GtkAccelGroupEntry *entry = accel_group->priv->priv_accels + pos;
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
  guint accel_key = entry->key.accel_key;
  GdkModifierType accel_mods = entry->key.accel_mods;
  GClosure *closure = entry->closure;

  /* quark for notification */
  if (accel_key)
    {
      gchar *accel_name = gtk_accelerator_name (accel_key, accel_mods);

      accel_quark = g_quark_from_string (accel_name);
      g_free (accel_name);
    }

  /* clean up closure invalidate notification and disconnect */
  g_closure_remove_invalidate_notifier (entry->closure, accel_group, accel_closure_invalidate);
  if (accel_quark)
    g_signal_handlers_disconnect_matched (accel_group,
					  G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DETAIL | G_SIGNAL_MATCH_CLOSURE,
					  signal_accel_activate, accel_quark,
					  closure, NULL, NULL);
  /* clean up accel path notification */
  if (entry->accel_path_quark)
    _gtk_accel_map_remove_group (g_quark_to_string (entry->accel_path_quark), accel_group);

  /* physically remove */
565
  accel_group->priv->n_accels -= 1;
566
  g_memmove (entry, entry + 1,
567
	     (accel_group->priv->n_accels - pos) * sizeof (accel_group->priv->priv_accels[0]));
568 569 570 571 572 573 574

  /* and notify */
  if (accel_quark)
    g_signal_emit (accel_group, signal_accel_changed, accel_quark, accel_key, accel_mods, closure);

  /* remove quick_accel_add() refcount */
  g_closure_unref (closure);
Tim Janik's avatar
Tim Janik committed
575 576
}

577 578 579 580 581
static GtkAccelGroupEntry*
quick_accel_find (GtkAccelGroup  *accel_group,
		  guint           accel_key,
		  GdkModifierType accel_mods,
		  guint		 *count_p)
Tim Janik's avatar
Tim Janik committed
582
{
583 584 585
  GtkAccelGroupEntry *entry;
  GtkAccelGroupEntry key;

586 587
  *count_p = 0;

588
  if (!accel_group->priv->n_accels)
589 590 591 592
    return NULL;

  key.key.accel_key = accel_key;
  key.key.accel_mods = accel_mods;
593 594
  entry = bsearch (&key, accel_group->priv->priv_accels, accel_group->priv->n_accels,
		   sizeof (accel_group->priv->priv_accels[0]), bsearch_compare_accels);
Tim Janik's avatar
Tim Janik committed
595
  
596 597 598 599
  if (!entry)
    return NULL;

  /* step back to the first member */
600
  for (; entry > accel_group->priv->priv_accels; entry--)
601 602 603 604
    if (entry[-1].key.accel_key != accel_key ||
	entry[-1].key.accel_mods != accel_mods)
      break;
  /* count equal members */
605
  for (; entry + *count_p < accel_group->priv->priv_accels + accel_group->priv->n_accels; (*count_p)++)
606 607 608 609
    if (entry[*count_p].key.accel_key != accel_key ||
	entry[*count_p].key.accel_mods != accel_mods)
      break;
  return entry;
Tim Janik's avatar
Tim Janik committed
610 611
}

612
/**
Matthias Clasen's avatar
Matthias Clasen committed
613 614
 * gtk_accel_group_connect:
 * @accel_group:      the accelerator group to install an accelerator in
615 616 617 618
 * @accel_key:        key value of the accelerator
 * @accel_mods:       modifier combination of the accelerator
 * @accel_flags:      a flag mask to configure this accelerator
 * @closure:          closure to be executed upon accelerator activation
619
 *
Matthias Clasen's avatar
Matthias Clasen committed
620
 * Installs an accelerator in this group. When @accel_group is being activated
621 622 623
 * in response to a call to gtk_accel_groups_activate(), @closure will be
 * invoked if the @accel_key and @accel_mods from gtk_accel_groups_activate()
 * match those of this connection.
Matthias Clasen's avatar
Matthias Clasen committed
624
 *
625
 * The signature used for the @closure is that of #GtkAccelGroupActivate.
Matthias Clasen's avatar
Matthias Clasen committed
626
 * 
627 628
 * Note that, due to implementation details, a single closure can only be
 * connected to one accelerator group.
629
 */
Tim Janik's avatar
Tim Janik committed
630
void
631 632 633 634
gtk_accel_group_connect (GtkAccelGroup	*accel_group,
			 guint		 accel_key,
			 GdkModifierType accel_mods,
			 GtkAccelFlags	 accel_flags,
635
			 GClosure	*closure)
Tim Janik's avatar
Tim Janik committed
636
{
637 638 639
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
  g_return_if_fail (closure != NULL);
  g_return_if_fail (accel_key > 0);
640
  g_return_if_fail (gtk_accel_group_from_accel_closure (closure) == NULL);
641

642 643
  g_object_ref (accel_group);
  if (!closure->is_invalid)
644 645 646
    quick_accel_add (accel_group,
		     gdk_keyval_to_lower (accel_key),
		     accel_mods, accel_flags, closure, 0);
647
  g_object_unref (accel_group);
648 649
}

650
/**
Matthias Clasen's avatar
Matthias Clasen committed
651 652
 * gtk_accel_group_connect_by_path:
 * @accel_group:      the accelerator group to install an accelerator in
653 654 655
 * @accel_path:       path used for determining key and modifiers.
 * @closure:          closure to be executed upon accelerator activation
 *
Matthias Clasen's avatar
Matthias Clasen committed
656 657
 * Installs an accelerator in this group, using an accelerator path to look
 * up the appropriate key and modifiers (see gtk_accel_map_add_entry()).
658 659 660 661
 * When @accel_group is being activated in response to a call to
 * gtk_accel_groups_activate(), @closure will be invoked if the @accel_key and
 * @accel_mods from gtk_accel_groups_activate() match the key and modifiers
 * for the path.
Matthias Clasen's avatar
Matthias Clasen committed
662
 *
663
 * The signature used for the @closure is that of #GtkAccelGroupActivate.
664 665 666 667
 * 
 * Note that @accel_path string will be stored in a #GQuark. Therefore, if you
 * pass a static string, you can save some memory by interning it first with 
 * g_intern_static_string().
668 669 670 671 672
 */
void
gtk_accel_group_connect_by_path (GtkAccelGroup	*accel_group,
				 const gchar    *accel_path,
				 GClosure	*closure)
673
{
674 675 676
  guint accel_key = 0;
  GdkModifierType accel_mods = 0;
  GtkAccelKey key;
677

678 679 680
  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
  g_return_if_fail (closure != NULL);
  g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
681

682 683
  if (closure->is_invalid)
    return;
684

685
  g_object_ref (accel_group);
686

687 688
  if (gtk_accel_map_lookup_entry (accel_path, &key))
    {
Tim Janik's avatar
Tim Janik committed
689
      accel_key = gdk_keyval_to_lower (key.accel_key);
690 691 692 693 694
      accel_mods = key.accel_mods;
    }

  quick_accel_add (accel_group, accel_key, accel_mods, GTK_ACCEL_VISIBLE, closure,
		   g_quark_from_string (accel_path));
695

696
  g_object_unref (accel_group);
Tim Janik's avatar
Tim Janik committed
697 698
}

699
/**
Matthias Clasen's avatar
Matthias Clasen committed
700
 * gtk_accel_group_disconnect:
701
 * @accel_group: the accelerator group to remove an accelerator from
Johan Dahlin's avatar
Johan Dahlin committed
702
 * @closure: (allow-none):     the closure to remove from this accelerator group, or %NULL
703
 *               to remove all closures
704 705
 * @returns:     %TRUE if the closure was found and got disconnected
 *
Matthias Clasen's avatar
Matthias Clasen committed
706
 * Removes an accelerator previously installed through
707
 * gtk_accel_group_connect().
708 709
 *
 * Since 2.20 @closure can be %NULL.
710 711 712 713 714 715 716 717 718
 */
gboolean
gtk_accel_group_disconnect (GtkAccelGroup *accel_group,
			    GClosure      *closure)
{
  guint i;

  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), FALSE);

719 720
  for (i = 0; i < accel_group->priv->n_accels; i++)
    if (accel_group->priv->priv_accels[i].closure == closure)
721 722
      {
	g_object_ref (accel_group);
Matthias Clasen's avatar
Fixes  
Matthias Clasen committed
723
	quick_accel_remove (accel_group, i);
724 725 726 727 728 729 730
	g_object_unref (accel_group);
	return TRUE;
      }
  return FALSE;
}

/**
Matthias Clasen's avatar
Matthias Clasen committed
731 732
 * gtk_accel_group_disconnect_key:
 * @accel_group:      the accelerator group to install an accelerator in
733 734
 * @accel_key:        key value of the accelerator
 * @accel_mods:       modifier combination of the accelerator
Matthias Clasen's avatar
Matthias Clasen committed
735 736
 * @returns:          %TRUE if there was an accelerator which could be 
 *                    removed, %FALSE otherwise
737
 *
Matthias Clasen's avatar
Matthias Clasen committed
738
 * Removes an accelerator previously installed through
739 740 741
 * gtk_accel_group_connect().
 */
gboolean
742 743 744
gtk_accel_group_disconnect_key (GtkAccelGroup  *accel_group,
				guint	        accel_key,
				GdkModifierType accel_mods)
Tim Janik's avatar
Tim Janik committed
745
{
746 747 748 749 750
  GtkAccelGroupEntry *entries;
  GSList *slist, *clist = NULL;
  gboolean removed_one = FALSE;
  guint n;

751 752
  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), FALSE);

753 754
  g_object_ref (accel_group);
  
755
  accel_key = gdk_keyval_to_lower (accel_key);
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
  entries = quick_accel_find (accel_group, accel_key, accel_mods, &n);
  while (n--)
    {
      GClosure *closure = g_closure_ref (entries[n].closure);

      clist = g_slist_prepend (clist, closure);
    }

  for (slist = clist; slist; slist = slist->next)
    {
      GClosure *closure = slist->data;

      removed_one |= gtk_accel_group_disconnect (accel_group, closure);
      g_closure_unref (closure);
    }
  g_slist_free (clist);

  g_object_unref (accel_group);

  return removed_one;
}

void
_gtk_accel_group_reconnect (GtkAccelGroup *accel_group,
			    GQuark         accel_path_quark)
{
  GSList *slist, *clist = NULL;
  guint i;

  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));

  g_object_ref (accel_group);

789 790
  for (i = 0; i < accel_group->priv->n_accels; i++)
    if (accel_group->priv->priv_accels[i].accel_path_quark == accel_path_quark)
791
      {
792
	GClosure *closure = g_closure_ref (accel_group->priv->priv_accels[i].closure);
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807

	clist = g_slist_prepend (clist, closure);
      }

  for (slist = clist; slist; slist = slist->next)
    {
      GClosure *closure = slist->data;

      gtk_accel_group_disconnect (accel_group, closure);
      gtk_accel_group_connect_by_path (accel_group, g_quark_to_string (accel_path_quark), closure);
      g_closure_unref (closure);
    }
  g_slist_free (clist);

  g_object_unref (accel_group);
Tim Janik's avatar
Tim Janik committed
808 809
}

810 811 812 813 814 815 816 817
GSList*
_gtk_accel_group_get_accelerables (GtkAccelGroup *accel_group)
{
    g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), NULL);

    return accel_group->priv->acceleratables;
}

818 819 820 821 822
/**
 * gtk_accel_group_query:
 * @accel_group:      the accelerator group to query
 * @accel_key:        key value of the accelerator
 * @accel_mods:       modifier combination of the accelerator
Johan Dahlin's avatar
Johan Dahlin committed
823 824
 * @n_entries: (allow-none):        location to return the number of entries found, or %NULL
 * @returns: (allow-none):          an array of @n_entries #GtkAccelGroupEntry elements, or %NULL. The array is owned by GTK+ and must not be freed. 
825 826 827 828
 *
 * Queries an accelerator group for all entries matching @accel_key and 
 * @accel_mods.
 */
829 830 831 832 833
GtkAccelGroupEntry*
gtk_accel_group_query (GtkAccelGroup  *accel_group,
		       guint           accel_key,
		       GdkModifierType accel_mods,
		       guint          *n_entries)
Tim Janik's avatar
Tim Janik committed
834
{
835 836
  GtkAccelGroupEntry *entries;
  guint n;
Tim Janik's avatar
Tim Janik committed
837

838 839
  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), NULL);

840
  entries = quick_accel_find (accel_group, gdk_keyval_to_lower (accel_key), accel_mods, &n);
841 842 843 844 845

  if (n_entries)
    *n_entries = entries ? n : 0;

  return entries;
Tim Janik's avatar
Tim Janik committed
846 847
}

848 849 850
/**
 * gtk_accel_group_from_accel_closure:
 * @closure: a #GClosure
Johan Dahlin's avatar
Johan Dahlin committed
851
 * @returns: (allow-none): the #GtkAccelGroup to which @closure is connected, or %NULL.
852 853 854 855
 *
 * Finds the #GtkAccelGroup to which @closure is connected; 
 * see gtk_accel_group_connect().
 */
856 857
GtkAccelGroup*
gtk_accel_group_from_accel_closure (GClosure *closure)
Tim Janik's avatar
Tim Janik committed
858
{
859 860 861 862
  guint i;

  g_return_val_if_fail (closure != NULL, NULL);

863
  /* a few remarks on what we do here. in general, we need a way to reverse lookup
864 865
   * accel_groups from closures that are being used in accel groups. this could
   * be done e.g via a hashtable. it is however cheaper (memory wise) to just
866 867 868
   * use the invalidation notifier on the closure itself (which we need to install
   * anyway), that contains the accel group as data which, besides needing to peek
   * a bit at closure internals, works just as good.
869 870
   */
  for (i = 0; i < G_CLOSURE_N_NOTIFIERS (closure); i++)
871
    if (closure->notifiers[i].notify == accel_closure_invalidate)
872 873 874
      return closure->notifiers[i].data;

  return NULL;
Tim Janik's avatar
Tim Janik committed
875 876
}

Matthias Clasen's avatar
Matthias Clasen committed
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
/**
 * gtk_accel_group_activate:
 * @accel_group:   a #GtkAccelGroup
 * @accel_quark:   the quark for the accelerator name
 * @acceleratable: the #GObject, usually a #GtkWindow, on which
 *                 to activate the accelerator.
 * @accel_key:     accelerator keyval from a key event
 * @accel_mods:    keyboard state mask from a key event
 * 
 * Finds the first accelerator in @accel_group 
 * that matches @accel_key and @accel_mods, and
 * activates it.
 *
 * Returns: %TRUE if an accelerator was activated and handled this keypress
 */
892
gboolean
893 894 895 896 897
gtk_accel_group_activate (GtkAccelGroup   *accel_group,
                          GQuark	   accel_quark,
                          GObject	  *acceleratable,
                          guint	           accel_key,
                          GdkModifierType  accel_mods)
Tim Janik's avatar
Tim Janik committed
898
{
899 900 901
  gboolean was_handled;

  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), FALSE);
902 903
  g_return_val_if_fail (G_IS_OBJECT (acceleratable), FALSE);
  
904 905 906 907 908
  was_handled = FALSE;
  g_signal_emit (accel_group, signal_accel_activate, accel_quark,
		 acceleratable, accel_key, accel_mods, &was_handled);

  return was_handled;
Tim Janik's avatar
Tim Janik committed
909 910
}

911 912
/**
 * gtk_accel_groups_activate:
913
 * @object:        the #GObject, usually a #GtkWindow, on which
914
 *                 to activate the accelerator.
915 916 917 918
 * @accel_key:     accelerator keyval from a key event
 * @accel_mods:    keyboard state mask from a key event
 * 
 * Finds the first accelerator in any #GtkAccelGroup attached
919
 * to @object that matches @accel_key and @accel_mods, and
920
 * activates that accelerator.
Matthias Clasen's avatar
Matthias Clasen committed
921 922
 *
 * Returns: %TRUE if an accelerator was activated and handled this keypress
923 924
 */
gboolean
925
gtk_accel_groups_activate (GObject	  *object,
926 927
			   guint	   accel_key,
			   GdkModifierType accel_mods)
Tim Janik's avatar
Tim Janik committed
928
{
929
  g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
Tim Janik's avatar
Tim Janik committed
930
  
931 932 933 934 935
  if (gtk_accelerator_valid (accel_key, accel_mods))
    {
      gchar *accel_name;
      GQuark accel_quark;
      GSList *slist;
Tim Janik's avatar
Tim Janik committed
936

937
      accel_name = gtk_accelerator_name (accel_key, (accel_mods & gtk_accelerator_get_default_mod_mask ()));
938 939 940
      accel_quark = g_quark_from_string (accel_name);
      g_free (accel_name);
      
941
      for (slist = gtk_accel_groups_from_object (object); slist; slist = slist->next)
942
	if (gtk_accel_group_activate (slist->data, accel_quark, object, accel_key, accel_mods))
943 944
	  return TRUE;
    }
Tim Janik's avatar
Tim Janik committed
945
  
946
  return FALSE;
Tim Janik's avatar
Tim Janik committed
947 948
}

949
/**
Matthias Clasen's avatar
Matthias Clasen committed
950
 * gtk_accelerator_valid:
951
 * @keyval:    a GDK keyval
952
 * @modifiers: modifier mask
953
 * @returns:   %TRUE if the accelerator is valid
954 955
 * 
 * Determines whether a given keyval and modifier mask constitute
Matthias Clasen's avatar
Matthias Clasen committed
956 957
 * a valid keyboard accelerator. For example, the #GDK_a keyval
 * plus #GDK_CONTROL_MASK is valid - this is a "Ctrl+a" accelerator.
958
 * But, you can't, for instance, use the #GDK_Control_L keyval
959
 * as an accelerator.
960
 */
Tim Janik's avatar
Tim Janik committed
961 962
gboolean
gtk_accelerator_valid (guint		  keyval,
Tim Janik's avatar
Tim Janik committed
963
		       GdkModifierType	  modifiers)
Tim Janik's avatar
Tim Janik committed
964
{
965
  static const guint invalid_accelerator_vals[] = {
966 967 968 969 970 971 972 973 974 975 976
    GDK_KEY_Shift_L, GDK_KEY_Shift_R, GDK_KEY_Shift_Lock, GDK_KEY_Caps_Lock, GDK_KEY_ISO_Lock,
    GDK_KEY_Control_L, GDK_KEY_Control_R, GDK_KEY_Meta_L, GDK_KEY_Meta_R,
    GDK_KEY_Alt_L, GDK_KEY_Alt_R, GDK_KEY_Super_L, GDK_KEY_Super_R, GDK_KEY_Hyper_L, GDK_KEY_Hyper_R,
    GDK_KEY_ISO_Level3_Shift, GDK_KEY_ISO_Next_Group, GDK_KEY_ISO_Prev_Group,
    GDK_KEY_ISO_First_Group, GDK_KEY_ISO_Last_Group,
    GDK_KEY_Mode_switch, GDK_KEY_Num_Lock, GDK_KEY_Multi_key,
    GDK_KEY_Scroll_Lock, GDK_KEY_Sys_Req, 
    GDK_KEY_Tab, GDK_KEY_ISO_Left_Tab, GDK_KEY_KP_Tab,
    GDK_KEY_First_Virtual_Screen, GDK_KEY_Prev_Virtual_Screen,
    GDK_KEY_Next_Virtual_Screen, GDK_KEY_Last_Virtual_Screen,
    GDK_KEY_Terminate_Server, GDK_KEY_AudibleBell_Enable,
Tim Janik's avatar
Tim Janik committed
977 978
    0
  };
979
  static const guint invalid_unmodified_vals[] = {
980 981
    GDK_KEY_Up, GDK_KEY_Down, GDK_KEY_Left, GDK_KEY_Right,
    GDK_KEY_KP_Up, GDK_KEY_KP_Down, GDK_KEY_KP_Left, GDK_KEY_KP_Right,
982 983
    0
  };
984
  const guint *ac_val;
Tim Janik's avatar
Tim Janik committed
985 986 987 988 989 990 991 992 993 994 995 996 997

  modifiers &= GDK_MODIFIER_MASK;
    
  if (keyval <= 0xFF)
    return keyval >= 0x20;

  ac_val = invalid_accelerator_vals;
  while (*ac_val)
    {
      if (keyval == *ac_val++)
	return FALSE;
    }

998 999 1000 1001 1002 1003 1004 1005 1006 1007
  if (!modifiers)
    {
      ac_val = invalid_unmodified_vals;
      while (*ac_val)
	{
	  if (keyval == *ac_val++)
	    return FALSE;
	}
    }
  
Tim Janik's avatar
Tim Janik committed
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
  return TRUE;
}

static inline gboolean
is_alt (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'a' || string[1] == 'A') &&
	  (string[2] == 'l' || string[2] == 'L') &&
	  (string[3] == 't' || string[3] == 'T') &&
	  (string[4] == '>'));
}

static inline gboolean
is_ctl (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'c' || string[1] == 'C') &&
	  (string[2] == 't' || string[2] == 'T') &&
	  (string[3] == 'l' || string[3] == 'L') &&
	  (string[4] == '>'));
}

static inline gboolean
is_modx (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'm' || string[1] == 'M') &&
	  (string[2] == 'o' || string[2] == 'O') &&
	  (string[3] == 'd' || string[3] == 'D') &&
	  (string[4] >= '1' && string[4] <= '5') &&
	  (string[5] == '>'));
}

static inline gboolean
is_ctrl (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'c' || string[1] == 'C') &&
	  (string[2] == 't' || string[2] == 'T') &&
	  (string[3] == 'r' || string[3] == 'R') &&
	  (string[4] == 'l' || string[4] == 'L') &&
	  (string[5] == '>'));
}

static inline gboolean
is_shft (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 's' || string[1] == 'S') &&
	  (string[2] == 'h' || string[2] == 'H') &&
	  (string[3] == 'f' || string[3] == 'F') &&
	  (string[4] == 't' || string[4] == 'T') &&
	  (string[5] == '>'));
}

static inline gboolean
is_shift (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 's' || string[1] == 'S') &&
	  (string[2] == 'h' || string[2] == 'H') &&
	  (string[3] == 'i' || string[3] == 'I') &&
	  (string[4] == 'f' || string[4] == 'F') &&
	  (string[5] == 't' || string[5] == 'T') &&
	  (string[6] == '>'));
}

static inline gboolean
is_control (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'c' || string[1] == 'C') &&
	  (string[2] == 'o' || string[2] == 'O') &&
	  (string[3] == 'n' || string[3] == 'N') &&
	  (string[4] == 't' || string[4] == 'T') &&
	  (string[5] == 'r' || string[5] == 'R') &&
	  (string[6] == 'o' || string[6] == 'O') &&
	  (string[7] == 'l' || string[7] == 'L') &&
	  (string[8] == '>'));
}

1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
static inline gboolean
is_release (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'r' || string[1] == 'R') &&
	  (string[2] == 'e' || string[2] == 'E') &&
	  (string[3] == 'l' || string[3] == 'L') &&
	  (string[4] == 'e' || string[4] == 'E') &&
	  (string[5] == 'a' || string[5] == 'A') &&
	  (string[6] == 's' || string[6] == 'S') &&
	  (string[7] == 'e' || string[7] == 'E') &&
	  (string[8] == '>'));
}

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
static inline gboolean
is_meta (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'm' || string[1] == 'M') &&
	  (string[2] == 'e' || string[2] == 'E') &&
	  (string[3] == 't' || string[3] == 'T') &&
	  (string[4] == 'a' || string[4] == 'A') &&
	  (string[5] == '>'));
}

static inline gboolean
is_super (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 's' || string[1] == 'S') &&
	  (string[2] == 'u' || string[2] == 'U') &&
	  (string[3] == 'p' || string[3] == 'P') &&
	  (string[4] == 'e' || string[4] == 'E') &&
	  (string[5] == 'r' || string[5] == 'R') &&
	  (string[6] == '>'));
}

static inline gboolean
is_hyper (const gchar *string)
{
  return ((string[0] == '<') &&
	  (string[1] == 'h' || string[1] == 'H') &&
	  (string[2] == 'y' || string[2] == 'Y') &&
	  (string[3] == 'p' || string[3] == 'P') &&
	  (string[4] == 'e' || string[4] == 'E') &&
	  (string[5] == 'r' || string[5] == 'R') &&
	  (string[6] == '>'));
}

1139
/**
Matthias Clasen's avatar
Matthias Clasen committed
1140
 * gtk_accelerator_parse:
1141 1142 1143
 * @accelerator:      string representing an accelerator
 * @accelerator_key:  return location for accelerator keyval
 * @accelerator_mods: return location for accelerator modifier mask
1144 1145
 *
 * Parses a string representing an accelerator. The
1146 1147
 * format looks like "&lt;Control&gt;a" or "&lt;Shift&gt;&lt;Alt&gt;F1" or
 * "&lt;Release&gt;z" (the last one is for key release).
1148
 * The parser is fairly liberal and allows lower or upper case,
1149
 * and also abbreviations such as "&lt;Ctl&gt;" and "&lt;Ctrl&gt;".
1150 1151 1152
 * Key names are parsed using gdk_keyval_from_name(). For character keys the
 * name is not the symbol, but the lowercase name, e.g. one would use
 * "&lt;Ctrl&gt;minus" instead of "&lt;Ctrl&gt;-".
1153 1154 1155
 *
 * If the parse fails, @accelerator_key and @accelerator_mods will
 * be set to 0 (zero).
1156
 */
Tim Janik's avatar
Tim Janik committed
1157
void
1158 1159 1160
gtk_accelerator_parse (const gchar     *accelerator,
		       guint           *accelerator_key,
		       GdkModifierType *accelerator_mods)
Tim Janik's avatar
Tim Janik committed
1161 1162
{
  guint keyval;
1163
  GdkModifierType mods;
Tim Janik's avatar
Tim Janik committed
1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
  gint len;
  
  if (accelerator_key)
    *accelerator_key = 0;
  if (accelerator_mods)
    *accelerator_mods = 0;
  g_return_if_fail (accelerator != NULL);
  
  keyval = 0;
  mods = 0;
  len = strlen (accelerator);
  while (len)
    {
      if (*accelerator == '<')
	{