gtkcssselector.c 55.1 KB
Newer Older
Benjamin Otte's avatar
Benjamin Otte committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* GTK - The GIMP Toolkit
 * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Benjamin Otte's avatar
Benjamin Otte committed
16 17 18 19 20 21
 */

#include "config.h"

#include "gtkcssselectorprivate.h"

22
#include <stdlib.h>
23 24
#include <string.h>

25
#include "gtkcssprovider.h"
26 27
#include "gtkstylecontextprivate.h"

28
#if defined(_MSC_VER) && _MSC_VER >= 1500
29 30 31
# include <intrin.h>
#endif

Benjamin Otte's avatar
Benjamin Otte committed
32
typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
33 34 35
typedef gboolean (* GtkCssSelectorForeachFunc) (const GtkCssSelector *selector,
                                                const GtkCssMatcher  *matcher,
                                                gpointer              data);
Benjamin Otte's avatar
Benjamin Otte committed
36 37 38 39

struct _GtkCssSelectorClass {
  const char        *name;

40 41
  void              (* print)       (const GtkCssSelector       *selector,
                                     GString                    *string);
42
  /* NULL or an iterator that calls func with each submatcher of @matcher.
43
   * Potentially no submatcher exists.
44 45 46 47 48 49 50 51
   * If any @invocation of @func returns %TRUE, the function will immediately
   * return %TRUE itself. If @func never returns %TRUE (or isn't called at all),
   * %FALSE will be returned.
   */
  gboolean          (* foreach_matcher)  (const GtkCssSelector       *selector,
                                          const GtkCssMatcher        *matcher,
                                          GtkCssSelectorForeachFunc   func,
                                          gpointer                    data);
52 53
  gboolean          (* match_one)   (const GtkCssSelector       *selector,
                                     const GtkCssMatcher        *matcher);
54 55
  GtkCssChange      (* get_change)  (const GtkCssSelector       *selector,
				     GtkCssChange                previous_change);
56 57 58 59
  void              (* add_specificity)  (const GtkCssSelector  *selector,
                                          guint                 *ids,
                                          guint                 *classes,
                                          guint                 *elements);
60
  guint             (* hash_one)    (const GtkCssSelector       *selector);
61 62
  int               (* compare_one) (const GtkCssSelector       *a,
				     const GtkCssSelector       *b);
Benjamin Otte's avatar
Benjamin Otte committed
63

64
  guint         is_simple :1;
Benjamin Otte's avatar
Benjamin Otte committed
65
};
66

67 68 69 70 71 72
typedef enum {
  POSITION_FORWARD,
  POSITION_BACKWARD,
  POSITION_ONLY,
  POSITION_SORTED
} PositionType;
73
#define POSITION_TYPE_BITS 4
74 75 76 77 78 79 80 81
#define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2)

union _GtkCssSelector
{
  const GtkCssSelectorClass     *class;         /* type of check this selector does */
  struct {
    const GtkCssSelectorClass   *class;
    const char                  *name;          /* interned */
82
  }                              id;
83 84 85 86 87 88
  struct {
    const GtkCssSelectorClass   *class;
    GQuark                       style_class;
  }                              style_class;
  struct {
    const GtkCssSelectorClass   *class;
89
    const char                  *name;          /* interned */
90 91 92 93 94 95 96 97 98 99 100
  }                              name;
  struct {
    const GtkCssSelectorClass   *class;
    GtkStateFlags                state;
  }                              state;
  struct {
    const GtkCssSelectorClass   *class;
    PositionType                 type :POSITION_TYPE_BITS;
    gssize                       a :POSITION_NUMBER_BITS;
    gssize                       b :POSITION_NUMBER_BITS;
  }                              position;
Benjamin Otte's avatar
Benjamin Otte committed
101 102
};

103
#define GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET G_MAXINT32
104 105 106
struct _GtkCssSelectorTree
{
  GtkCssSelector selector;
107 108 109 110
  gint32 parent_offset;
  gint32 previous_offset;
  gint32 sibling_offset;
  gint32 matches_offset; /* pointers that we return as matches if selector matches */
111 112 113 114 115 116 117 118
};

static gboolean
gtk_css_selector_equal (const GtkCssSelector *a,
			const GtkCssSelector *b)
{
  return
    a->class == b->class &&
119
    a->class->compare_one (a, b) == 0;
120 121 122
}

static guint
123
gtk_css_selector_hash_one (const GtkCssSelector *selector)
124
{
125
  return GPOINTER_TO_UINT (selector->class) ^ selector->class->hash_one (selector);
126 127
}

