gtkbindings.c 44.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Tim Janik's avatar
Tim Janik committed
2 3
 * 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
Javier Jardon's avatar
Javier Jardon committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Tim Janik's avatar
Tim Janik committed
19
 */
20 21

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

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

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

38 39
/**
 * SECTION:gtkbindings
Matthias Clasen's avatar
Matthias Clasen committed
40
 * @Title: Bindings
41
 * @Short_description: Key bindings for individual widgets
Matthias Clasen's avatar
Matthias Clasen committed
42
 * @See_also: Keyboard Accelerators, Mnemonics, #GtkCssProvider
43
 *
Matthias Clasen's avatar
Matthias Clasen committed
44
 * #GtkBindingSet provides a mechanism for configuring GTK+ key bindings
45 46 47 48 49
 * 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.
 *
50 51 52 53 54 55
 * In order for bindings to work in a custom widget implementation, the
 * widget’s #GtkWidget:can-focus and #GtkWidget:has-focus properties
 * must both be true. For example, by calling gtk_widget_set_can_focus()
 * in the widget’s initialisation function; and by calling
 * gtk_widget_grab_focus() when the widget is clicked.
 *
56
 * # Installing a key binding
57
 *
58
 * A CSS file binding consists of a “binding-set” definition and a match
59 60
 * statement to apply the binding set to specific widget types. Details
 * on the matching mechanism are described under
61
 * [Selectors][gtkcssprovider-selectors]
62 63 64 65
 * in the #GtkCssProvider documentation. Inside the binding set
 * definition, 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
66
 * [key names][gdk3-Keyboard-Handling]
67
 * such as those defined in `gdk/gdkkeysyms.h`
68 69 70 71
 * 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.
72
 *
73
 * For example for binding Control and the left or right cursor keys
74 75 76
 * of a #GtkEntry widget to the #GtkEntry::move-cursor signal (so
 * movement occurs in 3-character steps), the following binding can be
 * used:
77
 *
78
 * |[ <!-- language="CSS" -->
Matthias Clasen's avatar
Matthias Clasen committed
79
 * @binding-set MoveCursor3
80
 * {
81 82
 *   bind "<Control>Right" { "move-cursor" (visual-positions, 3, 0) };
 *   bind "<Control>Left" { "move-cursor" (visual-positions, -3, 0) };
83
 * }
84 85
 *
 * entry
86
 * {
87
 *   -gtk-key-bindings: MoveCursor3;
88
 * }
89
 * ]|
90
 *
91
 * # Unbinding existing key bindings
92
 *
93 94 95 96
 * 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
97
 * [Installing a key binding][gtk-bindings-install]
98
 * works as expected. The same mechanism can not be used to “unbind”
99
 * existing bindings, however.
100
 *
101
 * |[ <!-- language="CSS" -->
Matthias Clasen's avatar
Matthias Clasen committed
102
 * @binding-set MoveCursor3
103
 * {
104 105
 *   bind "<Control>Right" {  };
 *   bind "<Control>Left" {  };
106
 * }
107 108
 *
 * entry
109
 * {
110
 *   -gtk-key-bindings: MoveCursor3;
111
 * }
112
 * ]|
113
 *
114
 * The above example will not have the desired effect of causing
115 116 117 118 119 120 121 122 123
 * “<Control>Right” and “<Control>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 “<Control>Right” or
 * “<Control>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:
124
 *
125
 * |[ <!-- language="CSS" -->
Matthias Clasen's avatar
Matthias Clasen committed
126
 * @binding-set MoveCursor3
127
 * {
128 129
 *   unbind "<Control>Right";
 *   unbind "<Control>Left";
130
 * }
131 132
 *
 * entry
133
 * {
134
 *   -gtk-key-bindings: MoveCursor3;
135
 * }
136
 * ]|
137
 *
138 139 140 141 142
 * Now, GTK+ will find a match when looking up “<Control>Right” and
 * “<Control>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.
143
 */
Matthias Clasen's avatar
Matthias Clasen committed
144

