~ubuntu-branches/ubuntu/trusty/gnome-shell/trusty-proposed

« back to all changes in this revision

Viewing changes to .pc/git_add_stylesheets_signal.patch/src/st/st-theme.c

  • Committer: Package Import Robot
  • Author(s): Tim Lunn
  • Date: 2013-04-22 08:38:14 UTC
  • Revision ID: package-import@ubuntu.com-20130422083814-b95ak1pjo5fsw8th
Tags: 3.6.3.1-0ubuntu6
* debian/patches:
    - git_fix_theme_node_crash.patch, git_add_stylesheets_signal.patch
        Fixes crash that occurs when extensions have custom 
        stylesheets (LP: #1064022) 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 
2
/*
 
3
 * st-theme.c: A set of CSS stylesheets used for rule matching
 
4
 *
 
5
 * Copyright 2003-2004 Dodji Seketeli
 
6
 * Copyright 2008, 2009 Red Hat, Inc.
 
7
 *
 
8
 * This program is free software; you can redistribute it and/or modify it
 
9
 * under the terms and conditions of the GNU Lesser General Public License,
 
10
 * version 2.1, as published by the Free Software Foundation.
 
11
 *
 
12
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 
13
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
14
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 
15
 * more details.
 
16
 *
 
17
 * You should have received a copy of the GNU Lesser General Public License
 
18
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 
19
 *
 
20
 * This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
 
21
 *
 
22
 * In moving it to hippo-canvas:
 
23
 * - Reformatted and otherwise edited to match our coding style
 
24
 * - Switched from handling xmlNode to handling HippoStyle
 
25
 * - Simplified by removing things that we don't need or that don't
 
26
 *   make sense in our context.
 
27
 * - The code to get a list of matching properties works quite differently;
 
28
 *   we order things in priority order, but we don't actually try to
 
29
 *   coalesce properties with the same name.
 
30
 *
 
31
 * In moving it to GNOME Shell:
 
32
 *  - Renamed again to StTheme
 
33
 *  - Reformatted to match the gnome-shell coding style
 
34
 *  - Removed notion of "theme engine" from hippo-canvas
 
35
 *  - pseudo-class matching changed from link enum to strings
 
36
 *  - Some code simplification
 
37
 */
 
38
 
 
39
 
 
40
#include <stdlib.h>
 
41
#include <string.h>
 
42
 
 
43
#include <gio/gio.h>
 
44
 
 
45
#include "st-theme-node.h"
 
46
#include "st-theme-private.h"
 
47
 
 
48
static GObject *st_theme_constructor (GType                  type,
 
49
                                      guint                  n_construct_properties,
 
50
                                      GObjectConstructParam *construct_properties);
 
51
 
 
52
static void st_theme_finalize     (GObject      *object);
 
53
static void st_theme_set_property (GObject      *object,
 
54
                                   guint         prop_id,
 
55
                                   const GValue *value,
 
56
                                   GParamSpec   *pspec);
 
57
static void st_theme_get_property (GObject      *object,
 
58
                                   guint         prop_id,
 
59
                                   GValue       *value,
 
60
                                   GParamSpec   *pspec);
 
61
 
 
62
struct _StTheme
 
63
{
 
64
  GObject parent;
 
65
 
 
66
  char *application_stylesheet;
 
67
  char *default_stylesheet;
 
68
  char *theme_stylesheet;
 
69
  GSList *custom_stylesheets;
 
70
 
 
71
  GHashTable *stylesheets_by_filename;
 
72
  GHashTable *filenames_by_stylesheet;
 
73
 
 
74
  CRCascade *cascade;
 
75
};
 
76
 
 
77
struct _StThemeClass
 
78
{
 
79
  GObjectClass parent_class;
 
80
};
 
81
 
 
82
enum
 
83
{
 
84
  PROP_0,
 
85
  PROP_APPLICATION_STYLESHEET,
 
86
  PROP_THEME_STYLESHEET,
 
87
  PROP_DEFAULT_STYLESHEET
 
88
};
 
89
 
 
90
G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
 
91
 
 
92
/* Quick strcmp.  Test only for == 0 or != 0, not < 0 or > 0.  */
 
93
#define strqcmp(str,lit,lit_len) \
 
94
  (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
 
95
 
 
96
static void
 
97
st_theme_init (StTheme *theme)
 
98
{
 
99
  theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
 
100
                                                          (GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
 
101
  theme->filenames_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
 
102
}
 
103
 
 
104
static void
 
105
st_theme_class_init (StThemeClass *klass)
 
106
{
 
107
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
108
 
 
109
  object_class->constructor = st_theme_constructor;
 
110
  object_class->finalize = st_theme_finalize;
 
111
  object_class->set_property = st_theme_set_property;
 
112
  object_class->get_property = st_theme_get_property;
 
113
 
 
114
  /**
 
115
   * StTheme:application-stylesheet:
 
116
   *
 
117
   * The highest priority stylesheet, representing application-specific
 
118
   * styling; this is associated with the CSS "author" stylesheet.
 
119
   */
 
120
  g_object_class_install_property (object_class,
 
121
                                   PROP_APPLICATION_STYLESHEET,
 
122
                                   g_param_spec_string ("application-stylesheet",
 
123
                                                        "Application Stylesheet",
 
124
                                                        "Stylesheet with application-specific styling",
 
125
                                                        NULL,
 
126
                                                        G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
127
 
 
128
  /**
 
129
   * StTheme:theme-stylesheet:
 
130
   *
 
131
   * The second priority stylesheet, representing theme-specific styling;
 
132
   * this is associated with the CSS "user" stylesheet.
 
133
   */
 
134
  g_object_class_install_property (object_class,
 
135
                                   PROP_THEME_STYLESHEET,
 
136
                                   g_param_spec_string ("theme-stylesheet",
 
137
                                                        "Theme Stylesheet",
 
138
                                                        "Stylesheet with theme-specific styling",
 
139
                                                        NULL,
 
140
                                                        G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
141
 
 
142
  /**
 
143
   * StTheme:default-stylesheet:
 
144
   *
 
145
   * The lowest priority stylesheet, representing global default
 
146
   * styling; this is associated with the CSS "user agent" stylesheet.
 
147
   */
 
148
  g_object_class_install_property (object_class,
 
149
                                   PROP_DEFAULT_STYLESHEET,
 
150
                                   g_param_spec_string ("default-stylesheet",
 
151
                                                        "Default Stylesheet",
 
152
                                                        "Stylesheet with global default styling",
 
153
                                                        NULL,
 
154
                                                        G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
155
 
 
156
}
 
157
 
 
158
static CRStyleSheet *
 
159
parse_stylesheet (const char  *filename,
 
160
                  GError     **error)
 
161
{
 
162
  enum CRStatus status;
 
163
  CRStyleSheet *stylesheet;
 
164
 
 
165
  if (filename == NULL)
 
166
    return NULL;
 
167
 
 
168
  status = cr_om_parser_simply_parse_file ((const guchar *) filename,
 
169
                                           CR_UTF_8,
 
170
                                           &stylesheet);
 
171
 
 
172
  if (status != CR_OK)
 
173
    {
 
174
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
 
175
                   "Error parsing stylesheet '%s'; errcode:%d", filename, status);
 
176
      return NULL;
 
177
    }
 
178
 
 
179
  /* Extension stylesheet */
 
180
  stylesheet->app_data = GUINT_TO_POINTER (FALSE);
 
181
 
 
182
  return stylesheet;
 
183
}
 
184
 
 
185
CRDeclaration *
 
186
_st_theme_parse_declaration_list (const char *str)
 
187
{
 
188
  return cr_declaration_parse_list_from_buf ((const guchar *)str,
 
189
                                             CR_UTF_8);
 
190
}
 
191
 
 
192
/* Just g_warning for now until we have something nicer to do */
 
193
static CRStyleSheet *
 
194
parse_stylesheet_nofail (const char *filename)
 
195
{
 
196
  GError *error = NULL;
 
197
  CRStyleSheet *result;
 
198
 
 
199
  result = parse_stylesheet (filename, &error);
 
200
  if (error)
 
201
    {
 
202
      g_warning ("%s", error->message);
 
203
      g_clear_error (&error);
 
204
    }
 
205
  return result;
 
206
}
 
207
 
 
208
static void
 
209
insert_stylesheet (StTheme      *theme,
 
210
                   const char   *filename,
 
211
                   CRStyleSheet *stylesheet)
 
212
{
 
213
  char *filename_copy;
 
214
 
 
215
  if (stylesheet == NULL)
 
216
    return;
 
217
 
 
218
  filename_copy = g_strdup(filename);
 
219
  cr_stylesheet_ref (stylesheet);
 
220
 
 
221
  g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
 
222
  g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
 
223
}
 
224
 
 
225
gboolean
 
226
st_theme_load_stylesheet (StTheme    *theme,
 
227
                          const char *path,
 
228
                          GError    **error)
 
229
{
 
230
  CRStyleSheet *stylesheet;
 
231
 
 
232
  stylesheet = parse_stylesheet (path, error);
 
233
  if (!stylesheet)
 
234
    return FALSE;
 
235
 
 
236
  stylesheet->app_data = GUINT_TO_POINTER (TRUE);
 
237
 
 
238
  insert_stylesheet (theme, path, stylesheet);
 
239
  cr_stylesheet_ref (stylesheet);
 
240
  theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
 
241
 
 
242
  return TRUE;
 
243
}
 
244
 
 
245
void
 
246
st_theme_unload_stylesheet (StTheme    *theme,
 
247
                            const char *path)
 
248
{
 
249
  CRStyleSheet *stylesheet;
 
250
 
 
251
  stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path);
 
252
  if (!stylesheet)
 
253
    return;
 
254
 
 
255
  if (!g_slist_find (theme->custom_stylesheets, stylesheet))
 
256
    return;
 
257
 
 
258
  theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
 
259
  g_hash_table_remove (theme->stylesheets_by_filename, path);
 
260
  g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet);
 
261
  cr_stylesheet_unref (stylesheet);
 
262
}
 
263
 
 
264
/**
 
265
 * st_theme_get_custom_stylesheets:
 
266
 * @theme: an #StTheme
 
267
 *
 
268
 * Returns: (transfer full) (element-type utf8): the list of stylesheet filenames
 
269
 *          that were loaded with st_theme_load_stylesheet()
 
270
 */
 
271
GSList*
 
272
st_theme_get_custom_stylesheets (StTheme *theme)
 
273
{
 
274
  GSList *result = NULL;
 
275
  GSList *iter;
 
276
 
 
277
  for (iter = theme->custom_stylesheets; iter; iter = iter->next)
 
278
    {
 
279
      CRStyleSheet *stylesheet = iter->data;
 
280
      gchar *filename = g_hash_table_lookup (theme->filenames_by_stylesheet, stylesheet);
 
281
 
 
282
      result = g_slist_prepend (result, g_strdup (filename));
 
283
    }
 
284
 
 
285
  return result;
 
286
}
 
287
 
 
288
static GObject *
 
289
st_theme_constructor (GType                  type,
 
290
                      guint                  n_construct_properties,
 
291
                      GObjectConstructParam *construct_properties)
 
292
{
 
293
  GObject *object;
 
294
  StTheme *theme;
 
295
  CRStyleSheet *application_stylesheet;
 
296
  CRStyleSheet *theme_stylesheet;
 
297
  CRStyleSheet *default_stylesheet;
 
298
 
 
299
  object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
 
300
                                                                      n_construct_properties,
 
301
                                                                      construct_properties);
 
302
  theme = ST_THEME (object);
 
303
 
 
304
  application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
 
305
  theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
 
306
  default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
 
307
 
 
308
  theme->cascade = cr_cascade_new (application_stylesheet,
 
309
                                   theme_stylesheet,
 
310
                                   default_stylesheet);
 
311
 
 
312
  if (theme->cascade == NULL)
 
313
    g_error ("Out of memory when creating cascade object");
 
314
 
 
315
  insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
 
316
  insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
 
317
  insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
 
318
 
 
319
  return object;
 
320
}
 
321
 
 
322
static void
 
323
st_theme_finalize (GObject * object)
 
324
{
 
325
  StTheme *theme = ST_THEME (object);
 
326
 
 
327
  g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
 
328
  g_slist_free (theme->custom_stylesheets);
 
329
  theme->custom_stylesheets = NULL;
 
330
 
 
331
  g_hash_table_destroy (theme->stylesheets_by_filename);
 
332
  g_hash_table_destroy (theme->filenames_by_stylesheet);
 
333
 
 
334
  g_free (theme->application_stylesheet);
 
335
  g_free (theme->theme_stylesheet);
 
336
  g_free (theme->default_stylesheet);
 
337
 
 
338
  if (theme->cascade)
 
339
    {
 
340
      cr_cascade_unref (theme->cascade);
 
341
      theme->cascade = NULL;
 
342
    }
 
343
 
 
344
  G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
 
345
}
 
346
 
 
347
static void
 
348
st_theme_set_property (GObject      *object,
 
349
                       guint         prop_id,
 
350
                       const GValue *value,
 
351
                       GParamSpec   *pspec)
 
352
{
 
353
  StTheme *theme = ST_THEME (object);
 
354
 
 
355
  switch (prop_id)
 
356
    {
 
357
    case PROP_APPLICATION_STYLESHEET:
 
358
      {
 
359
        const char *path = g_value_get_string (value);
 
360
 
 
361
        if (path != theme->application_stylesheet)
 
362
          {
 
363
            g_free (theme->application_stylesheet);
 
364
            theme->application_stylesheet = g_strdup (path);
 
365
          }
 
366
 
 
367
        break;
 
368
      }
 
369
    case PROP_THEME_STYLESHEET:
 
370
      {
 
371
        const char *path = g_value_get_string (value);
 
372
 
 
373
        if (path != theme->theme_stylesheet)
 
374
          {
 
375
            g_free (theme->theme_stylesheet);
 
376
            theme->theme_stylesheet = g_strdup (path);
 
377
          }
 
378
 
 
379
        break;
 
380
      }
 
381
    case PROP_DEFAULT_STYLESHEET:
 
382
      {
 
383
        const char *path = g_value_get_string (value);
 
384
 
 
385
        if (path != theme->default_stylesheet)
 
386
          {
 
387
            g_free (theme->default_stylesheet);
 
388
            theme->default_stylesheet = g_strdup (path);
 
389
          }
 
390
 
 
391
        break;
 
392
      }
 
393
    default:
 
394
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
395
      break;
 
396
    }
 
397
}
 
398
 
 
399
static void
 
400
st_theme_get_property (GObject    *object,
 
401
                       guint       prop_id,
 
402
                       GValue     *value,
 
403
                       GParamSpec *pspec)
 
404
{
 
405
  StTheme *theme = ST_THEME (object);
 
406
 
 
407
  switch (prop_id)
 
408
    {
 
409
    case PROP_APPLICATION_STYLESHEET:
 
410
      g_value_set_string (value, theme->application_stylesheet);
 
411
      break;
 
412
    case PROP_THEME_STYLESHEET:
 
413
      g_value_set_string (value, theme->theme_stylesheet);
 
414
      break;
 
415
    case PROP_DEFAULT_STYLESHEET:
 
416
      g_value_set_string (value, theme->default_stylesheet);
 
417
      break;
 
418
    default:
 
419
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
420
      break;
 
421
    }
 
422
}
 
423
 
 
424
/**
 
425
 * st_theme_new:
 
426
 * @application_stylesheet: The highest priority stylesheet, representing application-specific
 
427
 *   styling; this is associated with the CSS "author" stylesheet, may be %NULL
 
428
 * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
 
429
 *   this is associated with the CSS "user" stylesheet, may be %NULL
 
430
 * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
 
431
 *   this is associated with the CSS "user agent" stylesheet, may be %NULL
 
432
 *
 
433
 * Return value: the newly created theme object
 
434
 **/
 
435
StTheme *
 
436
st_theme_new (const char       *application_stylesheet,
 
437
              const char       *theme_stylesheet,
 
438
              const char       *default_stylesheet)
 
439
{
 
440
  StTheme *theme = g_object_new (ST_TYPE_THEME,
 
441
                                    "application-stylesheet", application_stylesheet,
 
442
                                    "theme-stylesheet", theme_stylesheet,
 
443
                                    "default-stylesheet", default_stylesheet,
 
444
                                    NULL);
 
445
 
 
446
  return theme;
 
447
}
 
448
 
 
449
static gboolean
 
450
string_in_list (GString    *stryng,
 
451
                const char *list)
 
452
{
 
453
  const char *cur;
 
454
 
 
455
  for (cur = list; *cur;)
 
456
    {
 
457
      while (*cur && cr_utils_is_white_space (*cur))
 
458
        cur++;
 
459
 
 
460
      if (strncmp (cur, stryng->str, stryng->len) == 0)
 
461
        {
 
462
          cur +=  stryng->len;
 
463
          if ((!*cur) || cr_utils_is_white_space (*cur))
 
464
            return TRUE;
 
465
        }
 
466
 
 
467
      /*  skip to next whitespace character  */
 
468
      while (*cur && !cr_utils_is_white_space (*cur))
 
469
        cur++;
 
470
    }
 
471
 
 
472
  return FALSE;
 
473
}
 
474
 
 
475
static gboolean
 
476
pseudo_class_add_sel_matches_style (StTheme         *a_this,
 
477
                                    CRAdditionalSel *a_add_sel,
 
478
                                    StThemeNode     *a_node)
 
479
{
 
480
  const char *node_pseudo_class;
 
481
 
 
482
  g_return_val_if_fail (a_this
 
483
                        && a_add_sel
 
484
                        && a_add_sel->content.pseudo
 
485
                        && a_add_sel->content.pseudo->name
 
486
                        && a_add_sel->content.pseudo->name->stryng
 
487
                        && a_add_sel->content.pseudo->name->stryng->str
 
488
                        && a_node, FALSE);
 
489
 
 
490
  node_pseudo_class = st_theme_node_get_pseudo_class (a_node);
 
491
 
 
492
  if (node_pseudo_class == NULL)
 
493
    return FALSE;
 
494
 
 
495
  return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
 
496
}
 
497
 
 
498
/**
 
499
 * class_add_sel_matches_style:
 
500
 * @a_add_sel: The class additional selector to consider.
 
501
 * @a_node: The style node to consider.
 
502
 *
 
503
 * Returns: %TRUE if the class additional selector matches
 
504
 * the style node given in argument, %FALSE otherwise.
 
505
 */
 
506
static gboolean
 
507
class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
 
508
                             StThemeNode     *a_node)
 
509
{
 
510
  const char *element_class;
 
511
 
 
512
  g_return_val_if_fail (a_add_sel
 
513
                        && a_add_sel->type == CLASS_ADD_SELECTOR
 
514
                        && a_add_sel->content.class_name
 
515
                        && a_add_sel->content.class_name->stryng
 
516
                        && a_add_sel->content.class_name->stryng->str
 
517
                        && a_node, FALSE);
 
518
 
 
519
  element_class = st_theme_node_get_element_class (a_node);
 
520
  if (element_class == NULL)
 
521
    return FALSE;
 
522
 
 
523
  return string_in_list (a_add_sel->content.class_name->stryng, element_class);
 
524
}
 
525
 
 
526
/*
 
527
 *@return TRUE if the additional attribute selector matches
 
528
 *the current style node given in argument, FALSE otherwise.
 
529
 *@param a_add_sel the additional attribute selector to consider.
 
530
 *@param a_node the style node to consider.
 
531
 */
 
532
static gboolean
 
533
id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
 
534
                          StThemeNode     *a_node)
 
535
{
 
536
  gboolean result = FALSE;
 
537
  const char *id;
 
538
 
 
539
  g_return_val_if_fail (a_add_sel
 
540
                        && a_add_sel->type == ID_ADD_SELECTOR
 
541
                        && a_add_sel->content.id_name
 
542
                        && a_add_sel->content.id_name->stryng
 
543
                        && a_add_sel->content.id_name->stryng->str
 
544
                        && a_node, FALSE);
 
545
  g_return_val_if_fail (a_add_sel
 
546
                        && a_add_sel->type == ID_ADD_SELECTOR
 
547
                        && a_node, FALSE);
 
548
 
 
549
  id = st_theme_node_get_element_id (a_node);
 
550
 
 
551
  if (id != NULL)
 
552
    {
 
553
      if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
 
554
                    a_add_sel->content.id_name->stryng->len))
 
555
        {
 
556
          result = TRUE;
 
557
        }
 
558
    }
 
559
 
 
560
  return result;
 
561
}
 
562
 
 
563
/**
 
564
 *additional_selector_matches_style:
 
565
 *Evaluates if a given additional selector matches an style node.
 
566
 *@param a_add_sel the additional selector to consider.
 
567
 *@param a_node the style node to consider.
 
568
 *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
 
569
 */
 
570
static gboolean
 
571
additional_selector_matches_style (StTheme         *a_this,
 
572
                                   CRAdditionalSel *a_add_sel,
 
573
                                   StThemeNode     *a_node)
 
574
{
 
575
  CRAdditionalSel *cur_add_sel = NULL;
 
576
 
 
577
  g_return_val_if_fail (a_add_sel, FALSE);
 
578
 
 
579
  for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
 
580
    {
 
581
      switch (cur_add_sel->type)
 
582
        {
 
583
        case NO_ADD_SELECTOR:
 
584
          return FALSE;
 
585
        case CLASS_ADD_SELECTOR:
 
586
          if (!class_add_sel_matches_style (cur_add_sel, a_node))
 
587
            return FALSE;
 
588
          break;
 
589
        case ID_ADD_SELECTOR:
 
590
          if (!id_add_sel_matches_style (cur_add_sel, a_node))
 
591
            return FALSE;
 
592
          break;
 
593
        case ATTRIBUTE_ADD_SELECTOR:
 
594
          g_warning ("Attribute selectors not supported");
 
595
          return FALSE;
 
596
        case  PSEUDO_CLASS_ADD_SELECTOR:
 
597
          if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
 
598
            return FALSE;
 
599
          break;
 
600
        }
 
601
    }
 
602
 
 
603
  return TRUE;
 
604
}
 
605
 
 
606
static gboolean
 
607
element_name_matches_type (const char *element_name,
 
608
                           GType       element_type)
 
609
{
 
610
  if (element_type == G_TYPE_NONE)
 
611
    {
 
612
      return strcmp (element_name, "stage") == 0;
 
613
    }
 
614
  else
 
615
    {
 
616
      GType match_type = g_type_from_name (element_name);
 
617
      if (match_type == G_TYPE_INVALID)
 
618
        return FALSE;
 
619
 
 
620
      return g_type_is_a (element_type, match_type);
 
621
    }
 
622
}
 
623
 
 
624
/*
 
625
 *Evaluate a selector (a simple selectors list) and says
 
626
 *if it matches the style node given in parameter.
 
627
 *The algorithm used here is the following:
 
628
 *Walk the combinator separated list of simple selectors backward, starting
 
629
 *from the end of the list. For each simple selector, looks if
 
630
 *if matches the current style.
 
631
 *
 
632
 *@param a_this the selection engine.
 
633
 *@param a_sel the simple selection list.
 
634
 *@param a_node the style node.
 
635
 *@param a_result out parameter. Set to true if the
 
636
 *selector matches the style node, FALSE otherwise.
 
637
 *@param a_recurse if set to TRUE, the function will walk to
 
638
 *the next simple selector (after the evaluation of the current one)
 
639
 *and recursively evaluate it. Must be usually set to TRUE unless you
 
640
 *know what you are doing.
 
641
 */
 
642
static enum CRStatus
 
643
sel_matches_style_real (StTheme     *a_this,
 
644
                        CRSimpleSel *a_sel,
 
645
                        StThemeNode *a_node,
 
646
                        gboolean    *a_result,
 
647
                        gboolean     a_eval_sel_list_from_end,
 
648
                        gboolean     a_recurse)
 
649
{
 
650
  CRSimpleSel *cur_sel = NULL;
 
651
  StThemeNode *cur_node = NULL;
 
652
  GType cur_type;
 
653
 
 
654
  *a_result = FALSE;
 
655
 
 
656
  if (a_eval_sel_list_from_end)
 
657
    {
 
658
      /*go and get the last simple selector of the list */
 
659
      for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
 
660
        ;
 
661
    }
 
662
  else
 
663
    {
 
664
      cur_sel = a_sel;
 
665
    }
 
666
 
 
667
  cur_node = a_node;
 
668
  cur_type = st_theme_node_get_element_type (cur_node);
 
669
 
 
670
  while (cur_sel)
 
671
    {
 
672
      if (((cur_sel->type_mask & TYPE_SELECTOR)
 
673
           && (cur_sel->name
 
674
               && cur_sel->name->stryng
 
675
               && cur_sel->name->stryng->str)
 
676
           &&
 
677
           (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
 
678
          || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
 
679
        {
 
680
          /*
 
681
           *this simple selector
 
682
           *matches the current style node
 
683
           *Let's see if the preceding
 
684
           *simple selectors also match
 
685
           *their style node counterpart.
 
686
           */
 
687
          if (cur_sel->add_sel)
 
688
            {
 
689
              if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
 
690
                goto walk_a_step_in_expr;
 
691
              else
 
692
                goto done;
 
693
            }
 
694
          else
 
695
            goto walk_a_step_in_expr;
 
696
        }
 
697
      if (!(cur_sel->type_mask & TYPE_SELECTOR)
 
698
          && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
 
699
        {
 
700
          if (!cur_sel->add_sel)
 
701
            goto done;
 
702
          if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
 
703
            goto walk_a_step_in_expr;
 
704
          else
 
705
            goto done;
 
706
        }
 
707
      else
 
708
        {
 
709
          goto done;
 
710
        }
 
711
 
 
712
    walk_a_step_in_expr:
 
713
      if (a_recurse == FALSE)
 
714
        {
 
715
          *a_result = TRUE;
 
716
          goto done;
 
717
        }
 
718
 
 
719
      /*
 
720
       *here, depending on the combinator of cur_sel
 
721
       *choose the axis of the element tree traversal
 
722
       *and walk one step in the element tree.
 
723
       */
 
724
      if (!cur_sel->prev)
 
725
        break;
 
726
 
 
727
      switch (cur_sel->combinator)
 
728
        {
 
729
        case NO_COMBINATOR:
 
730
          break;
 
731
 
 
732
        case COMB_WS:           /*descendant selector */
 
733
          {
 
734
            StThemeNode *n = NULL;
 
735
 
 
736
            /*
 
737
             *walk the element tree upward looking for a parent
 
738
             *style that matches the preceding selector.
 
739
             */
 
740
            for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
 
741
              {
 
742
                enum CRStatus status;
 
743
                gboolean matches = FALSE;
 
744
 
 
745
                status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
 
746
 
 
747
                if (status != CR_OK)
 
748
                  goto done;
 
749
 
 
750
                if (matches)
 
751
                  {
 
752
                    cur_node = n;
 
753
                    cur_type = st_theme_node_get_element_type (cur_node);
 
754
                    break;
 
755
                  }
 
756
              }
 
757
 
 
758
            if (!n)
 
759
              {
 
760
                /*
 
761
                 *didn't find any ancestor that matches
 
762
                 *the previous simple selector.
 
763
                 */
 
764
                goto done;
 
765
              }
 
766
            /*
 
767
             *in this case, the preceding simple sel
 
768
             *will have been interpreted twice, which
 
769
             *is a cpu and mem waste ... I need to find
 
770
             *another way to do this. Anyway, this is
 
771
             *my first attempt to write this function and
 
772
             *I am a bit clueless.
 
773
             */
 
774
            break;
 
775
          }
 
776
 
 
777
        case COMB_PLUS:
 
778
          g_warning ("+ combinators are not supported");
 
779
          goto done;
 
780
 
 
781
        case COMB_GT:
 
782
          cur_node = st_theme_node_get_parent (cur_node);
 
783
          if (!cur_node)
 
784
            goto done;
 
785
          cur_type = st_theme_node_get_element_type (cur_node);
 
786
          break;
 
787
 
 
788
        default:
 
789
          goto done;
 
790
        }
 
791
 
 
792
      cur_sel = cur_sel->prev;
 
793
    }
 
794
 
 
795
  /*
 
796
   *if we reached this point, it means the selector matches
 
797
   *the style node.
 
798
   */
 
799
  *a_result = TRUE;
 
800
 
 
801
done:
 
802
  return CR_OK;
 
803
}
 
804
 
 
805
static void
 
806
add_matched_properties (StTheme      *a_this,
 
807
                        CRStyleSheet *a_nodesheet,
 
808
                        StThemeNode  *a_node,
 
809
                        GPtrArray    *props)
 
810
{
 
811
  CRStatement *cur_stmt = NULL;
 
812
  CRSelector *sel_list = NULL;
 
813
  CRSelector *cur_sel = NULL;
 
814
  gboolean matches = FALSE;
 
815
  enum CRStatus status = CR_OK;
 
816
 
 
817
  /*
 
818
   *walk through the list of statements and,
 
819
   *get the selectors list inside the statements that
 
820
   *contain some, and try to match our style node in these
 
821
   *selectors lists.
 
822
   */
 
823
  for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
 
824
    {
 
825
      /*
 
826
       *initialyze the selector list in which we will
 
827
       *really perform the search.
 
828
       */
 
829
      sel_list = NULL;
 
830
 
 
831
      /*
 
832
       *get the the damn selector list in
 
833
       *which we have to look
 
834
       */
 
835
      switch (cur_stmt->type)
 
836
        {
 
837
        case RULESET_STMT:
 
838
          if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
 
839
            {
 
840
              sel_list = cur_stmt->kind.ruleset->sel_list;
 
841
            }
 
842
          break;
 
843
 
 
844
        case AT_MEDIA_RULE_STMT:
 
845
          if (cur_stmt->kind.media_rule
 
846
              && cur_stmt->kind.media_rule->rulesets
 
847
              && cur_stmt->kind.media_rule->rulesets->kind.ruleset
 
848
              && cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
 
849
            {
 
850
              sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
 
851
            }
 
852
          break;
 
853
 
 
854
        case AT_IMPORT_RULE_STMT:
 
855
          {
 
856
            CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
 
857
 
 
858
            if (import_rule->sheet == NULL)
 
859
              {
 
860
                char *filename = NULL;
 
861
 
 
862
                if (import_rule->url->stryng && import_rule->url->stryng->str)
 
863
                  filename = _st_theme_resolve_url (a_this,
 
864
                                                    a_nodesheet,
 
865
                                                    import_rule->url->stryng->str);
 
866
 
 
867
                if (filename)
 
868
                  import_rule->sheet = parse_stylesheet (filename, NULL);
 
869
 
 
870
                if (import_rule->sheet)
 
871
                  {
 
872
                    insert_stylesheet (a_this, filename, import_rule->sheet);
 
873
                    /* refcount of stylesheets starts off at zero, so we don't need to unref! */
 
874
                  }
 
875
                else
 
876
                  {
 
877
                    /* Set a marker to avoid repeatedly trying to parse a non-existent or
 
878
                     * broken stylesheet
 
879
                     */
 
880
                    import_rule->sheet = (CRStyleSheet *) - 1;
 
881
                  }
 
882
 
 
883
                if (filename)
 
884
                  g_free (filename);
 
885
              }
 
886
 
 
887
            if (import_rule->sheet != (CRStyleSheet *) - 1)
 
888
              {
 
889
                add_matched_properties (a_this, import_rule->sheet,
 
890
                                        a_node, props);
 
891
              }
 
892
          }
 
893
          break;
 
894
        default:
 
895
          break;
 
896
        }
 
897
 
 
898
      if (!sel_list)
 
899
        continue;
 
900
 
 
901
      /*
 
902
       *now, we have a comma separated selector list to look in.
 
903
       *let's walk it and try to match the style node
 
904
       *on each item of the list.
 
905
       */
 
906
      for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
 
907
        {
 
908
          if (!cur_sel->simple_sel)
 
909
            continue;
 
910
 
 
911
          status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
 
912
 
 
913
          if (status == CR_OK && matches)
 
914
            {
 
915
              CRDeclaration *cur_decl = NULL;
 
916
 
 
917
              /* In order to sort the matching properties, we need to compute the
 
918
               * specificity of the selector that actually matched this
 
919
               * element. In a non-thread-safe fashion, we store it in the
 
920
               * ruleset. (Fixing this would mean cut-and-pasting
 
921
               * cr_simple_sel_compute_specificity(), and have no need for
 
922
               * thread-safety anyways.)
 
923
               *
 
924
               * Once we've sorted the properties, the specificity no longer
 
925
               * matters and it can be safely overriden.
 
926
               */
 
927
              cr_simple_sel_compute_specificity (cur_sel->simple_sel);
 
928
 
 
929
              cur_stmt->specificity = cur_sel->simple_sel->specificity;
 
930
 
 
931
              for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
 
932
                g_ptr_array_add (props, cur_decl);
 
933
            }
 
934
        }
 
935
    }
 
936
}
 
937
 
 
938
#define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS)
 
939
#define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2)
 
940
 
 
941
static inline int
 
942
get_origin (const CRDeclaration * decl)
 
943
{
 
944
  enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
 
945
  gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data);
 
946
 
 
947
  if (decl->important)
 
948
    origin += ORIGIN_OFFSET_IMPORTANT;
 
949
 
 
950
  if (is_extension_sheet)
 
951
    origin += ORIGIN_OFFSET_EXTENSION;
 
952
 
 
953
  return origin;
 
954
}
 
955
 
 
956
/* Order of comparison is so that higher priority statements compare after
 
957
 * lower priority statements */
 
958
static int
 
959
compare_declarations (gconstpointer a,
 
960
                      gconstpointer b)
 
961
{
 
962
  /* g_ptr_array_sort() is broooken */
 
963
  CRDeclaration *decl_a = *(CRDeclaration **) a;
 
964
  CRDeclaration *decl_b = *(CRDeclaration **) b;
 
965
 
 
966
  int origin_a = get_origin (decl_a);
 
967
  int origin_b = get_origin (decl_b);
 
968
 
 
969
  if (origin_a != origin_b)
 
970
    return origin_a - origin_b;
 
971
 
 
972
  if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
 
973
    return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
 
974
 
 
975
  return 0;
 
976
}
 
977
 
 
978
GPtrArray *
 
979
_st_theme_get_matched_properties (StTheme        *theme,
 
980
                                  StThemeNode    *node)
 
981
{
 
982
  enum CRStyleOrigin origin = 0;
 
983
  CRStyleSheet *sheet = NULL;
 
984
  GPtrArray *props = g_ptr_array_new ();
 
985
  GSList *iter;
 
986
 
 
987
  g_return_val_if_fail (ST_IS_THEME (theme), NULL);
 
988
  g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
 
989
 
 
990
  for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
 
991
    {
 
992
      sheet = cr_cascade_get_sheet (theme->cascade, origin);
 
993
      if (!sheet)
 
994
        continue;
 
995
 
 
996
      add_matched_properties (theme, sheet, node, props);
 
997
    }
 
998
 
 
999
  for (iter = theme->custom_stylesheets; iter; iter = iter->next)
 
1000
    add_matched_properties (theme, iter->data, node, props);
 
1001
 
 
1002
  /* We count on a stable sort here so that later declarations come
 
1003
   * after earlier declarations */
 
1004
  g_ptr_array_sort (props, compare_declarations);
 
1005
 
 
1006
  return props;
 
1007
}
 
1008
 
 
1009
/* Resolve an url from an url() reference in a stylesheet into an absolute
 
1010
 * local filename, if possible. The resolution here is distinctly lame and
 
1011
 * will fail on many examples.
 
1012
 */
 
1013
char *
 
1014
_st_theme_resolve_url (StTheme      *theme,
 
1015
                       CRStyleSheet *base_stylesheet,
 
1016
                       const char   *url)
 
1017
{
 
1018
  const char *base_filename = NULL;
 
1019
  char *dirname;
 
1020
  char *filename;
 
1021
 
 
1022
  /* Handle absolute file:/ URLs */
 
1023
  if (g_str_has_prefix (url, "file:") ||
 
1024
      g_str_has_prefix (url, "File:") ||
 
1025
      g_str_has_prefix (url, "FILE:"))
 
1026
    {
 
1027
      GError *error = NULL;
 
1028
      char *filename;
 
1029
 
 
1030
      filename = g_filename_from_uri (url, NULL, &error);
 
1031
      if (filename == NULL)
 
1032
        {
 
1033
          g_warning ("%s", error->message);
 
1034
          g_error_free (error);
 
1035
        }
 
1036
 
 
1037
      return NULL;
 
1038
    }
 
1039
 
 
1040
  /* Guard against http:/ URLs */
 
1041
 
 
1042
  if (g_str_has_prefix (url, "http:") ||
 
1043
      g_str_has_prefix (url, "Http:") ||
 
1044
      g_str_has_prefix (url, "HTTP:"))
 
1045
    {
 
1046
      g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
 
1047
      return NULL;
 
1048
    }
 
1049
 
 
1050
  /* Assume anything else is a relative URL, and "resolve" it
 
1051
   */
 
1052
  if (url[0] == '/')
 
1053
    return g_strdup (url);
 
1054
 
 
1055
  base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
 
1056
 
 
1057
  if (base_filename == NULL)
 
1058
    {
 
1059
      g_warning ("Can't get base to resolve url '%s'", url);
 
1060
      return NULL;
 
1061
    }
 
1062
 
 
1063
  dirname = g_path_get_dirname (base_filename);
 
1064
  filename = g_build_filename (dirname, url, NULL);
 
1065
  g_free (dirname);
 
1066
 
 
1067
  return filename;
 
1068
}