gtkbindings.c 46.3 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Tim Janik's avatar
Tim Janik committed
2 3
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
4
 * GtkBindingSet: Keybinding manager for GObjects.
Tim Janik's avatar
Tim Janik committed
5 6 7
 * 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
 * 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
14
 * 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
Tim Janik's avatar
Tim Janik committed
18 19 20 21
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
22 23

/*
24
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
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
27
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
28 29
 */

30
#include "config.h"
31
#include <string.h>
Tim Janik's avatar
Tim Janik committed
32
#include <stdarg.h>
33

34
#include "gtkbindingsprivate.h"
35
#include "gtkkeyhash.h"
Tim Janik's avatar
Tim Janik committed
36
#include "gtkwidget.h"
37
#include "gtkintl.h"
Tim Janik's avatar
Tim Janik committed
38

39 40
/**
 * SECTION:gtkbindings
Matthias Clasen's avatar
Matthias Clasen committed
41
 * @Title: Bindings
42
 * @Short_description: Key bindings for individual widgets
Matthias Clasen's avatar
Matthias Clasen committed
43
 * @See_also: Keyboard Accelerators, Mnemonics, #GtkCssProvider
44
 *
Matthias Clasen's avatar
Matthias Clasen committed
45
 * #GtkBindingSet provides a mechanism for configuring GTK+ key bindings
46 47 48 49 50 51 52 53 54 55 56 57
 * through CSS files. This eases key binding adjustments for application
 * developers as well as users and provides GTK+ users or administrators
 * with high key  binding configurability which requires no application
 * or toolkit side changes.
 *
 * <refsect2 id="gtk-bindings-install">
 * <title>Installing a key binding</title>
 * <para>
 * A CSS file binding consists of a 'binding-set' definition and a match
 * statement to apply the binding set to specific widget types. Details
 * on the matching mechanism are described under
 * <link linkend="gtkcssprovider-selectors">Selectors</link>
Matthias Clasen's avatar
Matthias Clasen committed
58
 * in the #GtkCssProvider documentation. Inside the binding set definition,
59 60 61 62 63 64 65 66 67 68 69 70 71 72
 * key combinations are bound to one or more specific signal emissions on
 * the target widget. Key combinations are strings consisting of an optional
 * #GdkModifierType name and <link linkend="gdk-Keyboard-Handling">key names</link>
 * such as those defined in <filename>&lt;gdk/gdkkeysyms.h&gt;</filename>
 * or returned from gdk_keyval_name(), they have to be parsable by
 * gtk_accelerator_parse(). Specifications of signal emissions consist
 * of a string identifying the signal name, and a list of signal specific
 * arguments in parenthesis.
 * </para>
 * <para>
 * For example for binding Control and the left or right cursor keys
 * of a #GtkEntry widget to the #GtkEntry::move-cursor signal (so movement
 * occurs in 3-character steps), the following binding can be used:
 * <informalexample><programlisting>
Matthias Clasen's avatar
Matthias Clasen committed
73
 * @binding-set MoveCursor3
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
 * {
 *   bind "&lt;Control&gt;Right" { "move-cursor" (visual-positions, 3, 0) };
 *   bind "&lt;Control&gt;Left" { "move-cursor" (visual-positions, -3, 0) };
 * };
 * GtkEntry
 * {
 *   gtk-key-bindings: MoveCursor3
 * }
 * </programlisting></informalexample>
 * </para>
 * </refsect2>
 * <refsect2 id="gtk-bindings-unbind">
 * <title>Unbinding existing key bindings</title>
 * <para>
 * GTK+ already defines a number of useful bindings for the widgets
 * it provides. Because custom bindings set up in CSS files take
 * precedence over the default bindings shipped with GTK+, overriding
 * existing bindings as demonstrated in
 * <link linkend="gtk-bindings-install">Installing a key binding</link>
 * works as expected. The same mechanism can not be used to "unbind"
 * existing bindings, however.
 * <informalexample><programlisting>
Matthias Clasen's avatar
Matthias Clasen committed
96
 * @binding-set MoveCursor3
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
 * {
 *   bind "&lt;Control&gt;Right" {  };
 *   bind "&lt;Control&gt;Left" {  };
 * };
 * GtkEntry
 * {
 *   gtk-key-bindings: MoveCursor3
 * }
 * </programlisting></informalexample>
 * The above example will not have the desired effect of causing
 * "&lt;Control&gt;Right" and "&lt;Control&gt;Left" key presses to
 * be ignored by GTK+. Instead, it just causes any existing bindings
 * from the bindings set "MoveCursor3" to be deleted, so when
 * "&lt;Control&gt;Right" or "&lt;Control&gt;Left" are pressed, no
 * binding for these keys is found in binding set "MoveCursor3".
 * GTK+ will thus continue to search for matching key bindings, and will
 * eventually lookup and find the default GTK+ bindings for entries which
 * implement word movement. To keep GTK+ from activating its default
 * bindings, the "unbind" keyword can be used like this:
 * <informalexample><programlisting>
Matthias Clasen's avatar
Matthias Clasen committed
117
 * @binding-set MoveCursor3
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
 * {
 *   unbind "&lt;Control&gt;Right";
 *   unbind "&lt;Control&gt;Left";
 * };
 * GtkEntry
 * {
 *   gtk-key-bindings: MoveCursor3
 * }
 * </programlisting></informalexample>
 * Now, GTK+ will find a match when looking up "&lt;Control&gt;Right"
 * and "&lt;Control&gt;Left" key presses before it resorts to its default
 * bindings, and the match instructs it to abort ("unbind") the search,
 * so the key presses are not consumed by this widget. As usual, further
 * processing of the key presses, e.g. by an entry's parent widget, is
 * now possible.
 * </para>
 * </refsect2>
 */
Matthias Clasen's avatar
Matthias Clasen committed
136

Tim Janik's avatar
Tim Janik committed
137
/* --- defines --- */
138 139 140 141 142
#define BINDING_MOD_MASK() (gtk_accelerator_get_default_mod_mask () | GDK_RELEASE_MASK)


#define GTK_TYPE_IDENTIFIER (gtk_identifier_get_type ())
GType gtk_identifier_get_type (void) G_GNUC_CONST;
Tim Janik's avatar
Tim Janik committed
143 144


145 146
/* --- structures --- */
typedef struct {
147
  GtkPathType   type;
148
  GPatternSpec *pspec;
149 150
  gpointer      user_data;
  guint         seq_id;
151 152
} PatternSpec;

153 154 155 156
typedef enum {
  GTK_BINDING_TOKEN_BIND,
  GTK_BINDING_TOKEN_UNBIND
} GtkBindingTokens;
157

Tim Janik's avatar
Tim Janik committed
158
/* --- variables --- */
159
static GHashTable       *binding_entry_hash_table = NULL;
160
static GSList           *binding_key_hashes = NULL;
161 162 163
static GSList           *binding_set_list = NULL;
static const gchar       key_class_binding_set[] = "gtk-class-binding-set";
static GQuark            key_id_class_binding_set = 0;
Tim Janik's avatar
Tim Janik committed
164 165 166


/* --- functions --- */
167 168 169 170 171 172 173 174 175 176 177 178 179 180
GType
gtk_identifier_get_type (void)
{
  static GType our_type = 0;

  if (our_type == 0)
    {
      GTypeInfo tinfo = { 0, };
      our_type = g_type_register_static (G_TYPE_STRING, I_("GtkIdentifier"), &tinfo, 0);
    }

  return our_type;
}

181 182 183 184 185 186 187 188
static void
pattern_spec_free (PatternSpec *pspec)
{
  if (pspec->pspec)
    g_pattern_spec_free (pspec->pspec);
  g_free (pspec);
}