Tim Janik's avatar
Tim Janik committed
145
/* --- defines --- */
146 147 148 149
#define BINDING_MOD_MASK() (gtk_accelerator_get_default_mod_mask () | GDK_RELEASE_MASK)


#define GTK_TYPE_IDENTIFIER (gtk_identifier_get_type ())
150
_GDK_EXTERN
151
GType gtk_identifier_get_type (void) G_GNUC_CONST;
Tim Janik's avatar
Tim Janik committed
152 153


154
/* --- structures --- */
155 156 157 158
typedef enum {
  GTK_BINDING_TOKEN_BIND,
  GTK_BINDING_TOKEN_UNBIND
} GtkBindingTokens;
159

Tim Janik's avatar
Tim Janik committed
160
/* --- variables --- */
161
static GHashTable       *binding_entry_hash_table = NULL;
162
static GSList           *binding_key_hashes = NULL;
163 164 165
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
166 167 168


/* --- functions --- */
169 170 171 172 173 174 175 176 177 178 179 180 181 182
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;
}

Tim Janik's avatar
Tim Janik committed
183 184
static GtkBindingSignal*
binding_signal_new (const gchar *signal_name,
185
                    guint        n_args)
Tim Janik's avatar
Tim Janik committed
186 187
{
  GtkBindingSignal *signal;
188

189
  signal = (GtkBindingSignal *) g_slice_alloc0 (sizeof (GtkBindingSignal) + n_args * sizeof (GtkBindingArg));
Tim Janik's avatar
Tim Janik committed
190
  signal->next = NULL;
191
  signal->signal_name = (gchar *)g_intern_string (signal_name);
Tim Janik's avatar
Tim Janik committed
192
  signal->n_args = n_args;
193
  signal->args = (GtkBindingArg *)(signal + 1);
194

Tim Janik's avatar
Tim Janik committed
195 196 197 198 199 200 201
  return signal;
}

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

Tim Janik's avatar
Tim Janik committed
203 204
  for (i = 0; i < sig->n_args; i++)
    {
Manish Singh's avatar
Manish Singh committed
205
      if (G_TYPE_FUNDAMENTAL (sig->args[i].arg_type) == G_TYPE_STRING)
206
        g_free (sig->args[i].d.string_data);
Tim Janik's avatar
Tim Janik committed
207
    }
208
  g_slice_free1 (sizeof (GtkBindingSignal) + sig->n_args * sizeof (GtkBindingArg), sig);
Tim Janik's avatar
Tim Janik committed
209 210 211
}

static guint
212
binding_entry_hash (gconstpointer  key)
Tim Janik's avatar
Tim Janik committed
213
{
214
  register const GtkBindingEntry *e = key;
Tim Janik's avatar
Tim Janik committed
215 216 217 218 219 220 221 222 223
  register guint h;

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

  return h;
}

static gint
224
binding_entries_compare (gconstpointer  a,
225
                         gconstpointer  b)
Tim Janik's avatar
Tim Janik committed
226
{
227 228
  register const GtkBindingEntry *ea = a;
  register const GtkBindingEntry *eb = b;
Tim Janik's avatar
Tim Janik committed
229 230 231 232

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

233 234
static void
binding_key_hash_insert_entry (GtkKeyHash      *key_hash,
235
                               GtkBindingEntry *entry)
236 237
{
  guint keyval = entry->keyval;
238

239 240 241 242 243
  /* We store lowercased accelerators. To deal with this, if <Shift>
   * was specified, uppercase.
   */
  if (entry->modifiers & GDK_SHIFT_MASK)
    {
244
      if (keyval == GDK_KEY_Tab)
245
        keyval = GDK_KEY_ISO_Left_Tab;
246
      else
247
        keyval = gdk_keyval_to_upper (keyval);
248
    }
249

250 251 252 253 254 255 256
  _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;
257

258 259 260 261 262 263
  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,
264 265
                              gpointer value,
                              gpointer data)
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
{
  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");
282

283 284 285 286 287 288 289 290
  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)
291 292 293
        g_hash_table_foreach (binding_entry_hash_table,
                              insert_entries_into_key_hash,
                              key_hash);
294 295 296 297 298 299 300 301

      binding_key_hashes = g_slist_prepend (binding_key_hashes, key_hash);
    }

  return key_hash;
}