128 129 130 131 132 133 134 135 136
static gpointer *
gtk_css_selector_tree_get_matches (const GtkCssSelectorTree *tree)
{
  if (tree->matches_offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
    return NULL;

  return (gpointer *) ((guint8 *)tree + tree->matches_offset);
}

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
static void
g_ptr_array_insert_sorted (GPtrArray *array,
                           gpointer   data)
{
  gint i;

  for (i = 0; i < array->len; i++)
    {
      if (data == array->pdata[i])
        return;

      if (data < array->pdata[i])
        break;
    }

  g_ptr_array_insert (array, i, data);
}

155
static void
156 157
gtk_css_selector_tree_found_match (const GtkCssSelectorTree  *tree,
				   GPtrArray                **array)
158 159
{
  int i;
160
  gpointer *matches;
161

162 163
  matches = gtk_css_selector_tree_get_matches (tree);
  if (matches)
164
    {
165 166 167
      if (!*array)
        *array = g_ptr_array_sized_new (16);

168
      for (i = 0; matches[i] != NULL; i++)
169
        g_ptr_array_insert_sorted (*array, matches[i]);
170 171 172
    }
}

Benjamin Otte's avatar
Benjamin Otte committed
173 174
static gboolean
gtk_css_selector_match (const GtkCssSelector *selector,
Benjamin Otte's avatar
Benjamin Otte committed
175
                        const GtkCssMatcher  *matcher)
Benjamin Otte's avatar
Benjamin Otte committed
176
{
177 178
  return selector->class->match_one (selector, matcher);
}
Benjamin Otte's avatar
Benjamin Otte committed
179

180 181 182 183 184 185 186
static gboolean
gtk_css_selector_foreach (const GtkCssSelector      *selector,
                          const GtkCssMatcher       *matcher,
                          GtkCssSelectorForeachFunc  func,
                          gpointer                   data)
{
  return selector->class->foreach_matcher (selector, matcher, func, data);
Benjamin Otte's avatar
Benjamin Otte committed
187 188
}

189 190 191 192 193 194 195 196 197
static int
gtk_css_selector_compare_one (const GtkCssSelector *a, const GtkCssSelector *b)
{
  if (a->class != b->class)
    return strcmp (a->class->name, b->class->name);
  else
    return a->class->compare_one (a, b);
}
  
198 199 200
static const GtkCssSelector *
gtk_css_selector_previous (const GtkCssSelector *selector)
{
201 202 203
  selector = selector + 1;

  return selector->class ? selector : NULL;
204 205
}

206 207 208 209 210 211 212 213 214 215
static const GtkCssSelectorTree *
gtk_css_selector_tree_at_offset (const GtkCssSelectorTree *tree,
				 gint32 offset)
{
  if (offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
    return NULL;

  return (GtkCssSelectorTree *) ((guint8 *)tree + offset);
}

216 217 218
static const GtkCssSelectorTree *
gtk_css_selector_tree_get_parent (const GtkCssSelectorTree *tree)
{
219
  return gtk_css_selector_tree_at_offset (tree, tree->parent_offset);
220 221 222 223 224
}

static const GtkCssSelectorTree *
gtk_css_selector_tree_get_previous (const GtkCssSelectorTree *tree)
{
225
  return gtk_css_selector_tree_at_offset (tree, tree->previous_offset);
226 227 228 229 230
}

static const GtkCssSelectorTree *
gtk_css_selector_tree_get_sibling (const GtkCssSelectorTree *tree)
{
231
  return gtk_css_selector_tree_at_offset (tree, tree->sibling_offset);
232 233
}

234 235 236 237 238 239 240 241 242 243 244
/* DEFAULTS */

static void
gtk_css_selector_default_add_specificity (const GtkCssSelector *selector,
                                          guint                *ids,
                                          guint                *classes,
                                          guint                *elements)
{
  /* no specificity changes */
}
 
245
static gboolean
246 247 248 249
gtk_css_selector_default_foreach_matcher (const GtkCssSelector       *selector,
                                          const GtkCssMatcher        *matcher,
                                          GtkCssSelectorForeachFunc   func,
                                          gpointer                    data)
250
{
251
  return func (selector, matcher, data);
252 253 254
}

static gboolean
255 256
gtk_css_selector_default_match_one (const GtkCssSelector *selector,
                                    const GtkCssMatcher  *matcher)
257
{
258
  return TRUE;
259 260
}

261 262 263 264 265 266 267 268 269 270 271 272 273
static guint
gtk_css_selector_default_hash_one (const GtkCssSelector *selector)
{
  return 0;
}

static int
gtk_css_selector_default_compare_one (const GtkCssSelector *a,
                                      const GtkCssSelector *b)
{
  return 0;
}

Benjamin Otte's avatar
Benjamin Otte committed
274 275 276 277 278 279 280 281 282 283
/* DESCENDANT */

static void
gtk_css_selector_descendant_print (const GtkCssSelector *selector,
                                   GString              *string)
{
  g_string_append_c (string, ' ');
}

static gboolean
284 285 286 287
gtk_css_selector_descendant_foreach_matcher (const GtkCssSelector      *selector,
                                             const GtkCssMatcher       *matcher,
                                             GtkCssSelectorForeachFunc  func,
                                             gpointer                   data)
Benjamin Otte's avatar
Benjamin Otte committed
288
{
Benjamin Otte's avatar
Benjamin Otte committed
289 290 291
  GtkCssMatcher ancestor;

  while (_gtk_css_matcher_get_parent (&ancestor, matcher))
Benjamin Otte's avatar
Benjamin Otte committed
292
    {
Benjamin Otte's avatar
Benjamin Otte committed
293 294
      matcher = &ancestor;

295
      if (func (selector, &ancestor, data))
Benjamin Otte's avatar
Benjamin Otte committed
296
        return TRUE;
297 298 299 300 301

      /* any matchers are dangerous here, as we may loop forever, but
	 we can terminate now as all possible matches have already been added */
      if (_gtk_css_matcher_matches_any (matcher))
	break;
Benjamin Otte's avatar
Benjamin Otte committed
302 303 304 305 306
    }

  return FALSE;
}

307
static GtkCssChange
308
gtk_css_selector_descendant_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
309
{
310
  return _gtk_css_change_for_child (previous_change);
311 312
}

Benjamin Otte's avatar
Benjamin Otte committed
313 314 315
static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
  "descendant",
  gtk_css_selector_descendant_print,
316
  gtk_css_selector_descendant_foreach_matcher,
317
  gtk_css_selector_default_match_one,
318
  gtk_css_selector_descendant_get_change,
319
  gtk_css_selector_default_add_specificity,
320 321
  gtk_css_selector_default_hash_one,
  gtk_css_selector_default_compare_one,
322
  FALSE
Benjamin Otte's avatar
Benjamin Otte committed
323 324 325 326 327 328 329 330 331 332 333 334
};

/* CHILD */

static void
gtk_css_selector_child_print (const GtkCssSelector *selector,
                              GString              *string)
{
  g_string_append (string, " > ");
}

static gboolean
335 336 337 338
gtk_css_selector_child_foreach_matcher (const GtkCssSelector      *selector,
                                        const GtkCssMatcher       *matcher,
                                        GtkCssSelectorForeachFunc  func,
                                        gpointer                   data)
Benjamin Otte's avatar
Benjamin Otte committed
339
{
Benjamin Otte's avatar
Benjamin Otte committed
340 341 342
  GtkCssMatcher parent;

  if (!_gtk_css_matcher_get_parent (&parent, matcher))
Benjamin Otte's avatar
Benjamin Otte committed
343 344
    return FALSE;

345
  return func (selector, &parent, data);
Benjamin Otte's avatar
Benjamin Otte committed
346 347
}

348
static GtkCssChange
349
gtk_css_selector_child_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
350
{
351
  return _gtk_css_change_for_child (previous_change);
352 353
}

Benjamin Otte's avatar
Benjamin Otte committed
354 355 356
static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
  "child",
  gtk_css_selector_child_print,
357
  gtk_css_selector_child_foreach_matcher,
358
  gtk_css_selector_default_match_one,
359
  gtk_css_selector_child_get_change,
360
  gtk_css_selector_default_add_specificity,
361 362
  gtk_css_selector_default_hash_one,
  gtk_css_selector_default_compare_one,
363
  FALSE
Benjamin Otte's avatar
Benjamin Otte committed
364 365
};