Tim Janik's avatar
Tim Janik committed
189 190
static GtkBindingSignal*
binding_signal_new (const gchar *signal_name,
191
                    guint        n_args)
Tim Janik's avatar
Tim Janik committed
192 193
{
  GtkBindingSignal *signal;
194

195
  signal = (GtkBindingSignal *) g_slice_alloc0 (sizeof (GtkBindingSignal) + n_args * sizeof (GtkBindingArg));
Tim Janik's avatar
Tim Janik committed
196
  signal->next = NULL;
197
  signal->signal_name = (gchar *)g_intern_string (signal_name);
Tim Janik's avatar
Tim Janik committed
198
  signal->n_args = n_args;
199
  signal->args = (GtkBindingArg *)(signal + 1);
200

Tim Janik's avatar
Tim Janik committed
201 202 203 204 205 206 207
  return signal;
}

static void
binding_signal_free (GtkBindingSignal *sig)
{
  guint i;
208

Tim Janik's avatar
Tim Janik committed
209 210
  for (i = 0; i < sig->n_args; i++)
    {
Manish Singh's avatar
Manish Singh committed
211
      if (G_TYPE_FUNDAMENTAL (sig->args[i].arg_type) == G_TYPE_STRING)
212
        g_free (sig->args[i].d.string_data);
Tim Janik's avatar
Tim Janik committed
213
    }
214
  g_slice_free1 (sizeof (GtkBindingSignal) + sig->n_args * sizeof (GtkBindingArg), sig);
Tim Janik's avatar
Tim Janik committed
215 216 217
}

static guint
218
binding_entry_hash (gconstpointer  key)
Tim Janik's avatar
Tim Janik committed
219
{
220
  register const GtkBindingEntry *e = key;
Tim Janik's avatar
Tim Janik committed
221 222 223 224 225 226 227 228 229
  register guint h;

  h = e->keyval;
  h ^= e->modifiers;

  return h;
}

static gint
230
binding_entries_compare (gconstpointer  a,
231
                         gconstpointer  b)
Tim Janik's avatar
Tim Janik committed
232
{
233 234
  register const GtkBindingEntry *ea = a;
  register const GtkBindingEntry *eb = b;
Tim Janik's avatar
Tim Janik committed
235 236 237 238

  return (ea->keyval == eb->keyval && ea->modifiers == eb->modifiers);
}

239 240
static void
binding_key_hash_insert_entry (GtkKeyHash      *key_hash,
241
                               GtkBindingEntry *entry)
242 243
{
  guint keyval = entry->keyval;
244

245 246 247 248 249
  /* We store lowercased accelerators. To deal with this, if <Shift>
   * was specified, uppercase.
   */
  if (entry->modifiers & GDK_SHIFT_MASK)
    {
250
      if (keyval == GDK_KEY_Tab)
251
        keyval = GDK_KEY_ISO_Left_Tab;
252
      else
253
        keyval = gdk_keyval_to_upper (keyval);
254
    }
255

256 257 258 259 260 261 262
  _gtk_key_hash_add_entry (key_hash, keyval, entry->modifiers & ~GDK_RELEASE_MASK, entry);
}

static void
binding_key_hash_destroy (gpointer data)
{
  GtkKeyHash *key_hash = data;
263

264 265 266 267 268 269
  binding_key_hashes = g_slist_remove (binding_key_hashes, key_hash);
  _gtk_key_hash_free (key_hash);
}

static void
insert_entries_into_key_hash (gpointer key,
270 271
                              gpointer value,
                              gpointer data)
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
{
  GtkKeyHash *key_hash = data;
  GtkBindingEntry *entry = value;

  for (; entry; entry = entry->hash_next)
    binding_key_hash_insert_entry (key_hash, entry);
}

static GtkKeyHash *
binding_key_hash_for_keymap (GdkKeymap *keymap)
{
  static GQuark key_hash_quark = 0;
  GtkKeyHash *key_hash;

  if (!key_hash_quark)
    key_hash_quark = g_quark_from_static_string ("gtk-binding-key-hash");
288

289 290 291 292 293 294 295 296
  key_hash = g_object_get_qdata (G_OBJECT (keymap), key_hash_quark);

  if (!key_hash)
    {
      key_hash = _gtk_key_hash_new (keymap, NULL);
      g_object_set_qdata_full (G_OBJECT (keymap), key_hash_quark, key_hash, binding_key_hash_destroy);

      if (binding_entry_hash_table)
297 298 299
        g_hash_table_foreach (binding_entry_hash_table,
                              insert_entries_into_key_hash,
                              key_hash);
300 301 302 303 304 305 306 307

      binding_key_hashes = g_slist_prepend (binding_key_hashes, key_hash);
    }

  return key_hash;
}