Tim Janik's avatar
Tim Janik committed
302
static GtkBindingEntry*
303
binding_entry_new (GtkBindingSet  *binding_set,
304 305
                   guint           keyval,
                   GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
306
{
307
  GSList *tmp_list;
Tim Janik's avatar
Tim Janik committed
308
  GtkBindingEntry *entry;
309

Tim Janik's avatar
Tim Janik committed
310 311 312 313 314 315 316 317 318
  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;
319
  entry->marks_unbound = FALSE;
Tim Janik's avatar
Tim Janik committed
320 321 322 323 324 325 326 327 328
  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);
329 330 331 332 333 334

  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);
    }
335

Tim Janik's avatar
Tim Janik committed
336 337 338 339 340 341 342 343 344
  return entry;
}

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

  g_assert (entry->set_next == NULL &&
345 346 347
            entry->hash_next == NULL &&
            entry->in_emission == FALSE &&
            entry->destroyed == TRUE);
Tim Janik's avatar
Tim Janik committed
348 349

  entry->destroyed = FALSE;
350

Tim Janik's avatar
Tim Janik committed
351 352 353 354
  sig = entry->signals;
  while (sig)
    {
      GtkBindingSignal *prev;
355

Tim Janik's avatar
Tim Janik committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369
      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;
370
  GSList *tmp_list;
Tim Janik's avatar
Tim Janik committed
371 372 373 374 375 376 377 378

  /* unlink from binding set
   */
  last = NULL;
  tmp = entry->binding_set->entries;
  while (tmp)
    {
      if (tmp == entry)
379 380 381 382 383 384 385
        {
          if (last)
            last->set_next = entry->set_next;
          else
            entry->binding_set->entries = entry->set_next;
          break;
        }
Tim Janik's avatar
Tim Janik committed
386 387 388 389
      last = tmp;
      tmp = last->set_next;
    }
  entry->set_next = NULL;
390

Tim Janik's avatar
Tim Janik committed
391 392 393 394 395 396 397
  o_entry = g_hash_table_lookup (binding_entry_hash_table, entry);
  begin = o_entry;
  last = NULL;
  tmp = begin;
  while (tmp)
    {
      if (tmp == entry)
398 399 400 401 402 403 404
        {
          if (last)
            last->hash_next = entry->hash_next;
          else
            begin = entry->hash_next;
          break;
        }
Tim Janik's avatar
Tim Janik committed
405 406 407 408
      last = tmp;
      tmp = last->hash_next;
    }
  entry->hash_next = NULL;
409

Tim Janik's avatar
Tim Janik committed
410 411 412 413 414 415 416 417
  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);
    }

418 419 420 421 422 423
  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
424 425 426 427 428 429 430
  entry->destroyed = TRUE;

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