366 367 368 369 370 371 372 373 374 375
/* SIBLING */

static void
gtk_css_selector_sibling_print (const GtkCssSelector *selector,
                                GString              *string)
{
  g_string_append (string, " ~ ");
}

static gboolean
376 377 378 379
gtk_css_selector_sibling_foreach_matcher (const GtkCssSelector      *selector,
                                          const GtkCssMatcher       *matcher,
                                          GtkCssSelectorForeachFunc  func,
                                          gpointer                   data)
380
{
Benjamin Otte's avatar
Benjamin Otte committed
381 382 383
  GtkCssMatcher previous;

  while (_gtk_css_matcher_get_previous (&previous, matcher))
384
    {
Benjamin Otte's avatar
Benjamin Otte committed
385 386
      matcher = &previous;

387
      if (func (selector, matcher, data))
388
        return TRUE;
389 390 391 392 393

      /* any matchers are dangerous here, as we may loop forever, but
	 we can terminate now as all possible matches have already been added */
      if (_gtk_css_matcher_matches_any (matcher))
	break;
394 395 396 397 398
    }

  return FALSE;
}

399
static GtkCssChange
400
gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
401
{
402
  return _gtk_css_change_for_sibling (previous_change);
403 404
}

405 406 407
static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
  "sibling",
  gtk_css_selector_sibling_print,
408
  gtk_css_selector_sibling_foreach_matcher,
409
  gtk_css_selector_default_match_one,
410
  gtk_css_selector_sibling_get_change,
411
  gtk_css_selector_default_add_specificity,
412 413
  gtk_css_selector_default_hash_one,
  gtk_css_selector_default_compare_one,
414
  FALSE
415 416 417 418 419 420 421 422 423 424 425 426
};

/* ADJACENT */

static void
gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
                                 GString              *string)
{
  g_string_append (string, " + ");
}

static gboolean
427 428 429 430
gtk_css_selector_adjacent_foreach_matcher (const GtkCssSelector      *selector,
                                           const GtkCssMatcher       *matcher,
                                           GtkCssSelectorForeachFunc  func,
                                           gpointer                   data)
431
{
Benjamin Otte's avatar
Benjamin Otte committed
432 433 434
  GtkCssMatcher previous;

  if (!_gtk_css_matcher_get_previous (&previous, matcher))
435
    return FALSE;
436 437
  
  return func (selector, &previous, data);
438 439
}

440
static GtkCssChange
441
gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
442
{
443
  return _gtk_css_change_for_sibling (previous_change);
444 445
}

446 447 448
static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
  "adjacent",
  gtk_css_selector_adjacent_print,
449
  gtk_css_selector_adjacent_foreach_matcher,
450
  gtk_css_selector_default_match_one,
451
  gtk_css_selector_adjacent_get_change,
452
  gtk_css_selector_default_add_specificity,
453 454
  gtk_css_selector_default_hash_one,
  gtk_css_selector_default_compare_one,
455
  FALSE
456 457
};

458 459 460 461 462 463
/* SIMPLE SELECTOR DEFINE */

#define DEFINE_SIMPLE_SELECTOR(n, \
                               c, \
                               print_func, \
                               match_func, \
464
                               hash_func, \
465 466 467 468 469 470 471 472 473 474 475
                               comp_func, \
                               increase_id_specificity, \
                               increase_class_specificity, \
                               increase_element_specificity) \
static void \
gtk_css_selector_ ## n ## _print (const GtkCssSelector *selector, \
                                    GString              *string) \
{ \
  print_func (selector, string); \
} \
\
476 477 478 479 480 481 482 483 484
static void \
gtk_css_selector_not_ ## n ## _print (const GtkCssSelector *selector, \
                                      GString              *string) \
{ \
  g_string_append (string, ":not("); \
  print_func (selector, string); \
  g_string_append (string, ")"); \
} \
\
485
static gboolean \
486 487
gtk_css_selector_not_ ## n ## _match_one (const GtkCssSelector *selector, \
                                          const GtkCssMatcher  *matcher) \
488
{ \
489
  return !match_func (selector, matcher); \
490 491
} \
\
492 493 494 495 496 497
static GtkCssChange \
gtk_css_selector_ ## n ## _get_change (const GtkCssSelector *selector, GtkCssChange previous_change) \
{ \
  return previous_change | GTK_CSS_CHANGE_ ## c; \
} \
\
498 499 500 501 502 503 504
static void \
gtk_css_selector_ ## n ## _add_specificity (const GtkCssSelector *selector, \
                                            guint                *ids, \
                                            guint                *classes, \
                                            guint                *elements) \
{ \
  if (increase_id_specificity) \
505 506 507
    { \
      (*ids)++; \
    } \
508
  if (increase_class_specificity) \
509 510 511
    { \
      (*classes)++; \
    } \
512
  if (increase_element_specificity) \
513 514 515
    { \
      (*elements)++; \
    } \
516 517
} \
\
518 519 520
static const GtkCssSelectorClass GTK_CSS_SELECTOR_ ## c = { \
  G_STRINGIFY(n), \
  gtk_css_selector_ ## n ## _print, \
521
  gtk_css_selector_default_foreach_matcher, \
522
  match_func, \
523
  gtk_css_selector_ ## n ## _get_change, \
524
  gtk_css_selector_ ## n ## _add_specificity, \
525
  hash_func, \
526
  comp_func, \
527
  TRUE \
528 529 530 531 532
};\
\
static const GtkCssSelectorClass GTK_CSS_SELECTOR_NOT_ ## c = { \
  "not_" G_STRINGIFY(n), \
  gtk_css_selector_not_ ## n ## _print, \
533
  gtk_css_selector_default_foreach_matcher, \
534
  gtk_css_selector_not_ ## n ## _match_one, \
535
  gtk_css_selector_ ## n ## _get_change, \
536
  gtk_css_selector_ ## n ## _add_specificity, \
537
  hash_func, \
538
  comp_func, \
539
  TRUE \
540 541
};

542 543 544
/* ANY */

static void
545 546
print_any (const GtkCssSelector *selector,
           GString              *string)
547 548 549 550 551
{
  g_string_append_c (string, '*');
}