Tim Janik's avatar
Tim Janik committed
308
static GtkBindingEntry*
309
binding_entry_new (GtkBindingSet  *binding_set,
310 311
                   guint           keyval,
                   GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
312
{
313
  GSList *tmp_list;
Tim Janik's avatar
Tim Janik committed
314
  GtkBindingEntry *entry;
315

Tim Janik's avatar
Tim Janik committed
316 317 318 319 320 321 322 323 324
  if (!binding_entry_hash_table)
    binding_entry_hash_table = g_hash_table_new (binding_entry_hash, binding_entries_compare);

  entry = g_new (GtkBindingEntry, 1);
  entry->keyval = keyval;
  entry->modifiers = modifiers;
  entry->binding_set = binding_set,
  entry->destroyed = FALSE;
  entry->in_emission = FALSE;
325
  entry->marks_unbound = FALSE;
Tim Janik's avatar
Tim Janik committed
326 327 328 329 330 331 332 333 334
  entry->signals = NULL;

  entry->set_next = binding_set->entries;
  binding_set->entries = entry;

  entry->hash_next = g_hash_table_lookup (binding_entry_hash_table, entry);
  if (entry->hash_next)
    g_hash_table_remove (binding_entry_hash_table, entry->hash_next);
  g_hash_table_insert (binding_entry_hash_table, entry, entry);
335 336 337 338 339 340

  for (tmp_list = binding_key_hashes; tmp_list; tmp_list = tmp_list->next)
    {
      GtkKeyHash *key_hash = tmp_list->data;
      binding_key_hash_insert_entry (key_hash, entry);
    }
341

Tim Janik's avatar
Tim Janik committed
342 343 344 345 346 347 348 349 350
  return entry;
}

static void
binding_entry_free (GtkBindingEntry *entry)
{
  GtkBindingSignal *sig;

  g_assert (entry->set_next == NULL &&
351 352 353
            entry->hash_next == NULL &&
            entry->in_emission == FALSE &&
            entry->destroyed == TRUE);
Tim Janik's avatar
Tim Janik committed
354 355

  entry->destroyed = FALSE;
356

Tim Janik's avatar
Tim Janik committed
357 358 359 360
  sig = entry->signals;
  while (sig)
    {
      GtkBindingSignal *prev;
361

Tim Janik's avatar
Tim Janik committed
362 363 364 365 366 367 368 369 370 371 372 373 374 375
      prev = sig;
      sig = prev->next;
      binding_signal_free (prev);
    }
  g_free (entry);
}

static void
binding_entry_destroy (GtkBindingEntry *entry)
{
  GtkBindingEntry *o_entry;
  register GtkBindingEntry *tmp;
  GtkBindingEntry *begin;
  register GtkBindingEntry *last;
376
  GSList *tmp_list;
Tim Janik's avatar
Tim Janik committed
377 378 379 380 381 382 383 384

  /* unlink from binding set
   */
  last = NULL;
  tmp = entry->binding_set->entries;
  while (tmp)
    {
      if (tmp == entry)
385 386 387 388 389 390 391
        {
          if (last)
            last->set_next = entry->set_next;
          else
            entry->binding_set->entries = entry->set_next;
          break;
        }
Tim Janik's avatar
Tim Janik committed
392 393 394 395
      last = tmp;
      tmp = last->set_next;
    }
  entry->set_next = NULL;
396

Tim Janik's avatar
Tim Janik committed
397 398 399 400 401 402 403
  o_entry = g_hash_table_lookup (binding_entry_hash_table, entry);
  begin = o_entry;
  last = NULL;
  tmp = begin;
  while (tmp)
    {
      if (tmp == entry)
404 405 406 407 408 409 410
        {
          if (last)
            last->hash_next = entry->hash_next;
          else
            begin = entry->hash_next;
          break;
        }
Tim Janik's avatar
Tim Janik committed
411 412 413 414
      last = tmp;
      tmp = last->hash_next;
    }
  entry->hash_next = NULL;
415

Tim Janik's avatar
Tim Janik committed
416 417 418 419 420 421 422 423
  if (!begin)
    g_hash_table_remove (binding_entry_hash_table, entry);
  else if (begin != o_entry)
    {
      g_hash_table_remove (binding_entry_hash_table, entry);
      g_hash_table_insert (binding_entry_hash_table, begin, begin);
    }

424 425 426 427 428 429
  for (tmp_list = binding_key_hashes; tmp_list; tmp_list = tmp_list->next)
    {
      GtkKeyHash *key_hash = tmp_list->data;
      _gtk_key_hash_remove_entry (key_hash, entry);
    }

Tim Janik's avatar
Tim Janik committed
430 431 432 433 434 435 436
  entry->destroyed = TRUE;

  if (!entry->in_emission)
    binding_entry_free (entry);
}

static GtkBindingEntry*
437
binding_ht_lookup_entry (GtkBindingSet  *set,
438 439
                         guint           keyval,
                         GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
440 441 442
{
  GtkBindingEntry lookup_entry = { 0 };
  GtkBindingEntry *entry;
443

Tim Janik's avatar
Tim Janik committed
444 445
  if (!binding_entry_hash_table)
    return NULL;
446

Tim Janik's avatar
Tim Janik committed
447 448
  lookup_entry.keyval = keyval;
  lookup_entry.modifiers = modifiers;
449

Tim Janik's avatar
Tim Janik committed
450 451 452 453 454 455 456 457 458
  entry = g_hash_table_lookup (binding_entry_hash_table, &lookup_entry);
  for (; entry; entry = entry->hash_next)
    if (entry->binding_set == set)
      return entry;

  return NULL;
}

static gboolean
459
binding_compose_params (GObject         *object,
460 461 462
                        GtkBindingArg   *args,
                        GSignalQuery    *query,
                        GValue         **params_p)
Tim Janik's avatar
Tim Janik committed
463
{
464 465
  GValue *params;
  const GType *types;
Tim Janik's avatar
Tim Janik committed
466 467
  guint i;
  gboolean valid;
468

469
  params = g_new0 (GValue, query->n_params + 1);
Tim Janik's avatar
Tim Janik committed
470
  *params_p = params;
471 472 473 474 475 476

  /* The instance we emit on is the first object in the array
   */
  g_value_init (params, G_TYPE_OBJECT);
  g_value_set_object (params, G_OBJECT (object));
  params++;
477

478
  types = query->param_types;
Tim Janik's avatar
Tim Janik committed
479
  valid = TRUE;
480
  for (i = 1; i < query->n_params + 1 && valid; i++)
Tim Janik's avatar
Tim Janik committed
481
    {
Javier Jardón's avatar
Javier Jardón committed
482
      GValue tmp_value = G_VALUE_INIT;
483

484 485 486
      g_value_init (params, *types);

      switch (G_TYPE_FUNDAMENTAL (args->arg_type))
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
        {
        case G_TYPE_DOUBLE:
          g_value_init (&tmp_value, G_TYPE_DOUBLE);
          g_value_set_double (&tmp_value, args->d.double_data);
          break;
        case G_TYPE_LONG:
          g_value_init (&tmp_value, G_TYPE_LONG);
          g_value_set_long (&tmp_value, args->d.long_data);
          break;
        case G_TYPE_STRING:
          /* gtk_rc_parse_flags/enum() has fancier parsing for this; we can't call
           * that since we don't have a GParamSpec, so just do something simple
           */
          if (G_TYPE_FUNDAMENTAL (*types) == G_TYPE_ENUM)
            {
              GEnumClass *class = G_ENUM_CLASS (g_type_class_ref (*types));

              valid = FALSE;

              if (args->arg_type == GTK_TYPE_IDENTIFIER)
                {
                  GEnumValue *enum_value = NULL;
                  enum_value = g_enum_get_value_by_name (class, args->d.string_data);
                  if (!enum_value)
                    enum_value = g_enum_get_value_by_nick (class, args->d.string_data);
                  if (enum_value)
                    {
                      g_value_init (&tmp_value, *types);
                      g_value_set_enum (&tmp_value, enum_value->value);
                      valid = TRUE;
                    }
                }

              g_type_class_unref (class);
            }
          /* This is just a hack for compatibility with GTK+-1.2 where a string
           * could be used for a single flag value / without the support for multiple
           * values in gtk_rc_parse_flags(), this isn't very useful.
           */
          else if (G_TYPE_FUNDAMENTAL (*types) == G_TYPE_FLAGS)
            {
              GFlagsClass *class = G_FLAGS_CLASS (g_type_class_ref (*types));

              valid = FALSE;

              if (args->arg_type == GTK_TYPE_IDENTIFIER)
                {
                  GFlagsValue *flags_value = NULL;
                  flags_value = g_flags_get_value_by_name (class, args->d.string_data);
                  if (!flags_value)
                    flags_value = g_flags_get_value_by_nick (class, args->d.string_data);
                  if (flags_value)
                    {
                      g_value_init (&tmp_value, *types);
                      g_value_set_flags (&tmp_value, flags_value->value);
                      valid = TRUE;
                    }
                }

              g_type_class_unref (class);
            }
          else
            {
              g_value_init (&tmp_value, G_TYPE_STRING);
              g_value_set_static_string (&tmp_value, args->d.string_data);
            }
          break;
        default:
          valid = FALSE;
          break;
        }
558

559
      if (valid)
560 561 562
        {
          if (!g_value_transform (&tmp_value, params))
            valid = FALSE;
563

564 565
          g_value_unset (&tmp_value);
        }
566

Tim Janik's avatar
Tim Janik committed
567 568 569 570
      types++;
      params++;
      args++;
    }
571

Tim Janik's avatar
Tim Janik committed
572 573
  if (!valid)
    {
574 575 576
      guint j;

      for (j = 0; j < i; j++)
577
        g_value_unset (&(*params_p)[j]);
578

Tim Janik's avatar
Tim Janik committed
579 580 581
      g_free (*params_p);
      *params_p = NULL;
    }
582

Tim Janik's avatar
Tim Janik committed
583 584 585
  return valid;
}

586 587
static gboolean
gtk_binding_entry_activate (GtkBindingEntry *entry,
588
                            GObject         *object)
Tim Janik's avatar
Tim Janik committed
589 590 591
{
  GtkBindingSignal *sig;
  gboolean old_emission;
592 593
  gboolean handled = FALSE;
  gint i;
594

Tim Janik's avatar
Tim Janik committed
595 596
  old_emission = entry->in_emission;
  entry->in_emission = TRUE;
597

598
  g_object_ref (object);
599

Tim Janik's avatar
Tim Janik committed
600 601
  for (sig = entry->signals; sig; sig = sig->next)
    {
602
      GSignalQuery query;
Tim Janik's avatar
Tim Janik committed
603
      guint signal_id;
604
      GValue *params = NULL;
Javier Jardón's avatar
Javier Jardón committed
605
      GValue return_val = G_VALUE_INIT;
606
      gchar *accelerator = NULL;
607

608
      signal_id = g_signal_lookup (sig->signal_name, G_OBJECT_TYPE (object));
Tim Janik's avatar
Tim Janik committed
609
      if (!signal_id)
610 611 612 613 614 615 616 617 618 619 620 621
        {
          accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers);
          g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": "
                     "could not find signal \"%s\" in the `%s' class ancestry",
                     entry->binding_set->set_name,
                     accelerator,
                     sig->signal_name,
                     g_type_name (G_OBJECT_TYPE (object)));
          g_free (accelerator);
          continue;
        }

622 623
      g_signal_query (signal_id, &query);
      if (query.n_params != sig->n_args ||
624 625 626 627 628 629 630 631 632 633 634
          (query.return_type != G_TYPE_NONE && query.return_type != G_TYPE_BOOLEAN) ||
          !binding_compose_params (object, sig->args, &query, &params))
        {
          accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers);
          g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": "
                     "signature mismatch for signal \"%s\" in the `%s' class ancestry",
                     entry->binding_set->set_name,
                     accelerator,
                     sig->signal_name,
                     g_type_name (G_OBJECT_TYPE (object)));
        }