static GtkBindingEntry*
431
binding_ht_lookup_entry (GtkBindingSet  *set,
432 433
                         guint           keyval,
                         GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
434 435 436
{
  GtkBindingEntry lookup_entry = { 0 };
  GtkBindingEntry *entry;
437

Tim Janik's avatar
Tim Janik committed
438 439
  if (!binding_entry_hash_table)
    return NULL;
440

Tim Janik's avatar
Tim Janik committed
441 442
  lookup_entry.keyval = keyval;
  lookup_entry.modifiers = modifiers;
443

Tim Janik's avatar
Tim Janik committed
444 445 446 447 448 449 450 451 452
  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
453
binding_compose_params (GObject         *object,
454 455 456
                        GtkBindingArg   *args,
                        GSignalQuery    *query,
                        GValue         **params_p)
Tim Janik's avatar
Tim Janik committed
457
{
458 459
  GValue *params;
  const GType *types;
Tim Janik's avatar
Tim Janik committed
460 461
  guint i;
  gboolean valid;
462

463
  params = g_new0 (GValue, query->n_params + 1);
Tim Janik's avatar
Tim Janik committed
464
  *params_p = params;
465 466 467 468 469 470

  /* 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++;
471

472
  types = query->param_types;
Tim Janik's avatar
Tim Janik committed
473
  valid = TRUE;
474
  for (i = 1; i < query->n_params + 1 && valid; i++)
Tim Janik's avatar
Tim Janik committed
475
    {
Javier Jardon's avatar
Javier Jardon committed
476
      GValue tmp_value = G_VALUE_INIT;
477

478 479 480
      g_value_init (params, *types);

      switch (G_TYPE_FUNDAMENTAL (args->arg_type))
481 482 483 484 485 486 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
        {
        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;
        }
552

553
      if (valid)
554 555 556
        {
          if (!g_value_transform (&tmp_value, params))
            valid = FALSE;
557

558 559
          g_value_unset (&tmp_value);
        }
560

Tim Janik's avatar
Tim Janik committed
561 562 563 564
      types++;
      params++;
      args++;
    }
565

Tim Janik's avatar
Tim Janik committed
566 567
  if (!valid)
    {
568 569 570
      guint j;

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

Tim Janik's avatar
Tim Janik committed
573 574 575
      g_free (*params_p);
      *params_p = NULL;
    }
576

Tim Janik's avatar
Tim Janik committed
577 578 579
  return valid;
}

580 581
static gboolean
gtk_binding_entry_activate (GtkBindingEntry *entry,
582
                            GObject         *object)
Tim Janik's avatar
Tim Janik committed
583 584 585
{
  GtkBindingSignal *sig;
  gboolean old_emission;
586 587
  gboolean handled = FALSE;
  gint i;
588

Tim Janik's avatar
Tim Janik committed
589 590
  old_emission = entry->in_emission;
  entry->in_emission = TRUE;
591

592
  g_object_ref (object);
593

Tim Janik's avatar
Tim Janik committed
594 595
  for (sig = entry->signals; sig; sig = sig->next)
    {
596
      GSignalQuery query;
Tim Janik's avatar
Tim Janik committed
597
      guint signal_id;
598
      GValue *params = NULL;
Javier Jardon's avatar
Javier Jardon committed
599
      GValue return_val = G_VALUE_INIT;
600
      gchar *accelerator = NULL;
601

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

616 617
      g_signal_query (signal_id, &query);
      if (query.n_params != sig->n_args ||
618 619 620 621 622
          (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\": "
623
                     "signature mismatch for signal \"%s\" in the '%s' class ancestry",
624 625 626 627 628
                     entry->binding_set->set_name,
                     accelerator,
                     sig->signal_name,
                     g_type_name (G_OBJECT_TYPE (object)));
        }
Manish Singh's avatar
Manish Singh committed
629
      else if (!(query.signal_flags & G_SIGNAL_ACTION))
630 631 632
        {
          accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers);
          g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": "
633
                     "signal \"%s\" in the '%s' class ancestry cannot be used for action emissions",
634 635 636 637 638
                     entry->binding_set->set_name,
                     accelerator,
                     sig->signal_name,
                     g_type_name (G_OBJECT_TYPE (object)));
        }
639 640
      g_free (accelerator);
      if (accelerator)
641
        continue;
Tim Janik's avatar
Tim Janik committed
642

643
      if (query.return_type == G_TYPE_BOOLEAN)
644
        g_value_init (&return_val, G_TYPE_BOOLEAN);
645

646 647 648
      g_signal_emitv (params, signal_id, 0, &return_val);

      if (query.return_type == G_TYPE_BOOLEAN)
649 650 651 652 653
        {
          if (g_value_get_boolean (&return_val))
            handled = TRUE;
          g_value_unset (&return_val);
        }
654
      else
655
        handled = TRUE;
656

657 658 659 660 661 662 663
      if (params != NULL)
        {
          for (i = 0; i < query.n_params + 1; i++)
            g_value_unset (&params[i]);

          g_free (params);
        }
664

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

669
  g_object_unref (object);
Tim Janik's avatar
Tim Janik committed
670 671 672 673

  entry->in_emission = old_emission;
  if (entry->destroyed && !entry->in_emission)
    binding_entry_free (entry);
674 675

  return handled;
Tim Janik's avatar
Tim Janik committed
676 677
}

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

Tim Janik's avatar
Tim Janik committed
692
  g_return_val_if_fail (set_name != NULL, NULL);
693

Tim Janik's avatar
Tim Janik committed
694
  binding_set = g_new (GtkBindingSet, 1);
695
  binding_set->set_name = (gchar *) g_intern_string (set_name);
Tim Janik's avatar
Tim Janik committed
696 697 698 699 700
  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;
701
  binding_set->parsed = FALSE;
702

703
  binding_set_list = g_slist_prepend (binding_set_list, binding_set);
704

Tim Janik's avatar
Tim Janik committed
705 706 707
  return binding_set;
}

708
/**
709
 * gtk_binding_set_by_class: (skip)
710
 * @object_class: a valid #GObject class
711 712 713 714 715
 *
 * 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.
 *
716
 * Returns: (transfer none): the binding set corresponding to
717
 *     @object_class
Matthias Clasen's avatar
Matthias Clasen committed
718
 */
Tim Janik's avatar
Tim Janik committed
719 720 721
GtkBindingSet*
gtk_binding_set_by_class (gpointer object_class)
{
722
  GObjectClass *class = object_class;
Tim Janik's avatar
Tim Janik committed
723 724
  GtkBindingSet* binding_set;

725
  g_return_val_if_fail (G_IS_OBJECT_CLASS (class), NULL);
Tim Janik's avatar
Tim Janik committed
726 727

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

  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
735
  binding_set = gtk_binding_set_new (g_type_name (G_OBJECT_CLASS_TYPE (class)));
Tim Janik's avatar
Tim Janik committed
736 737 738 739 740
  g_dataset_id_set_data (class, key_id_class_binding_set, binding_set);

  return binding_set;
}

Matthias Clasen's avatar
Matthias Clasen committed
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
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;
}

758 759 760 761
/**
 * gtk_binding_set_find:
 * @set_name: unique binding set name
 *
762 763 764 765
 * 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().
766
 *
767
 * Returns: (nullable) (transfer none): %NULL or the specified binding set
Matthias Clasen's avatar
Matthias Clasen committed
768
 */
Tim Janik's avatar
Tim Janik committed
769
GtkBindingSet*
770
gtk_binding_set_find (const gchar *set_name)
Tim Janik's avatar
Tim Janik committed
771 772
{
  g_return_val_if_fail (set_name != NULL, NULL);
773

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

777 778
/**
 * gtk_binding_set_activate:
Matthias Clasen's avatar
Matthias Clasen committed
779
 * @binding_set: a #GtkBindingSet set to activate
780 781 782 783 784 785 786
 * @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.
 *
787
 * Returns: %TRUE if a binding was found and activated
Matthias Clasen's avatar
Matthias Clasen committed
788
 */
Tim Janik's avatar
Tim Janik committed
789
gboolean
790 791 792 793
gtk_binding_set_activate (GtkBindingSet  *binding_set,
                          guint           keyval,
                          GdkModifierType modifiers,
                          GObject        *object)
Tim Janik's avatar
Tim Janik committed
794 795
{
  GtkBindingEntry *entry;
796

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

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

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

Tim Janik's avatar
Tim Janik committed
807 808 809
  return FALSE;
}

810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
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);
}

827 828
/**
 * gtk_binding_entry_skip:
Matthias Clasen's avatar
Matthias Clasen committed
829
 * @binding_set: a #GtkBindingSet to skip an entry of
830 831 832
 * @keyval:      key value of binding to skip
 * @modifiers:   key modifier of binding to skip
 *
833 834 835
 * 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
836 837
 *
 * Since: 2.12
Matthias Clasen's avatar
Matthias Clasen committed
838
 */
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
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;
}