static gboolean
552 553
match_any (const GtkCssSelector *selector,
           const GtkCssMatcher  *matcher)
554
{
555
  return TRUE;
556 557
}

558 559
#undef GTK_CSS_CHANGE_ANY
#define GTK_CSS_CHANGE_ANY 0
560 561 562
DEFINE_SIMPLE_SELECTOR(any, ANY, print_any, match_any, 
                       gtk_css_selector_default_hash_one, gtk_css_selector_default_compare_one,
                       FALSE, FALSE, FALSE)
563
#undef GTK_CSS_CHANGE_ANY
564

Benjamin Otte's avatar
Benjamin Otte committed
565 566 567
/* NAME */

static void
568 569
print_name (const GtkCssSelector *selector,
            GString              *string)
Benjamin Otte's avatar
Benjamin Otte committed
570
{
571
  g_string_append (string, selector->name.name);
Benjamin Otte's avatar
Benjamin Otte committed
572 573 574
}

static gboolean
575 576
match_name (const GtkCssSelector *selector,
            const GtkCssMatcher  *matcher)
Benjamin Otte's avatar
Benjamin Otte committed
577
{
578
  return _gtk_css_matcher_has_name (matcher, selector->name.name);
579 580
}

581 582 583
static guint
hash_name (const GtkCssSelector *a)
{
584
  return g_str_hash (a->name.name);
585 586
}

587
static int
588 589
comp_name (const GtkCssSelector *a,
           const GtkCssSelector *b)
590
{
591 592
  return strcmp (a->name.name,
		 b->name.name);
593 594
}

595
DEFINE_SIMPLE_SELECTOR(name, NAME, print_name, match_name, hash_name, comp_name, FALSE, FALSE, TRUE)
Benjamin Otte's avatar
Benjamin Otte committed
596 597 598 599

/* CLASS */

static void
600 601
print_class (const GtkCssSelector *selector,
             GString              *string)
Benjamin Otte's avatar
Benjamin Otte committed
602 603
{
  g_string_append_c (string, '.');
604
  g_string_append (string, g_quark_to_string (selector->style_class.style_class));
Benjamin Otte's avatar
Benjamin Otte committed
605 606 607
}

static gboolean
608 609
match_class (const GtkCssSelector *selector,
             const GtkCssMatcher  *matcher)
610
{
611
  return _gtk_css_matcher_has_class (matcher, selector->style_class.style_class);
612 613
}

614 615 616
static guint
hash_class (const GtkCssSelector *a)
{
617
  return a->style_class.style_class;
618 619
}

620
static int
621 622
comp_class (const GtkCssSelector *a,
            const GtkCssSelector *b)
623
{
624 625 626 627 628 629
  if (a->style_class.style_class < b->style_class.style_class)
    return -1;
  if (a->style_class.style_class > b->style_class.style_class)
    return 1;
  else
    return 0;
630 631
}

632
DEFINE_SIMPLE_SELECTOR(class, CLASS, print_class, match_class, hash_class, comp_class, FALSE, TRUE, FALSE)
Benjamin Otte's avatar
Benjamin Otte committed
633 634 635 636

/* ID */

static void
637 638
print_id (const GtkCssSelector *selector,
          GString              *string)
Benjamin Otte's avatar
Benjamin Otte committed
639 640
{
  g_string_append_c (string, '#');
641
  g_string_append (string, selector->id.name);
Benjamin Otte's avatar
Benjamin Otte committed
642 643 644
}

static gboolean
645 646
match_id (const GtkCssSelector *selector,
          const GtkCssMatcher  *matcher)
647
{
648
  return _gtk_css_matcher_has_id (matcher, selector->id.name);
649 650
}

651 652 653
static guint
hash_id (const GtkCssSelector *a)
{
654
  return GPOINTER_TO_UINT (a->id.name);
655 656
}

657
static int
658 659
comp_id (const GtkCssSelector *a,
	 const GtkCssSelector *b)
660
{
661 662 663 664 665 666
  if (a->id.name < b->id.name)
    return -1;
  else if (a->id.name > b->id.name)
    return 1;
  else
    return 0;
667 668
}

669
DEFINE_SIMPLE_SELECTOR(id, ID, print_id, match_id, hash_id, comp_id, TRUE, FALSE, FALSE)
Benjamin Otte's avatar
Benjamin Otte committed
670

671 672
const gchar *
gtk_css_pseudoclass_name (GtkStateFlags state)
Benjamin Otte's avatar
Benjamin Otte committed
673 674 675 676 677
{
  static const char * state_names[] = {
    "active",
    "hover",
    "selected",
678 679
    "disabled",
    "indeterminate",
Benjamin Otte's avatar
Benjamin Otte committed
680
    "focus",
681 682
    "backdrop",
    "dir(ltr)",
683 684
    "dir(rtl)",
    "link",
685
    "visited",
686
    "checked",
687
    "drop(active)"
Benjamin Otte's avatar
Benjamin Otte committed
688
  };
689
  guint i;
Benjamin Otte's avatar
Benjamin Otte committed
690 691 692

  for (i = 0; i < G_N_ELEMENTS (state_names); i++)
    {
693 694
      if (state == (1 << i))
        return state_names[i];
Benjamin Otte's avatar
Benjamin Otte committed
695 696
    }

697 698 699 700 701 702 703 704 705 706
  return NULL;
}

/* PSEUDOCLASS FOR STATE */
static void
print_pseudoclass_state (const GtkCssSelector *selector,
                         GString              *string)
{
  g_string_append_c (string, ':');
  g_string_append (string, gtk_css_pseudoclass_name (selector->state.state));
Benjamin Otte's avatar
Benjamin Otte committed
707 708 709
}

static gboolean
710 711
match_pseudoclass_state (const GtkCssSelector *selector,
                         const GtkCssMatcher  *matcher)
Benjamin Otte's avatar
Benjamin Otte committed
712
{
713
  return (_gtk_css_matcher_get_state (matcher) & selector->state.state) == selector->state.state;
714 715
}

716
static guint
717
hash_pseudoclass_state (const GtkCssSelector *selector)
718
{
719
  return selector->state.state;
720 721 722
}


723
static int
724 725
comp_pseudoclass_state (const GtkCssSelector *a,
		        const GtkCssSelector *b)
726
{
727
  return a->state.state - b->state.state;
728 729
}