Manish Singh's avatar
Manish Singh committed
635
      else if (!(query.signal_flags & G_SIGNAL_ACTION))
636 637 638 639 640 641 642 643 644
        {
          accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers);
          g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": "
                     "signal \"%s\" in the `%s' class ancestry cannot be used for action emissions",
                     entry->binding_set->set_name,
                     accelerator,
                     sig->signal_name,
                     g_type_name (G_OBJECT_TYPE (object)));
        }
645 646
      g_free (accelerator);
      if (accelerator)
647
        continue;
Tim Janik's avatar
Tim Janik committed
648

649
      if (query.return_type == G_TYPE_BOOLEAN)
650
        g_value_init (&return_val, G_TYPE_BOOLEAN);
651

652 653 654
      g_signal_emitv (params, signal_id, 0, &return_val);

      if (query.return_type == G_TYPE_BOOLEAN)
655 656 657 658 659
        {
          if (g_value_get_boolean (&return_val))
            handled = TRUE;
          g_value_unset (&return_val);
        }
660
      else
661
        handled = TRUE;
662

663
      for (i = 0; i < query.n_params + 1; i++)
664
        g_value_unset (&params[i]);
Tim Janik's avatar
Tim Janik committed
665
      g_free (params);
666

667
      if (entry->destroyed)
668
        break;
Tim Janik's avatar
Tim Janik committed
669
    }
670

671
  g_object_unref (object);
Tim Janik's avatar
Tim Janik committed
672 673 674 675

  entry->in_emission = old_emission;
  if (entry->destroyed && !entry->in_emission)
    binding_entry_free (entry);
676 677

  return handled;
Tim Janik's avatar
Tim Janik committed
678 679
}

680
/**
681
 * gtk_binding_set_new: (skip)
682 683
 * @set_name: unique name of this binding set
 *
Matthias Clasen's avatar
Matthias Clasen committed
684
 * GTK+ maintains a global list of binding sets. Each binding set has
685 686
 * a unique name which needs to be specified upon creation.
 *
687
 * Return value: (transfer full): new binding set
Matthias Clasen's avatar
Matthias Clasen committed
688
 */
Tim Janik's avatar
Tim Janik committed
689
GtkBindingSet*
690
gtk_binding_set_new (const gchar *set_name)
Tim Janik's avatar
Tim Janik committed
691 692
{
  GtkBindingSet *binding_set;
693

Tim Janik's avatar
Tim Janik committed
694
  g_return_val_if_fail (set_name != NULL, NULL);
695

Tim Janik's avatar
Tim Janik committed
696
  binding_set = g_new (GtkBindingSet, 1);
697
  binding_set->set_name = (gchar *) g_intern_string (set_name);
Tim Janik's avatar
Tim Janik committed
698 699 700 701 702
  binding_set->widget_path_pspecs = NULL;
  binding_set->widget_class_pspecs = NULL;
  binding_set->class_branch_pspecs = NULL;
  binding_set->entries = NULL;
  binding_set->current = NULL;
703
  binding_set->parsed = FALSE;
704

705
  binding_set_list = g_slist_prepend (binding_set_list, binding_set);
706

Tim Janik's avatar
Tim Janik committed
707 708 709
  return binding_set;
}

710
/**
711
 * gtk_binding_set_by_class: (skip)
712
 * @object_class: a valid #GObject class
713 714 715 716 717
 *
 * This function returns the binding set named after the type name of
 * the passed in class structure. New binding sets are created on
 * demand by this function.
 *
718 719
 * Return value: (transfer full): the binding set corresponding to
 *     @object_class
Matthias Clasen's avatar
Matthias Clasen committed
720
 */
Tim Janik's avatar
Tim Janik committed
721 722 723
GtkBindingSet*
gtk_binding_set_by_class (gpointer object_class)
{
724
  GObjectClass *class = object_class;
Tim Janik's avatar
Tim Janik committed
725 726
  GtkBindingSet* binding_set;

727
  g_return_val_if_fail (G_IS_OBJECT_CLASS (class), NULL);
Tim Janik's avatar
Tim Janik committed
728 729

  if (!key_id_class_binding_set)
730
    key_id_class_binding_set = g_quark_from_static_string (key_class_binding_set);
Tim Janik's avatar
Tim Janik committed
731 732 733 734 735 736

  binding_set = g_dataset_id_get_data (class, key_id_class_binding_set);

  if (binding_set)
    return binding_set;

Manish Singh's avatar
Manish Singh committed
737
  binding_set = gtk_binding_set_new (g_type_name (G_OBJECT_CLASS_TYPE (class)));
Tim Janik's avatar
Tim Janik committed
738 739 740 741 742
  g_dataset_id_set_data (class, key_id_class_binding_set, binding_set);

  return binding_set;
}

Matthias Clasen's avatar
Matthias Clasen committed
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
static GtkBindingSet*
gtk_binding_set_find_interned (const gchar *set_name)
{
  GSList *slist;

  for (slist = binding_set_list; slist; slist = slist->next)
    {
      GtkBindingSet *binding_set;

      binding_set = slist->data;
      if (binding_set->set_name == set_name)
        return binding_set;
    }

  return NULL;
}

760 761 762 763
/**
 * gtk_binding_set_find:
 * @set_name: unique binding set name
 *
764 765 766 767
 * Find a binding set by its globally unique name.
 *
 * The @set_name can either be a name used for gtk_binding_set_new()
 * or the type name of a class used in gtk_binding_set_by_class().
768
 *
769
 * Return value: (transfer none): %NULL or the specified binding set
Matthias Clasen's avatar
Matthias Clasen committed
770
 */
Tim Janik's avatar
Tim Janik committed
771
GtkBindingSet*
772
gtk_binding_set_find (const gchar *set_name)
Tim Janik's avatar
Tim Janik committed
773 774
{
  g_return_val_if_fail (set_name != NULL, NULL);
775

Matthias Clasen's avatar
Matthias Clasen committed
776
  return gtk_binding_set_find_interned (g_intern_string (set_name));
Tim Janik's avatar
Tim Janik committed
777 778
}