859 860
/**
 * gtk_binding_entry_remove:
Matthias Clasen's avatar
Matthias Clasen committed
861
 * @binding_set: a #GtkBindingSet to remove an entry of
862 863 864 865 866
 * @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
867
 */
Tim Janik's avatar
Tim Janik committed
868
void
869 870 871
gtk_binding_entry_remove (GtkBindingSet  *binding_set,
                          guint           keyval,
                          GdkModifierType modifiers)
Tim Janik's avatar
Tim Janik committed
872 873
{
  GtkBindingEntry *entry;
874

Tim Janik's avatar
Tim Janik committed
875
  g_return_if_fail (binding_set != NULL);
876

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

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

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

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

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

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

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

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

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

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

987 988
/**
 * gtk_binding_entry_add_signal:
Matthias Clasen's avatar
Matthias Clasen committed
989
 * @binding_set: a #GtkBindingSet to install an entry for
990 991 992 993
 * @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
994
 * @...:         arguments to @signal_name
995 996
 *
 * Override or install a new key binding for @keyval with @modifiers on
Matthias Clasen's avatar
Matthias Clasen committed
997
 * @binding_set. When the binding is activated, @signal_name will be
998 999
 * emitted on the target widget, with @n_args @Varargs used as
 * arguments.
1000 1001 1002 1003 1004 1005 1006 1007
 *
 * Each argument to the signal must be passed as a pair of varargs: the
 * #GType of the argument, followed by the argument value (which must
 * be of the given type). There must be @n_args pairs in total.
 *
 * ## Adding a Key Binding
 *
 * |[<!-- language="C" -->
Timm Bäder's avatar
Timm Bäder committed
1008 1009 1010 1011 1012 1013 1014 1015
 * GtkBindingSet *binding_set;
 * GdkModifierType modmask = GDK_CONTROL_MASK;
 * int count = 1;
 * gtk_binding_entry_add_signal (binding_set,
 *                               GDK_KEY_space,
 *                               modmask,
 *                               "move-cursor", 2,
 *                               GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_PAGES,
1016 1017 1018
 *                               G_TYPE_INT, count,
 *                               G_TYPE_BOOLEAN, FALSE);
 * ]|
Matthias Clasen's avatar
Matthias Clasen committed
1019
 */
Tim Janik's avatar
Tim Janik committed
1020 1021
void
gtk_binding_entry_add_signal (GtkBindingSet  *binding_set,
1022 1023 1024 1025 1026
                              guint           keyval,
                              GdkModifierType modifiers,
                              const gchar    *signal_name,
                              guint           n_args,
                              ...)
Tim Janik's avatar
Tim Janik committed
1027 1028 1029 1030 1031 1032 1033
{
  GSList *slist, *free_slist;
  va_list args;
  guint i;

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

Tim Janik's avatar
Tim Janik committed
1035 1036 1037 1038 1039 1040
  va_start (args, n_args);
  slist = NULL;
  for (i = 0; i < n_args; i++)
    {
      GtkBindingArg *arg;

1041
      arg = g_slice_new0 (GtkBindingArg);
Tim Janik's avatar
Tim Janik committed
1042 1043
      slist = g_slist_prepend (slist, arg);

Michael Natterer's avatar
Michael Natterer committed
1044
      arg->arg_type = va_arg (args, GType);
Manish Singh's avatar
Manish Singh committed
1045
      switch (G_TYPE_FUNDAMENTAL (arg->arg_type))
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
        {
        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)
            {
1073
              g_warning ("gtk_binding_entry_add_signal(): type '%s' arg[%u] is 'NULL'",
1074 1075 1076 1077 1078 1079
                         g_type_name (arg->arg_type),
                         i);
              i += n_args + 1;
            }
          break;
        default:
1080
          g_warning ("gtk_binding_entry_add_signal(): unsupported type '%s' for arg[%u]",
1081 1082 1083 1084
                     g_type_name (arg->arg_type), i);
          i += n_args + 1;
          break;
        }
Tim Janik's avatar
Tim Janik committed
1085 1086 1087
    }
  va_end (args);

1088
  if (i == n_args || i == 0)
Tim Janik's avatar
Tim Janik committed
1089 1090
    {
      slist = g_slist_reverse (slist);
1091
      _gtk_binding_entry_add_signall (binding_set, keyval, modifiers, signal_name, slist);
Tim Janik's avatar
Tim Janik committed
1092 1093 1094 1095 1096
    }

  free_slist = slist;
  while (slist)
    {
1097
      g_slice_free (GtkBindingArg, slist->data);
Tim Janik's avatar
Tim Janik committed
1098 1099 1100 1101 1102
      slist = slist->next;
    }
  g_slist_free (free_slist);
}

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 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374
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
1375
 * Signal descriptions may either bind a key combination to