730
#define GTK_CSS_CHANGE_PSEUDOCLASS_STATE GTK_CSS_CHANGE_STATE
731 732 733
DEFINE_SIMPLE_SELECTOR(pseudoclass_state, PSEUDOCLASS_STATE, print_pseudoclass_state,
                       match_pseudoclass_state, hash_pseudoclass_state, comp_pseudoclass_state,
                       FALSE, TRUE, FALSE)
734
#undef GTK_CSS_CHANGE_PSEUDOCLASS_STATE
Benjamin Otte's avatar
Benjamin Otte committed
735

736
/* PSEUDOCLASS FOR POSITION */
Benjamin Otte's avatar
Benjamin Otte committed
737 738

static void
739 740
print_pseudoclass_position (const GtkCssSelector *selector,
                            GString              *string)
Benjamin Otte's avatar
Benjamin Otte committed
741
{
742
  switch (selector->position.type)
Benjamin Otte's avatar
Benjamin Otte committed
743
    {
744
    case POSITION_FORWARD:
745
      if (selector->position.a == 0)
Benjamin Otte's avatar
Benjamin Otte committed
746
        {
747
          if (selector->position.b == 1)
748 749
            g_string_append (string, ":first-child");
          else
750
            g_string_append_printf (string, ":nth-child(%d)", selector->position.b);
751
        }
752
      else if (selector->position.a == 2 && selector->position.b == 0)
753
        g_string_append (string, ":nth-child(even)");
754
      else if (selector->position.a == 2 && selector->position.b == 1)
755 756 757 758
        g_string_append (string, ":nth-child(odd)");
      else
        {
          g_string_append (string, ":nth-child(");
759
          if (selector->position.a == 1)
760
            g_string_append (string, "n");
761
          else if (selector->position.a == -1)
762 763
            g_string_append (string, "-n");
          else
764 765 766 767 768
            g_string_append_printf (string, "%dn", selector->position.a);
          if (selector->position.b > 0)
            g_string_append_printf (string, "+%d)", selector->position.b);
          else if (selector->position.b < 0)
            g_string_append_printf (string, "%d)", selector->position.b);
769 770
          else
            g_string_append (string, ")");
Benjamin Otte's avatar
Benjamin Otte committed
771
        }
772 773
      break;
    case POSITION_BACKWARD:
774
      if (selector->position.a == 0)
775
        {
776
          if (selector->position.b == 1)
777 778
            g_string_append (string, ":last-child");
          else
779
            g_string_append_printf (string, ":nth-last-child(%d)", selector->position.b);
780
        }
781
      else if (selector->position.a == 2 && selector->position.b == 0)
782
        g_string_append (string, ":nth-last-child(even)");
783
      else if (selector->position.a == 2 && selector->position.b == 1)
784 785 786 787
        g_string_append (string, ":nth-last-child(odd)");
      else
        {
          g_string_append (string, ":nth-last-child(");
788
          if (selector->position.a == 1)
789
            g_string_append (string, "n");
790
          else if (selector->position.a == -1)
791 792
            g_string_append (string, "-n");
          else
793 794 795 796 797
            g_string_append_printf (string, "%dn", selector->position.a);
          if (selector->position.b > 0)
            g_string_append_printf (string, "+%d)", selector->position.b);
          else if (selector->position.b < 0)
            g_string_append_printf (string, "%d)", selector->position.b);
798 799 800 801 802 803 804 805 806 807 808 809 810
          else
            g_string_append (string, ")");
        }
      break;
    case POSITION_ONLY:
      g_string_append (string, ":only-child");
      break;
    case POSITION_SORTED:
      g_string_append (string, ":sorted");
      break;
    default:
      g_assert_not_reached ();
      break;
Benjamin Otte's avatar
Benjamin Otte committed
811 812 813 814
    }
}

static gboolean
815 816
match_pseudoclass_position (const GtkCssSelector *selector,
		            const GtkCssMatcher  *matcher)
Benjamin Otte's avatar
Benjamin Otte committed
817
{
818
  switch (selector->position.type)
Benjamin Otte's avatar
Benjamin Otte committed
819
    {
820
    case POSITION_FORWARD:
821
      if (!_gtk_css_matcher_has_position (matcher, TRUE, selector->position.a, selector->position.b))
Benjamin Otte's avatar
Benjamin Otte committed
822 823
        return FALSE;
      break;
824
    case POSITION_BACKWARD:
825
      if (!_gtk_css_matcher_has_position (matcher, FALSE, selector->position.a, selector->position.b))
Benjamin Otte's avatar
Benjamin Otte committed
826 827
        return FALSE;
      break;
828 829 830
    case POSITION_ONLY:
      if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
          !_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
Benjamin Otte's avatar
Benjamin Otte committed
831 832
        return FALSE;
      break;
833
    case POSITION_SORTED:
Benjamin Otte's avatar
Benjamin Otte committed
834 835 836 837 838 839
      return FALSE;
    default:
      g_assert_not_reached ();
      return FALSE;
    }

840 841 842
  return TRUE;
}

843
static guint
844
hash_pseudoclass_position (const GtkCssSelector *a)
845
{
846
  return (guint)(((((gulong)a->position.type) << POSITION_NUMBER_BITS) | a->position.a) << POSITION_NUMBER_BITS) | a->position.b;
847 848
}

849
static int
850 851
comp_pseudoclass_position (const GtkCssSelector *a,
			   const GtkCssSelector *b)
852
{
853 854 855 856 857 858 859 860 861 862 863
  int diff;
  
  diff = a->position.type - b->position.type;
  if (diff)
    return diff;

  diff = a->position.a - b->position.a;
  if (diff)
    return diff;

  return a->position.b - b->position.b;
864 865
}

866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
static GtkCssChange
change_pseudoclass_position (const GtkCssSelector *selector)
{
  switch (selector->position.type)
    {
    case POSITION_FORWARD:
      if (selector->position.a == 0 && selector->position.b == 1)
        return GTK_CSS_CHANGE_FIRST_CHILD;
      else
        return GTK_CSS_CHANGE_NTH_CHILD;
    case POSITION_BACKWARD:
      if (selector->position.a == 0 && selector->position.b == 1)
        return GTK_CSS_CHANGE_LAST_CHILD;
      else
        return GTK_CSS_CHANGE_NTH_LAST_CHILD;
    case POSITION_ONLY:
      return GTK_CSS_CHANGE_FIRST_CHILD | GTK_CSS_CHANGE_LAST_CHILD;
    default:
      g_assert_not_reached ();
    case POSITION_SORTED:
      return 0;
    }
}