779 780
/**
 * gtk_binding_set_activate:
Matthias Clasen's avatar
Matthias Clasen committed
781
 * @binding_set: a #GtkBindingSet set to activate
782 783 784 785 786 787 788 789
 * @keyval:      key value of the binding
 * @modifiers:   key modifier of the binding
 * @object:      object to activate when binding found
 *
 * Find a key binding matching @keyval and @modifiers within
 * @binding_set and activate the binding on @object.
 *
 * Return value: %TRUE if a binding was found and activated
Matthias Clasen's avatar
Matthias Clasen committed
790
 */
Tim Janik's avatar
Tim Janik committed
791
gboolean
792 793 794 795
gtk_binding_set_activate (GtkBindingSet  *binding_set,
                          guint           keyval,
                          GdkModifierType modifiers,
                          GObject        *object)
Tim Janik's avatar
Tim Janik committed
796 797
{
  GtkBindingEntry *entry;
798

Tim Janik's avatar
Tim Janik committed
799
  g_return_val_if_fail (binding_set != NULL, FALSE);
800
  g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
801

Tim Janik's avatar
Tim Janik committed
802 803
  keyval = gdk_keyval_to_lower (keyval);
  modifiers = modifiers & BINDING_MOD_MASK ();
804

805 806
  entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
  if (entry)
807
    return gtk_binding_entry_activate (entry, object);
808

Tim Janik's avatar
Tim Janik committed
809 810 811
  return FALSE;
}

812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
static void
gtk_binding_entry_clear_internal (GtkBindingSet  *binding_set,
                                  guint           keyval,
                                  GdkModifierType modifiers)
{
  GtkBindingEntry *entry;

  keyval = gdk_keyval_to_lower (keyval);
  modifiers = modifiers & BINDING_MOD_MASK ();

  entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
  if (entry)
    binding_entry_destroy (entry);

  entry = binding_entry_new (binding_set, keyval, modifiers);
}

829 830
/**
 * gtk_binding_entry_skip:
Matthias Clasen's avatar
Matthias Clasen committed
831
 * @binding_set: a #GtkBindingSet to skip an entry of
832 833 834
 * @keyval:      key value of binding to skip
 * @modifiers:   key modifier of binding to skip
 *
835 836 837
 * Install a binding on @binding_set which causes key lookups
 * to be aborted, to prevent bindings from lower priority sets
 * to be activated.
Matthias Clasen's avatar
Matthias Clasen committed
838 839
 *
 * Since: 2.12
Matthias Clasen's avatar
Matthias Clasen committed
840
 */
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
void
gtk_binding_entry_skip (GtkBindingSet  *binding_set,
                        guint           keyval,
                        GdkModifierType modifiers)
{
  GtkBindingEntry *entry;

  g_return_if_fail (binding_set != NULL);

  keyval = gdk_keyval_to_lower (keyval);
  modifiers = modifiers & BINDING_MOD_MASK ();

  entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
  if (entry)
    binding_entry_destroy (entry);

  entry = binding_entry_new (binding_set, keyval, modifiers);
  entry->marks_unbound = TRUE;
}

861 862
/**
 * gtk_binding_entry_remove:
Matthias Clasen's avatar
Matthias Clasen committed
863
 * @binding_set: a #GtkBindingSet to remove an entry of
864 865 866 867 868
 * @keyval:      key value of binding to remove
 * @modifiers:   key modifier of binding to remove
 *
 * Remove a binding previously installed via
 * gtk_binding_entry_add_signal() on @binding_set.
Matthias Clasen's avatar
Matthias Clasen committed
869
 */