1376
 * one or more signals:
1377
 * |[
Matthias Clasen's avatar
Matthias Clasen committed
1378 1379
 *   bind "key" {
 *     "signalname" (param, ...)
1380 1381
 *     ...
 *   }
1382
 * ]|
1383 1384
 *
 * Or they may also unbind a key combination:
1385
 * |[
Matthias Clasen's avatar
Matthias Clasen committed
1386
 *   unbind "key"
1387
 * ]|
1388 1389 1390 1391
 *
 * Key combinations must be in a format that can be parsed by
 * gtk_accelerator_parse().
 *
1392 1393 1394
 * Returns: %G_TOKEN_NONE if the signal was successfully parsed and added,
 *     the expected token otherwise
 *
1395
 * Since: 3.0
1396 1397
 */
GTokenType
1398
gtk_binding_entry_add_signal_from_string (GtkBindingSet *binding_set,
1399
                                          const gchar   *signal_desc)
1400 1401 1402 1403
{
  static GScanner *scanner = NULL;
  GTokenType ret;

1404 1405
  g_return_val_if_fail (binding_set != NULL, G_TOKEN_NONE);
  g_return_val_if_fail (signal_desc != NULL, G_TOKEN_NONE);
1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416

  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);
1417 1418

  return ret;
1419 1420
}