#define GTK_CSS_CHANGE_PSEUDOCLASS_POSITION change_pseudoclass_position(selector)
891 892 893 894
DEFINE_SIMPLE_SELECTOR(pseudoclass_position, PSEUDOCLASS_POSITION, print_pseudoclass_position,
                       match_pseudoclass_position, hash_pseudoclass_position, comp_pseudoclass_position,
                       FALSE, TRUE, FALSE)
#undef GTK_CSS_CHANGE_PSEUDOCLASS_POSITION
Benjamin Otte's avatar
Benjamin Otte committed
895 896
/* API */

897 898 899 900 901 902 903 904 905 906 907 908 909 910
static guint
gtk_css_selector_size (const GtkCssSelector *selector)
{
  guint size = 0;

  while (selector)
    {
      selector = gtk_css_selector_previous (selector);
      size++;
    }

  return size;
}

911
static GtkCssSelector *
Benjamin Otte's avatar
Benjamin Otte committed
912
gtk_css_selector_new (const GtkCssSelectorClass *class,
913
                      GtkCssSelector            *selector)
Benjamin Otte's avatar
Benjamin Otte committed
914
{
915 916 917 918 919 920 921 922
  guint size;

  size = gtk_css_selector_size (selector);
  selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
  if (size == 0)
    selector[1].class = NULL;
  else
    memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
Benjamin Otte's avatar
Benjamin Otte committed
923

924
  memset (selector, 0, sizeof (GtkCssSelector));
Benjamin Otte's avatar
Benjamin Otte committed
925
  selector->class = class;
Benjamin Otte's avatar
Benjamin Otte committed
926 927 928 929

  return selector;
}

Benjamin Otte's avatar
Benjamin Otte committed
930
static GtkCssSelector *
931 932 933
parse_selector_class (GtkCssParser   *parser,
                      GtkCssSelector *selector,
                      gboolean        negate)
934 935 936 937 938 939 940 941
{
  char *name;
    
  name = _gtk_css_parser_try_name (parser, FALSE);

  if (name == NULL)
    {
      _gtk_css_parser_error (parser, "Expected a valid name for class");
Benjamin Otte's avatar
Benjamin Otte committed
942 943 944
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
945 946
    }

947 948
  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_CLASS
                                          : &GTK_CSS_SELECTOR_CLASS,
949 950
                                   selector);
  selector->style_class.style_class = g_quark_from_string (name);
Benjamin Otte's avatar
Benjamin Otte committed
951

952
  g_free (name);
Benjamin Otte's avatar
Benjamin Otte committed
953 954

  return selector;
955 956
}

Benjamin Otte's avatar
Benjamin Otte committed
957
static GtkCssSelector *
958 959 960
parse_selector_id (GtkCssParser   *parser,
                   GtkCssSelector *selector,
                   gboolean        negate)
961 962 963 964 965 966 967 968
{
  char *name;
    
  name = _gtk_css_parser_try_name (parser, FALSE);

  if (name == NULL)
    {
      _gtk_css_parser_error (parser, "Expected a valid name for id");
Benjamin Otte's avatar
Benjamin Otte committed
969 970 971
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
972 973
    }

974 975
  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_ID
                                          : &GTK_CSS_SELECTOR_ID,
976 977
                                   selector);
  selector->id.name = g_intern_string (name);
Benjamin Otte's avatar
Benjamin Otte committed
978

979
  g_free (name);
Benjamin Otte's avatar
Benjamin Otte committed
980 981

  return selector;
982 983
}

Benjamin Otte's avatar
Benjamin Otte committed
984
static GtkCssSelector *
985 986
parse_selector_pseudo_class_nth_child (GtkCssParser   *parser,
                                       GtkCssSelector *selector,
987 988
                                       PositionType    type,
                                       gboolean        negate)
989
{
990
  int a, b;
991

992
  if (!_gtk_css_parser_try (parser, "(", TRUE))
993
    {
994
      _gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
Benjamin Otte's avatar
Benjamin Otte committed
995 996 997
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
998 999
    }

1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
  if (_gtk_css_parser_try (parser, "even", TRUE))
    {
      a = 2;
      b = 0;
    }
  else if (_gtk_css_parser_try (parser, "odd", TRUE))
    {
      a = 2;
      b = 1;
    }
  else if (type == POSITION_FORWARD &&
           _gtk_css_parser_try (parser, "first", TRUE))
1012
    {
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
      a = 0;
      b = 1;
    }
  else if (type == POSITION_FORWARD &&
           _gtk_css_parser_try (parser, "last", TRUE))
    {
      a = 0;
      b = 1;
      type = POSITION_BACKWARD;
    }
  else
    {
      int multiplier;
1026

1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
      if (_gtk_css_parser_try (parser, "+", TRUE))
        multiplier = 1;
      else if (_gtk_css_parser_try (parser, "-", TRUE))
        multiplier = -1;
      else
        multiplier = 1;

      if (_gtk_css_parser_try_int (parser, &a))
        {
          if (a < 0)
            {
              _gtk_css_parser_error (parser, "Expected an integer");
              if (selector)
                _gtk_css_selector_free (selector);
              return NULL;
            }
          a *= multiplier;
        }
      else if (_gtk_css_parser_has_prefix (parser, "n"))
1046
        {
1047 1048 1049 1050 1051
          a = multiplier;
        }
      else
        {
          _gtk_css_parser_error (parser, "Expected an integer");
Benjamin Otte's avatar
Benjamin Otte committed
1052 1053 1054
          if (selector)
            _gtk_css_selector_free (selector);
          return NULL;
1055 1056
        }

1057
      if (_gtk_css_parser_try (parser, "n", TRUE))
1058
        {
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
          if (_gtk_css_parser_try (parser, "+", TRUE))
            multiplier = 1;
          else if (_gtk_css_parser_try (parser, "-", TRUE))
            multiplier = -1;
          else
            multiplier = 1;

          if (_gtk_css_parser_try_int (parser, &b))
            {
              if (b < 0)
                {
                  _gtk_css_parser_error (parser, "Expected an integer");
                  if (selector)
                    _gtk_css_selector_free (selector);
                  return NULL;
                }
            }
          else
            b = 0;

          b *= multiplier;
1080
        }
1081
      else
1082
        {
1083 1084
          b = a;
          a = 0;
1085
        }
1086
    }
1087

1088 1089 1090 1091 1092 1093
  if (!_gtk_css_parser_try (parser, ")", FALSE))
    {
      _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
1094 1095
    }

1096 1097
  selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
                                          : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
1098 1099 1100 1101
                                   selector);
  selector->position.type = type;
  selector->position.a = a;
  selector->position.b = b;