Tim Janik's avatar
Tim Janik committed
870
void
871 872 873
gtk_binding_entry_remove (GtkBindingSet  *binding_set,
                          guint           keyval,
                          GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
874 875
{
  GtkBindingEntry *entry;
876

Tim Janik's avatar
Tim Janik committed
877
  g_return_if_fail (binding_set != NULL);
878

Tim Janik's avatar
Tim Janik committed
879 880
  keyval = gdk_keyval_to_lower (keyval);
  modifiers = modifiers & BINDING_MOD_MASK ();
881

Tim Janik's avatar
Tim Janik committed
882 883 884 885 886
  entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
  if (entry)
    binding_entry_destroy (entry);
}

887 888
/**
 * gtk_binding_entry_add_signall:
Matthias Clasen's avatar
Matthias Clasen committed
889
 * @binding_set:  a #GtkBindingSet to add a signal to
890 891 892
 * @keyval:       key value
 * @modifiers:    key modifier
 * @signal_name:  signal name to be bound
893 894
 * @binding_args: (transfer none) (element-type GtkBindingArg):
 *     list of #GtkBindingArg signal arguments
895
 *
896 897
 * Override or install a new key binding for @keyval with @modifiers on
 * @binding_set.
Matthias Clasen's avatar
Matthias Clasen committed
898
 */
Tim Janik's avatar
Tim Janik committed
899 900
void
gtk_binding_entry_add_signall (GtkBindingSet  *binding_set,
901
                               guint           keyval,
902 903
                               GdkModifierType modifiers,
                               const gchar    *signal_name,
904
                               GSList         *binding_args)
905 906 907 908 909 910 911 912
{
  _gtk_binding_entry_add_signall (binding_set,
                                  keyval, modifiers,
                                  signal_name, binding_args);
}

void
_gtk_binding_entry_add_signall (GtkBindingSet  *binding_set,
913
                                guint          keyval,
914 915
                                GdkModifierType modifiers,
                                const gchar    *signal_name,
916
                                GSList        *binding_args)
Tim Janik's avatar
Tim Janik committed
917 918 919 920 921 922
{
  GtkBindingEntry *entry;
  GtkBindingSignal *signal, **signal_p;
  GSList *slist;
  guint n = 0;
  GtkBindingArg *arg;
923

Tim Janik's avatar
Tim Janik committed
924 925
  g_return_if_fail (binding_set != NULL);
  g_return_if_fail (signal_name != NULL);
926

Tim Janik's avatar
Tim Janik committed
927 928
  keyval = gdk_keyval_to_lower (keyval);
  modifiers = modifiers & BINDING_MOD_MASK ();
929

Tim Janik's avatar
Tim Janik committed
930
  signal = binding_signal_new (signal_name, g_slist_length (binding_args));
931

Tim Janik's avatar
Tim Janik committed
932 933 934 935
  arg = signal->args;
  for (slist = binding_args; slist; slist = slist->next)
    {
      GtkBindingArg *tmp_arg;
936

Tim Janik's avatar
Tim Janik committed
937 938
      tmp_arg = slist->data;
      if (!tmp_arg)
939 940 941 942 943
        {
          g_warning ("gtk_binding_entry_add_signall(): arg[%u] is `NULL'", n);
          binding_signal_free (signal);
          return;
        }
Manish Singh's avatar
Manish Singh committed
944
      switch (G_TYPE_FUNDAMENTAL (tmp_arg->arg_type))
945 946 947 948 949 950 951 952 953 954
        {
        case  G_TYPE_LONG:
          arg->arg_type = G_TYPE_LONG;
          arg->d.long_data = tmp_arg->d.long_data;
          break;
        case  G_TYPE_DOUBLE:
          arg->arg_type = G_TYPE_DOUBLE;
          arg->d.double_data = tmp_arg->d.double_data;
          break;
        case  G_TYPE_STRING:
955
          if (tmp_arg->arg_type != GTK_TYPE_IDENTIFIER)
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
            arg->arg_type = G_TYPE_STRING;
          else
            arg->arg_type = GTK_TYPE_IDENTIFIER;
          arg->d.string_data = g_strdup (tmp_arg->d.string_data);
          if (!arg->d.string_data)
            {
              g_warning ("gtk_binding_entry_add_signall(): value of `string' arg[%u] is `NULL'", n);
              binding_signal_free (signal);
              return;
            }
          break;
        default:
          g_warning ("gtk_binding_entry_add_signall(): unsupported type `%s' for arg[%u]",
                     g_type_name (arg->arg_type), n);
          binding_signal_free (signal);
          return;
        }
Tim Janik's avatar
Tim Janik committed
973 974 975
      arg++;
      n++;
    }
976

Tim Janik's avatar
Tim Janik committed
977 978 979
  entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
  if (!entry)
    {
980
      gtk_binding_entry_clear_internal (binding_set, keyval, modifiers);
Tim Janik's avatar
Tim Janik committed
981 982 983 984 985 986 987 988
      entry = binding_ht_lookup_entry (binding_set, keyval, modifiers);
    }
  signal_p = &entry->signals;
  while (*signal_p)
    signal_p = &(*signal_p)->next;
  *signal_p = signal;
}

989 990
/**
 * gtk_binding_entry_add_signal:
Matthias Clasen's avatar
Matthias Clasen committed
991
 * @binding_set: a #GtkBindingSet to install an entry for
992 993 994 995
 * @keyval:      key value of binding to install
 * @modifiers:   key modifier of binding to install
 * @signal_name: signal to execute upon activation
 * @n_args:      number of arguments to @signal_name
Matthias Clasen's avatar
Matthias Clasen committed
996
 * @...:         arguments to @signal_name
997 998
 *
 * Override or install a new key binding for @keyval with @modifiers on
Matthias Clasen's avatar
Matthias Clasen committed
999
 * @binding_set. When the binding is activated, @signal_name will be
1000 1001
 * emitted on the target widget, with @n_args @Varargs used as
 * arguments.
Matthias Clasen's avatar
Matthias Clasen committed
1002
 */
Tim Janik's avatar
Tim Janik committed
1003 1004
void
gtk_binding_entry_add_signal (GtkBindingSet  *binding_set,
1005 1006 1007 1008 1009
                              guint           keyval,
                              GdkModifierType modifiers,
                              const gchar    *signal_name,
                              guint           n_args,
                              ...)
Tim Janik's avatar
Tim Janik committed
1010 1011 1012 1013 1014 1015 1016
{
  GSList *slist, *free_slist;
  va_list args;
  guint i;

  g_return_if_fail (binding_set != NULL);
  g_return_if_fail (signal_name != NULL);
1017

Tim Janik's avatar
Tim Janik committed
1018 1019 1020 1021 1022 1023
  va_start (args, n_args);
  slist = NULL;
  for (i = 0; i < n_args; i++)
    {
      GtkBindingArg *arg;

1024
      arg = g_slice_new0 (GtkBindingArg);
Tim Janik's avatar
Tim Janik committed
1025 1026
      slist = g_slist_prepend (slist, arg);

Michael Natterer's avatar
Michael Natterer committed
1027
      arg->arg_type = va_arg (args, GType);
Manish Singh's avatar
Manish Singh committed
1028
      switch (G_TYPE_FUNDAMENTAL (arg->arg_type))
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
        {
        case G_TYPE_CHAR:
        case G_TYPE_UCHAR:
        case G_TYPE_INT:
        case G_TYPE_UINT:
        case G_TYPE_BOOLEAN:
        case G_TYPE_ENUM:
        case G_TYPE_FLAGS:
          arg->arg_type = G_TYPE_LONG;
          arg->d.long_data = va_arg (args, gint);
          break;
        case G_TYPE_LONG:
        case G_TYPE_ULONG:
          arg->arg_type = G_TYPE_LONG;
          arg->d.long_data = va_arg (args, glong);
          break;
        case G_TYPE_FLOAT:
        case G_TYPE_DOUBLE:
          arg->arg_type = G_TYPE_DOUBLE;
          arg->d.double_data = va_arg (args, gdouble);
          break;
        case G_TYPE_STRING:
          if (arg->arg_type != GTK_TYPE_IDENTIFIER)
            arg->arg_type = G_TYPE_STRING;
          arg->d.string_data = va_arg (args, gchar*);
          if (!arg->d.string_data)
            {
              g_warning ("gtk_binding_entry_add_signal(): type `%s' arg[%u] is `NULL'",
                         g_type_name (arg->arg_type),
                         i);
              i += n_args + 1;
            }
          break;
        default:
          g_warning ("gtk_binding_entry_add_signal(): unsupported type `%s' for arg[%u]",
                     g_type_name (arg->arg_type), i);
          i += n_args + 1;
          break;
        }
Tim Janik's avatar
Tim Janik committed
1068 1069 1070
    }
  va_end (args);

1071
  if (i == n_args || i == 0)
Tim Janik's avatar
Tim Janik committed
1072 1073
    {
      slist = g_slist_reverse (slist);
1074
      _gtk_binding_entry_add_signall (binding_set, keyval, modifiers, signal_name, slist);
Tim Janik's avatar
Tim Janik committed
1075 1076 1077 1078 1079
    }

  free_slist = slist;
  while (slist)
    {
1080
      g_slice_free (GtkBindingArg, slist->data);
Tim Janik's avatar
Tim Janik committed
1081 1082 1083 1084 1085
      slist = slist->next;
    }
  g_slist_free (free_slist);
}

1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 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 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357
static guint
gtk_binding_parse_signal (GScanner       *scanner,
                          GtkBindingSet  *binding_set,
                          guint           keyval,
                          GdkModifierType modifiers)
{
  gchar *signal;
  guint expected_token = 0;
  GSList *args;
  GSList *slist;
  gboolean done;
  gboolean negate;
  gboolean need_arg;
  gboolean seen_comma;

  g_return_val_if_fail (scanner != NULL, G_TOKEN_ERROR);

  g_scanner_get_next_token (scanner);

  if (scanner->token != G_TOKEN_STRING)
    return G_TOKEN_STRING;

  g_scanner_peek_next_token (scanner);

  if (scanner->next_token != '(')
    {
      g_scanner_get_next_token (scanner);
      return '(';
    }

  signal = g_strdup (scanner->value.v_string);
  g_scanner_get_next_token (scanner);

  negate = FALSE;
  args = NULL;
  done = FALSE;
  need_arg = TRUE;
  seen_comma = FALSE;
  scanner->config->scan_symbols = FALSE;

  do
    {
      GtkBindingArg *arg;

      if (need_arg)
        expected_token = G_TOKEN_INT;
      else
        expected_token = ')';

      g_scanner_get_next_token (scanner);

      switch ((guint) scanner->token)
        {
        case G_TOKEN_FLOAT:
          if (need_arg)
            {
              need_arg = FALSE;
              arg = g_new (GtkBindingArg, 1);
              arg->arg_type = G_TYPE_DOUBLE;
              arg->d.double_data = scanner->value.v_float;

              if (negate)
                {
                  arg->d.double_data = - arg->d.double_data;
                  negate = FALSE;
                }
              args = g_slist_prepend (args, arg);
            }
          else
            done = TRUE;

          break;
        case G_TOKEN_INT:
          if (need_arg)
            {
              need_arg = FALSE;
              arg = g_new (GtkBindingArg, 1);
              arg->arg_type = G_TYPE_LONG;
              arg->d.long_data = scanner->value.v_int;

              if (negate)
                {
                  arg->d.long_data = - arg->d.long_data;
                  negate = FALSE;
                }
              args = g_slist_prepend (args, arg);
            }
          else
            done = TRUE;
          break;
        case G_TOKEN_STRING:
          if (need_arg && !negate)
            {
              need_arg = FALSE;
              arg = g_new (GtkBindingArg, 1);
              arg->arg_type = G_TYPE_STRING;
              arg->d.string_data = g_strdup (scanner->value.v_string);
              args = g_slist_prepend (args, arg);
            }
          else
            done = TRUE;

          break;
        case G_TOKEN_IDENTIFIER:
          if (need_arg && !negate)
            {
              need_arg = FALSE;
              arg = g_new (GtkBindingArg, 1);
              arg->arg_type = GTK_TYPE_IDENTIFIER;
              arg->d.string_data = g_strdup (scanner->value.v_identifier);
              args = g_slist_prepend (args, arg);
            }
          else
            done = TRUE;

          break;
        case '-':
          if (!need_arg)
            done = TRUE;
          else if (negate)
            {
              expected_token = G_TOKEN_INT;
              done = TRUE;
            }
          else
            negate = TRUE;

          break;
        case ',':
          seen_comma = TRUE;
          if (need_arg)
            done = TRUE;
          else
            need_arg = TRUE;

          break;
        case ')':
          if (!(need_arg && seen_comma) && !negate)
            {
              args = g_slist_reverse (args);
              _gtk_binding_entry_add_signall (binding_set,
                                              keyval,
                                              modifiers,
                                              signal,
                                              args);
              expected_token = G_TOKEN_NONE;
            }

          done = TRUE;
          break;
        default:
          done = TRUE;
          break;
        }
    }
  while (!done);

  scanner->config->scan_symbols = TRUE;

  for (slist = args; slist; slist = slist->next)
    {
      GtkBindingArg *arg;

      arg = slist->data;

      if (G_TYPE_FUNDAMENTAL (arg->arg_type) == G_TYPE_STRING)
        g_free (arg->d.string_data);
      g_free (arg);
    }

  g_slist_free (args);
  g_free (signal);

  return expected_token;
}

static inline guint
gtk_binding_parse_bind (GScanner       *scanner,
                        GtkBindingSet  *binding_set)
{
  guint keyval = 0;
  GdkModifierType modifiers = 0;
  gboolean unbind = FALSE;

  g_return_val_if_fail (scanner != NULL, G_TOKEN_ERROR);

  g_scanner_get_next_token (scanner);

  if (scanner->token != G_TOKEN_SYMBOL)
    return G_TOKEN_SYMBOL;

  if (scanner->value.v_symbol != GUINT_TO_POINTER (GTK_BINDING_TOKEN_BIND) &&
      scanner->value.v_symbol != GUINT_TO_POINTER (GTK_BINDING_TOKEN_UNBIND))
    return G_TOKEN_SYMBOL;

  unbind = (scanner->value.v_symbol == GUINT_TO_POINTER (GTK_BINDING_TOKEN_UNBIND));
  g_scanner_get_next_token (scanner);

  if (scanner->token != (guint) G_TOKEN_STRING)
    return G_TOKEN_STRING;

  gtk_accelerator_parse (scanner->value.v_string, &keyval, &modifiers);
  modifiers &= BINDING_MOD_MASK ();

  if (keyval == 0)
    return G_TOKEN_STRING;

  if (unbind)
    {
      gtk_binding_entry_skip (binding_set, keyval, modifiers);
      return G_TOKEN_NONE;
    }

  g_scanner_get_next_token (scanner);

  if (scanner->token != '{')
    return '{';

  gtk_binding_entry_clear_internal (binding_set, keyval, modifiers);
  g_scanner_peek_next_token (scanner);

  while (scanner->next_token != '}')
    {
      guint expected_token;

      switch (scanner->next_token)
        {
        case G_TOKEN_STRING:
          expected_token = gtk_binding_parse_signal (scanner,
                                                     binding_set,
                                                     keyval,
                                                     modifiers);
          if (expected_token != G_TOKEN_NONE)
            return expected_token;
          break;
        default:
          g_scanner_get_next_token (scanner);
          return '}';
        }

      g_scanner_peek_next_token (scanner);
    }

  g_scanner_get_next_token (scanner);

  return G_TOKEN_NONE;
}

static GScanner *
create_signal_scanner (void)
{
  GScanner *scanner;

  scanner = g_scanner_new (NULL);
  scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_";

  g_scanner_scope_add_symbol (scanner, 0, "bind", GUINT_TO_POINTER (GTK_BINDING_TOKEN_BIND));
  g_scanner_scope_add_symbol (scanner, 0, "unbind", GUINT_TO_POINTER (GTK_BINDING_TOKEN_UNBIND));

  g_scanner_set_scope (scanner, 0);

  return scanner;
}

/**
 * gtk_binding_entry_add_signal_from_string:
 * @binding_set: a #GtkBindingSet
 * @signal_desc: a signal description
 *
 * Parses a signal description from @signal_desc and incorporates
 * it into @binding_set.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1358
 * Signal descriptions may either bind a key combination to
1359
 * one or more signals:
1360
 * <informalexample><programlisting>
Matthias Clasen's avatar
Matthias Clasen committed
1361 1362
 *   bind "key" {
 *     "signalname" (param, ...)
1363 1364 1365 1366 1367 1368
 *     ...
 *   }
 * </programlisting></informalexample>
 *
 * Or they may also unbind a key combination:
 * <informalexample><programlisting>
Matthias Clasen's avatar
Matthias Clasen committed
1369
 *   unbind "key"
1370 1371 1372 1373 1374
 * </programlisting></informalexample>
 *
 * Key combinations must be in a format that can be parsed by
 * gtk_accelerator_parse().
 *
1375 1376 1377
 * Returns: %G_TOKEN_NONE if the signal was successfully parsed and added,
 *     the expected token otherwise
 *
1378
 * Since: 3.0
1379 1380
 */
GTokenType
1381
gtk_binding_entry_add_signal_from_string (GtkBindingSet *binding_set,
1382
                                          const gchar   *signal_desc)
1383 1384 1385 1386
{
  static GScanner *scanner = NULL;
  GTokenType ret;

1387 1388
  g_return_val_if_fail (binding_set != NULL, G_TOKEN_NONE);
  g_return_val_if_fail (signal_desc != NULL, G_TOKEN_NONE);
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399

  if (G_UNLIKELY (!scanner))
    scanner = create_signal_scanner ();

  g_scanner_input_text (scanner, signal_desc,
                        (guint) strlen (signal_desc));

  ret = gtk_binding_parse_bind (scanner, binding_set);

  /* Reset for next use */
  g_scanner_set_scope (scanner, 0);
1400 1401

  return ret;
1402 1403
}

1404 1405
/**
 * gtk_binding_set_add_path:
1406 1407
 * @binding_set: a #GtkBindingSet to add a path to
 * @path_type: path type the pattern applies to
1408
 * @path_pattern: the actual match pattern
1409
 * @priority: binding priority
1410
 *
Matthias Clasen's avatar
Matthias Clasen committed
1411 1412 1413 1414
 * This function was used internally by the GtkRC parsing mechanism
 * to assign match patterns to #GtkBindingSet structures.
 *
 * In GTK+ 3, these match patterns are unused.
1415 1416
 *
 * Deprecated: 3.0
Matthias Clasen's avatar
Matthias Clasen committed
1417
 */
Tim Janik's avatar
Tim Janik committed
1418
void
1419 1420 1421 1422
gtk_binding_set_add_path (GtkBindingSet       *binding_set,
                          GtkPathType          path_type,
                          const gchar         *path_pattern,
                          GtkPathPriorityType  priority)
Tim Janik's avatar
Tim Janik committed
1423
{
1424
  PatternSpec *pspec;
Tim Janik's avatar
Tim Janik committed
1425 1426
  GSList **slist_p, *slist;
  static guint seq_id = 0;
1427

Tim Janik's avatar
Tim Janik committed
1428 1429
  g_return_if_fail (binding_set != NULL);
  g_return_if_fail (path_pattern != NULL);
1430
  g_return_if_fail (priority <= GTK_PATH_PRIO_MASK);
Tim Janik's avatar
Tim Janik committed
1431 1432

  priority &= GTK_PATH_PRIO_MASK;
1433

Tim Janik's avatar
Tim Janik committed
1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449
  switch (path_type)
    {
    case  GTK_PATH_WIDGET:
      slist_p = &binding_set->widget_path_pspecs;
      break;
    case  GTK_PATH_WIDGET_CLASS:
      slist_p = &binding_set->widget_class_pspecs;
      break;
    case  GTK_PATH_CLASS:
      slist_p = &binding_set->class_branch_pspecs;
      break;
    default:
      g_assert_not_reached ();
      slist_p = NULL;
      break;
    }
1450

1451
  pspec = g_new (PatternSpec, 1);
1452 1453
  pspec->type = path_type;
  if (path_type == GTK_PATH_WIDGET_CLASS)
1454
    pspec->pspec = NULL;
1455
  else
1456 1457
    pspec->pspec = g_pattern_spec_new (path_pattern);

1458
  pspec->seq_id = priority << 28;
Tim Janik's avatar
Tim Janik committed
1459
  pspec->user_data = binding_set;
1460

Tim Janik's avatar
Tim Janik committed
1461 1462 1463
  slist = *slist_p;
  while (slist)
    {
1464
      PatternSpec *tmp_pspec;
1465

Tim Janik's avatar
Tim Janik committed
1466 1467
      tmp_pspec = slist->data;
      slist = slist->next;
1468

1469
      if (g_pattern_spec_equal (tmp_pspec->pspec, pspec->pspec))
1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
        {
          GtkPathPriorityType lprio = tmp_pspec->seq_id >> 28;

          pattern_spec_free (pspec);
          pspec = NULL;
          if (lprio < priority)
            {
              tmp_pspec->seq_id &= 0x0fffffff;
              tmp_pspec->seq_id |= priority << 28;
            }
          break;
        }
Tim Janik's avatar
Tim Janik committed
1482 1483
    }
  if (pspec)
1484 1485 1486 1487
    {
      pspec->seq_id |= seq_id++ & 0x0fffffff;
      *slist_p = g_slist_prepend (*slist_p, pspec);
    }
Tim Janik's avatar
Tim Janik committed
1488 1489
}

1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529
static gint
find_entry_with_binding (GtkBindingEntry *entry,
                         GtkBindingSet   *binding_set)
{
  return (entry->binding_set == binding_set) ? 0 : 1;
}

static gboolean
binding_activate (GtkBindingSet *binding_set,
                  GSList        *entries,
                  GObject       *object,
                  gboolean       is_release,
                  gboolean      *unbound)
{
  GtkBindingEntry *entry;
  GSList *elem;

  elem = g_slist_find_custom (entries, binding_set,
                              (GCompareFunc) find_entry_with_binding);

  if (!elem)
    return FALSE;

  entry = elem->data;

  if (is_release != ((entry->modifiers & GDK_RELEASE_MASK) != 0))
    return FALSE;

  if (entry->marks_unbound)
    {
      *unbound = TRUE;
      return FALSE;
    }

  if (gtk_binding_entry_activate (entry, object))
    return TRUE;

  return FALSE;
}

1530
static gboolean
1531 1532 1533
gtk_bindings_activate_list (GObject  *object,
                            GSList   *entries,
                            gboolean  is_release)
Tim Janik's avatar
Tim Janik committed
1534
{
1535 1536 1537
  GtkStyleContext *context;
  GtkBindingSet *binding_set;
  GtkStateFlags state;
Tim Janik's avatar
Tim Janik committed
1538
  gboolean handled = FALSE;
1539 1540
  gboolean unbound = FALSE;
  GPtrArray *array;
Tim Janik's avatar
Tim Janik committed
1541 1542 1543 1544

  if (!entries)
    return FALSE;

1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569
  context = gtk_widget_get_style_context (GTK_WIDGET (object));
  state = gtk_widget_get_state_flags (GTK_WIDGET (object));

  gtk_style_context_get (context, state,
                         "gtk-key-bindings", &array,
                         NULL);
  if (array)
    {
      gint i;

      for (i = 0; i < array->len; i++)
        {
          binding_set = g_ptr_array_index (array, i);
          handled = binding_activate (binding_set, entries,
                                      object, is_release,
                                      &unbound);
          if (handled)
            break;
        }

      g_ptr_array_unref (array);

      if (unbound)
        return FALSE;
    }
Tim Janik's avatar
Tim Janik committed
1570 1571 1572

  if (!handled)
    {
Manish Singh's avatar
Manish Singh committed
1573
      GType class_type;
1574

Manish Singh's avatar
Manish Singh committed
1575
      class_type = G_TYPE_FROM_INSTANCE (object);
1576

Tim Janik's avatar
Tim Janik committed
1577
      while (class_type && !handled)
1578
        {
Matthias Clasen's avatar
Matthias Clasen committed
1579
          binding_set = gtk_binding_set_find_interned (g_type_name (class_type));
1580
          class_type = g_type_parent (class_type);
1581 1582 1583 1584 1585 1586 1587

          if (!binding_set)
            continue;

          handled = binding_activate (binding_set, entries,
                                      object, is_release,
                                      &unbound);
1588
        }
1589 1590 1591

      if (unbound)
        return FALSE;
Tim Janik's avatar
Tim Janik committed
1592 1593 1594 1595 1596
    }

  return handled;
}

1597 1598 1599 1600 1601 1602 1603 1604 1605 1606
/**
 * gtk_bindings_activate:
 * @object: object to activate when binding found
 * @keyval: key value of the binding
 * @modifiers: key modifier of the binding
 *
 * Find a key binding matching @keyval and @modifiers and activate the
 * binding on @object.
 *
 * Return value: %TRUE if a binding was found and activated
Matthias Clasen's avatar
Matthias Clasen committed
1607
 */
1608
gboolean
1609
gtk_bindings_activate (GObject         *object,
1610 1611
                       guint            keyval,
                       GdkModifierType  modifiers)
1612 1613
{
  GSList *entries = NULL;
1614
  GdkDisplay *display;
1615 1616 1617 1618 1619 1620 1621
  GtkKeyHash *key_hash;
  gboolean handled = FALSE;
  gboolean is_release;

  if (!GTK_IS_WIDGET (object))
    return FALSE;

1622
  is_release = (modifiers & GDK_RELEASE_MASK) != 0;
1623 1624
  modifiers = modifiers & BINDING_MOD_MASK () & ~GDK_RELEASE_MASK;

1625 1626
  display = gtk_widget_get_display (GTK_WIDGET (object));
  key_hash = binding_key_hash_for_keymap (gdk_keymap_get_for_display (display));
1627

1628 1629 1630 1631 1632 1633 1634 1635 1636 1637
  entries = _gtk_key_hash_lookup_keyval (key_hash, keyval, modifiers);

  handled = gtk_bindings_activate_list (object, entries, is_release);

  g_slist_free (entries);

  return handled;
}

/**
1638
 * gtk_bindings_activate_event:
1639
 * @object: a #GObject (generally must be a widget)
1640
 * @event: a #GdkEventKey
1641
 *
1642 1643
 * Looks up key bindings for @object to find one matching
 * @event, and if one was found, activate it.
1644
 *
1645
 * Return value: %TRUE if a matching key binding was found
1646 1647
 *
 * Since: 2.4
Matthias Clasen's avatar
Matthias Clasen committed
1648
 */
1649
gboolean
1650
gtk_bindings_activate_event (GObject     *object,
Matthias Clasen's avatar
Matthias Clasen committed
1651
                             GdkEventKey *event)