1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
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;
}

1461
static gboolean
1462 1463 1464
gtk_bindings_activate_list (GObject  *object,
                            GSList   *entries,
                            gboolean  is_release)
Tim Janik's avatar
Tim Janik committed
1465
{
1466 1467
  GtkStyleContext *context;
  GtkBindingSet *binding_set;
Tim Janik's avatar
Tim Janik committed
1468
  gboolean handled = FALSE;
1469 1470
  gboolean unbound = FALSE;
  GPtrArray *array;
Tim Janik's avatar
Tim Janik committed
1471 1472 1473 1474

  if (!entries)
    return FALSE;

1475 1476
  context = gtk_widget_get_style_context (GTK_WIDGET (object));

1477
  gtk_style_context_get (context, gtk_style_context_get_state (context),
1478
                         "-gtk-key-bindings", &array,
1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
                         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);
1490
          if (handled || unbound)
1491 1492 1493 1494 1495 1496 1497 1498
            break;
        }

      g_ptr_array_unref (array);

      if (unbound)
        return FALSE;
    }
Tim Janik's avatar
Tim Janik committed
1499 1500 1501

  if (!handled)
    {
Manish Singh's avatar
Manish Singh committed
1502
      GType class_type;
1503

Manish Singh's avatar
Manish Singh committed
1504
      class_type = G_TYPE_FROM_INSTANCE (object);
1505

Tim Janik's avatar
Tim Janik committed
1506
      while (class_type && !handled)
1507
        {
Matthias Clasen's avatar
Matthias Clasen committed
1508
          binding_set = gtk_binding_set_find_interned (g_type_name (class_type));
1509
          class_type = g_type_parent (class_type);
1510 1511 1512 1513 1514 1515 1516

          if (!binding_set)
            continue;

          handled = binding_activate (binding_set, entries,
                                      object, is_release,
                                      &unbound);
1517 1518
          if (unbound)
            break;
1519
        }
1520 1521 1522

      if (unbound)
        return FALSE;
Tim Janik's avatar
Tim Janik committed
1523 1524 1525 1526 1527
    }

  return handled;
}

1528 1529 1530 1531 1532 1533 1534 1535 1536
/**
 * 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.
 *
1537
 * Returns: %TRUE if a binding was found and activated