1102 1103 1104 1105 1106 1107

  return selector;
}

static GtkCssSelector *
parse_selector_pseudo_class (GtkCssParser   *parser,
1108 1109
                             GtkCssSelector *selector,
                             gboolean        negate)
1110 1111 1112
{
  static const struct {
    const char    *name;
1113
    gboolean       deprecated;
1114 1115 1116 1117 1118
    GtkStateFlags  state_flag;
    PositionType   position_type;
    int            position_a;
    int            position_b;
  } pseudo_classes[] = {
1119 1120 1121
    { "first-child",   0, 0,                           POSITION_FORWARD,  0, 1 },
    { "last-child",    0, 0,                           POSITION_BACKWARD, 0, 1 },
    { "only-child",    0, 0,                           POSITION_ONLY,     0, 0 },
1122
    { "sorted",        1, 0,                           POSITION_SORTED,   0, 0 },
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
    { "active",        0, GTK_STATE_FLAG_ACTIVE, },
    { "prelight",      1, GTK_STATE_FLAG_PRELIGHT, },
    { "hover",         0, GTK_STATE_FLAG_PRELIGHT, },
    { "selected",      0, GTK_STATE_FLAG_SELECTED, },
    { "insensitive",   1, GTK_STATE_FLAG_INSENSITIVE, },
    { "disabled",      0, GTK_STATE_FLAG_INSENSITIVE, },
    { "inconsistent",  1, GTK_STATE_FLAG_INCONSISTENT, },
    { "indeterminate", 0, GTK_STATE_FLAG_INCONSISTENT, },
    { "focused",       1, GTK_STATE_FLAG_FOCUSED, },
    { "focus",         0, GTK_STATE_FLAG_FOCUSED, },
    { "backdrop",      0, GTK_STATE_FLAG_BACKDROP, },
    { "dir(ltr)",      0, GTK_STATE_FLAG_DIR_LTR, },
    { "dir(rtl)",      0, GTK_STATE_FLAG_DIR_RTL, },
    { "link",          0, GTK_STATE_FLAG_LINK, },
    { "visited",       0, GTK_STATE_FLAG_VISITED, },
    { "checked",       0, GTK_STATE_FLAG_CHECKED, },
    { "drop(active)",  0, GTK_STATE_FLAG_DROP_ACTIVE, }
1140

1141 1142 1143 1144
  };
  guint i;

  if (_gtk_css_parser_try (parser, "nth-child", FALSE))
1145
    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD, negate);
1146
  else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
1147
    return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD, negate);
1148 1149

  for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
1150
    {
1151
      if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
1152
        {
1153
          if (pseudo_classes[i].state_flag)
1154 1155 1156 1157 1158
            {
              selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
                                                      : &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
                                               selector);
              selector->state.state = pseudo_classes[i].state_flag;
1159
              if (pseudo_classes[i].deprecated)
1160
                {
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171
                  if (pseudo_classes[i + 1].state_flag == pseudo_classes[i].state_flag)
                    _gtk_css_parser_error_full (parser,
                                                GTK_CSS_PROVIDER_ERROR_DEPRECATED,
                                                "The :%s pseudo-class is deprecated. Use :%s instead.",
                                                pseudo_classes[i].name,
                                                pseudo_classes[i + 1].name);
                  else
                    _gtk_css_parser_error_full (parser,
                                                GTK_CSS_PROVIDER_ERROR_DEPRECATED,
                                                "The :%s pseudo-class is deprecated.",
                                                pseudo_classes[i].name);
1172
                }
1173
            }
Benjamin Otte's avatar
Benjamin Otte committed
1174
          else
1175
            {
1176 1177
              selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
                                                      : &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
1178 1179 1180 1181 1182
                                               selector);
              selector->position.type = pseudo_classes[i].position_type;
              selector->position.a = pseudo_classes[i].position_a;
              selector->position.b = pseudo_classes[i].position_b;
            }
Benjamin Otte's avatar
Benjamin Otte committed
1183
          return selector;
1184 1185
        }
    }
1186 1187
      
  _gtk_css_parser_error (parser, "Missing name of pseudo-class");
Benjamin Otte's avatar
Benjamin Otte committed
1188 1189 1190
  if (selector)
    _gtk_css_selector_free (selector);
  return NULL;
1191 1192
}

1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
static GtkCssSelector *
parse_selector_negation (GtkCssParser   *parser,
                         GtkCssSelector *selector)
{
  char *name;

  name = _gtk_css_parser_try_ident (parser, FALSE);
  if (name)
    {
      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_NAME,
1203
                                       selector);
1204
      selector->name.name = g_intern_string (name);
1205 1206
      g_free (name);
    }
1207
  else if (_gtk_css_parser_try (parser, "*", FALSE))
1208
    selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NOT_ANY, selector);
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
  else if (_gtk_css_parser_try (parser, "#", FALSE))
    selector = parse_selector_id (parser, selector, TRUE);
  else if (_gtk_css_parser_try (parser, ".", FALSE))
    selector = parse_selector_class (parser, selector, TRUE);
  else if (_gtk_css_parser_try (parser, ":", FALSE))
    selector = parse_selector_pseudo_class (parser, selector, TRUE);
  else
    {
      _gtk_css_parser_error (parser, "Not a valid selector for :not()");
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
    }
  
  _gtk_css_parser_skip_whitespace (parser);

  if (!_gtk_css_parser_try (parser, ")", FALSE))
    {
      _gtk_css_parser_error (parser, "Missing closing bracket for :not()");
      if (selector)
        _gtk_css_selector_free (selector);
      return NULL;
    }

  return selector;
}

Benjamin Otte's avatar
Benjamin Otte committed
1236 1237
static GtkCssSelector *
parse_simple_selector (GtkCssParser   *parser,
1238
                       GtkCssSelector *selector)
Benjamin Otte's avatar
Benjamin Otte committed
1239
{
1240 1241 1242 1243 1244 1245
  gboolean parsed_something = FALSE;
  char *name;

  name = _gtk_css_parser_try_ident (parser, FALSE);
  if (name)
    {
1246 1247
      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_NAME,
                                       selector);
1248
      selector->name.name = g_intern_string (name);
1249 1250 1251 1252 1253
      g_free (name);
      parsed_something = TRUE;
    }
  else if (_gtk_css_parser_try (parser, "*", FALSE))
    {
1254
      selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector);
1255 1256
      parsed_something = TRUE;
    }
1257 1258 1259

  do {
      if (_gtk_css_parser_try (parser, "#", FALSE))
1260
        selector = parse_selector_id (parser, selector, FALSE);
1261
      else if (_gtk_css_parser_try (parser, ".", FALSE))
1262 1263 1264
        selector = parse_selector_class (parser, selector, FALSE);
      else if (_gtk_css_parser_try (parser, ":not(", TRUE))
        selector = parse_selector_negation (parser, selector);
1265
      else if (_gtk_css_parser_try (parser, ":", FALSE))
1266
        selector = parse_selector_pseudo_class (parser, selector, FALSE);
1267
      else if (!parsed_something)
1268 1269
        {
          _gtk_css_parser_error (parser, "Expected a valid selector");
Benjamin Otte's avatar
Benjamin Otte committed
1270 1271
          if (selector)
            _gtk_css_selector_free (selector);
1272
          return NULL;
1273 1274 1275
        }
      else
        break;
1276 1277

      parsed_something = TRUE;
1278
    }
Benjamin Otte's avatar
Benjamin Otte committed
1279
  while (selector && !_gtk_css_parser_is_eof (parser));
1280 1281

  _gtk_css_parser_skip_whitespace (parser);
Benjamin Otte's avatar
Benjamin Otte committed
1282 1283

  return selector;
1284 1285 1286 1287 1288 1289 1290
}

GtkCssSelector *
_gtk_css_selector_parse (GtkCssParser *parser)
{
  GtkCssSelector *selector = NULL;

Benjamin Otte's avatar
Benjamin Otte committed
1291 1292
  while ((selector = parse_simple_selector (parser, selector)) &&
         !_gtk_css_parser_is_eof (parser) &&
1293
         !_gtk_css_parser_begins_with (parser, ',') &&
Benjamin Otte's avatar
Benjamin Otte committed
1294 1295
         !_gtk_css_parser_begins_with (parser, '{'))
    {
1296
      if (_gtk_css_parser_try (parser, "+", TRUE))
1297
        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector);
1298
      else if (_gtk_css_parser_try (parser, "~", TRUE))
1299
        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector);
1300
      else if (_gtk_css_parser_try (parser, ">", TRUE))
1301
        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector);
Benjamin Otte's avatar
Benjamin Otte committed
1302
      else
1303
        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector);
Benjamin Otte's avatar
Benjamin Otte committed
1304
    }
1305 1306 1307 1308

  return selector;
}

Benjamin Otte's avatar
Benjamin Otte committed
1309 1310 1311 1312 1313
void
_gtk_css_selector_free (GtkCssSelector *selector)
{
  g_return_if_fail (selector != NULL);

1314
  g_free (selector);
Benjamin Otte's avatar
Benjamin Otte committed
1315 1316 1317 1318 1319 1320
}

void
_gtk_css_selector_print (const GtkCssSelector *selector,
                         GString *             str)
{
1321 1322
  const GtkCssSelector *previous;

Benjamin Otte's avatar
Benjamin Otte committed
1323
  g_return_if_fail (selector != NULL);
Benjamin Otte's avatar
Benjamin Otte committed
1324

1325 1326 1327
  previous = gtk_css_selector_previous (selector);
  if (previous)
    _gtk_css_selector_print (previous, str);
Benjamin Otte's avatar
Benjamin Otte committed
1328

Benjamin Otte's avatar
Benjamin Otte committed
1329
  selector->class->print (selector, str);
Benjamin Otte's avatar
Benjamin Otte committed
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345
}

char *
_gtk_css_selector_to_string (const GtkCssSelector *selector)
{
  GString *string;

  g_return_val_if_fail (selector != NULL, NULL);

  string = g_string_new (NULL);

  _gtk_css_selector_print (selector, string);

  return g_string_free (string, FALSE);
}

1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361
static gboolean
gtk_css_selector_foreach_match (const GtkCssSelector *selector,
                                const GtkCssMatcher  *matcher,
                                gpointer              unused)
{
  selector = gtk_css_selector_previous (selector);

  if (selector == NULL)
    return TRUE;

  if (!gtk_css_selector_match (selector, matcher))
    return FALSE;

  return gtk_css_selector_foreach (selector, matcher, gtk_css_selector_foreach_match, NULL);
}

1362 1363 1364 1365
/**
 * _gtk_css_selector_matches:
 * @selector: the selector
 * @path: the path to check
1366
 * @state: The state to match
1367 1368 1369 1370 1371 1372 1373 1374 1375
 *
 * Checks if the @selector matches the given @path. If @length is
 * smaller than the number of elements in @path, it is assumed that
 * only the first @length element of @path are valid and the rest
 * does not exist. This is useful for doing parent matches for the
 * 'inherit' keyword.
 *
 * Returns: %TRUE if the selector matches @path
 **/
Benjamin Otte's avatar
Benjamin Otte committed
1376
gboolean
1377 1378
_gtk_css_selector_matches (const GtkCssSelector *selector,
                           const GtkCssMatcher  *matcher)
Benjamin Otte's avatar
Benjamin Otte committed
1379 1380 1381
{

  g_return_val_if_fail (selector != NULL, FALSE);
1382
  g_return_val_if_fail (matcher != NULL, FALSE);
Benjamin Otte's avatar
Benjamin Otte committed
1383

1384 1385 1386 1387
  if (!gtk_css_selector_match (selector, matcher))
    return FALSE;

  return gtk_css_selector_foreach (selector, matcher, gtk_css_selector_foreach_match, NULL);
Benjamin Otte's avatar
Benjamin Otte committed
1388 1389 1390 1391 1392 1393 1394 1395 1396 1397
}

/* Computes specificity according to CSS 2.1.
 * The arguments must be initialized to 0 */
static void
_gtk_css_selector_get_specificity (const GtkCssSelector *selector,
                                   guint                *ids,
                                   guint                *classes,
                                   guint                *elements)
{
1398
  for (; selector; selector = gtk_css_selector_p